Skip to content

Commit 03af917

Browse files
vdrgfrolic
andauthored
feat(world): find systems based on inheritance (#3649)
Co-authored-by: Kevin Ingersoll <[email protected]>
1 parent b8239d8 commit 03af917

File tree

17 files changed

+169
-99
lines changed

17 files changed

+169
-99
lines changed

.changeset/small-horses-own.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
"@latticexyz/world": patch
3+
"@latticexyz/cli": patch
4+
---
5+
6+
`mud` CLI commands will now recognize systems if they inherit directly from the base `System` imported from `@latticexyz/world/src/System.sol`, allowing you to write systems without a `System` suffix.
7+
8+
```solidity
9+
import {System} from "@latticexyz/world/src/System.sol";
10+
11+
contract EntityProgram is System {
12+
...
13+
}
14+
```
15+
16+
If you have contracts that inherit from the base `System` that aren't meant to be deployed, you can mark them as `abstract contract` or [disable the system's deploy via config](https://mud.dev/config/reference).

packages/cli/src/build.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ type BuildOptions = {
1616
};
1717

1818
export async function build({ rootDir, config, foundryProfile }: BuildOptions): Promise<void> {
19-
await Promise.all([tablegen({ rootDir, config }), worldgen({ rootDir, config })]);
19+
await tablegen({ rootDir, config });
20+
await worldgen({ rootDir, config });
2021
await printCommand(
2122
execa("forge", ["build"], {
2223
stdio: "inherit",

packages/common/src/codegen/utils/contractToInterface.ts

Lines changed: 8 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
import { parse, visit } from "@solidity-parser/parser";
2-
import type {
3-
ContractDefinition,
4-
SourceUnit,
5-
TypeName,
6-
VariableDeclaration,
7-
} from "@solidity-parser/parser/dist/src/ast-types";
2+
import type { SourceUnit, TypeName, VariableDeclaration } from "@solidity-parser/parser/dist/src/ast-types";
83
import { MUDError } from "../../errors";
4+
import { findContractNode } from "./findContractNode";
5+
import { SymbolImport, findSymbolImport } from "./findSymbolImport";
96

107
export interface ContractInterfaceFunction {
118
name: string;
@@ -19,11 +16,6 @@ export interface ContractInterfaceError {
1916
parameters: string[];
2017
}
2118

22-
interface SymbolImport {
23-
symbol: string;
24-
path: string;
25-
}
26-
2719
/**
2820
* Parse the contract data to get the functions necessary to generate an interface,
2921
* and symbols to import from the original contract.
@@ -106,20 +98,6 @@ export function contractToInterface(
10698
};
10799
}
108100

109-
export function findContractNode(ast: SourceUnit, contractName: string): ContractDefinition | undefined {
110-
let contract: ContractDefinition | undefined = undefined;
111-
112-
visit(ast, {
113-
ContractDefinition(node) {
114-
if (node.name === contractName) {
115-
contract = node;
116-
}
117-
},
118-
});
119-
120-
return contract;
121-
}
122-
123101
function parseParameter({ name, typeName, storageLocation }: VariableDeclaration): string {
124102
let typedNameWithLocation = "";
125103

@@ -197,42 +175,10 @@ function typeNameToSymbols(typeName: TypeName | null): string[] {
197175
}
198176
}
199177

200-
// Get imports for given symbols.
201-
// To avoid circular dependencies of interfaces on their implementations,
202-
// symbols used for args/returns must always be imported from an auxiliary file.
203-
// To avoid parsing the entire project to build dependencies,
204-
// symbols must be imported with an explicit `import { symbol } from ...`
205178
function symbolsToImports(ast: SourceUnit, symbols: string[]): SymbolImport[] {
206-
const imports: SymbolImport[] = [];
207-
208-
for (const symbol of symbols) {
209-
let symbolImport: SymbolImport | undefined;
210-
211-
visit(ast, {
212-
ImportDirective({ path, symbolAliases }) {
213-
if (symbolAliases) {
214-
for (const symbolAndAlias of symbolAliases) {
215-
// either check the alias, or the original symbol if there's no alias
216-
const symbolAlias = symbolAndAlias[1] || symbolAndAlias[0];
217-
if (symbol === symbolAlias) {
218-
symbolImport = {
219-
// always use the original symbol for interface imports
220-
symbol: symbolAndAlias[0],
221-
path,
222-
};
223-
return;
224-
}
225-
}
226-
}
227-
},
228-
});
229-
230-
if (symbolImport) {
231-
imports.push(symbolImport);
232-
} else {
233-
throw new MUDError(`Symbol "${symbol}" has no explicit import`);
234-
}
235-
}
236-
237-
return imports;
179+
return symbols.map((symbol) => {
180+
const symbolImport = findSymbolImport(ast, symbol);
181+
if (!symbolImport) throw new MUDError(`Symbol "${symbol}" has no explicit import`);
182+
return symbolImport;
183+
});
238184
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { visit } from "@solidity-parser/parser";
2+
import type { ContractDefinition, SourceUnit } from "@solidity-parser/parser/dist/src/ast-types";
3+
4+
export function findContractNode(ast: SourceUnit, contractName: string): ContractDefinition | undefined {
5+
let contract: ContractDefinition | undefined = undefined;
6+
7+
visit(ast, {
8+
ContractDefinition(node) {
9+
if (node.name === contractName) {
10+
contract = node;
11+
}
12+
},
13+
});
14+
15+
return contract;
16+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { visit } from "@solidity-parser/parser";
2+
import type { SourceUnit } from "@solidity-parser/parser/dist/src/ast-types";
3+
4+
export interface SymbolImport {
5+
symbol: string;
6+
path: string;
7+
}
8+
9+
/**
10+
* Get import for given symbol.
11+
*
12+
* To avoid circular dependencies of interfaces on their implementations,
13+
* symbols used for args/returns must always be imported from an auxiliary file.
14+
* To avoid parsing the entire project to build dependencies,
15+
* symbols must be imported with an explicit `import { symbol } from ...`
16+
*/
17+
export function findSymbolImport(ast: SourceUnit, symbol: string): SymbolImport | undefined {
18+
let symbolImport: SymbolImport | undefined;
19+
20+
visit(ast, {
21+
ImportDirective({ path, symbolAliases }) {
22+
if (symbolAliases) {
23+
for (const symbolAndAlias of symbolAliases) {
24+
// either check the alias, or the original symbol if there's no alias
25+
const symbolAlias = symbolAndAlias[1] ?? symbolAndAlias[0];
26+
if (symbol === symbolAlias) {
27+
symbolImport = {
28+
// always use the original symbol for interface imports
29+
symbol: symbolAndAlias[0],
30+
path,
31+
};
32+
return;
33+
}
34+
}
35+
}
36+
},
37+
});
38+
39+
return symbolImport;
40+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from "./contractToInterface";
22
export * from "./format";
33
export * from "./formatAndWrite";
4+
export * from "./parseSystem";
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { parse, visit } from "@solidity-parser/parser";
2+
3+
import { findContractNode } from "./findContractNode";
4+
import { findSymbolImport } from "./findSymbolImport";
5+
6+
const baseSystemName = "System";
7+
const baseSystemPath = "@latticexyz/world/src/System.sol";
8+
9+
export function parseSystem(
10+
source: string,
11+
contractName: string,
12+
): undefined | { contractType: "contract" | "abstract" } {
13+
const ast = parse(source);
14+
const contractNode = findContractNode(ast, contractName);
15+
if (!contractNode) return;
16+
17+
const contractType = contractNode.kind;
18+
// skip libraries and interfaces
19+
// we allow abstract systems here so that we can create system libraries from them but without deploying them
20+
if (contractType !== "contract" && contractType !== "abstract") return;
21+
22+
const isSystem = ((): boolean => {
23+
// if using the System suffix, assume its a system
24+
if (contractName.endsWith("System") && contractName !== baseSystemName) return true;
25+
26+
// otherwise check if we're inheriting from the base system
27+
let extendsBaseSystem = false;
28+
visit(contractNode, {
29+
InheritanceSpecifier(node) {
30+
if (node.baseName.namePath === baseSystemName) {
31+
extendsBaseSystem = true;
32+
}
33+
},
34+
});
35+
return extendsBaseSystem && findSymbolImport(ast, baseSystemName)?.path === baseSystemPath;
36+
})();
37+
38+
if (isSystem) {
39+
return { contractType };
40+
}
41+
}

packages/store/ts/flattenStoreLogs.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,8 @@ describe("flattenStoreLogs", async () => {
134134
"Store_SetRecord store__ResourceIds (0x746200000000000000000000000000005465727261696e000000000000000000)",
135135
"Store_SetRecord store__ResourceIds (0x737900000000000000000000000000004d6f766553797374656d000000000000)",
136136
"Store_SetRecord world__Systems (0x737900000000000000000000000000004d6f766553797374656d000000000000)",
137-
"Store_SetRecord world__SystemRegistry (0x000000000000000000000000cbcdc66f9301ccf30b6b46efba8a3015d332dc13)",
138-
"Store_SetRecord world__ResourceAccess (0x6e73000000000000000000000000000000000000000000000000000000000000,0x000000000000000000000000cbcdc66f9301ccf30b6b46efba8a3015d332dc13)",
137+
"Store_SetRecord world__SystemRegistry (0x00000000000000000000000040d21680e49a1f969a53760ff488a9d1ad01ca89)",
138+
"Store_SetRecord world__ResourceAccess (0x6e73000000000000000000000000000000000000000000000000000000000000,0x00000000000000000000000040d21680e49a1f969a53760ff488a9d1ad01ca89)",
139139
"Store_SetRecord world__FunctionSelector (0xb591186e00000000000000000000000000000000000000000000000000000000)",
140140
"Store_SetRecord world__FunctionSignatur (0xb591186e00000000000000000000000000000000000000000000000000000000)",
141141
"Store_SetRecord store__Tables (0x7462000000000000000000000000000043616c6c576974685369676e61747572)",

packages/store/ts/getStoreLogs.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,8 @@ describe("getStoreLogs", async () => {
157157
"Store_SpliceStaticData store__ResourceIds (0x746200000000000000000000000000005465727261696e000000000000000000)",
158158
"Store_SpliceStaticData store__ResourceIds (0x737900000000000000000000000000004d6f766553797374656d000000000000)",
159159
"Store_SetRecord world__Systems (0x737900000000000000000000000000004d6f766553797374656d000000000000)",
160-
"Store_SpliceStaticData world__SystemRegistry (0x000000000000000000000000cbcdc66f9301ccf30b6b46efba8a3015d332dc13)",
161-
"Store_SpliceStaticData world__ResourceAccess (0x6e73000000000000000000000000000000000000000000000000000000000000,0x000000000000000000000000cbcdc66f9301ccf30b6b46efba8a3015d332dc13)",
160+
"Store_SpliceStaticData world__SystemRegistry (0x00000000000000000000000040d21680e49a1f969a53760ff488a9d1ad01ca89)",
161+
"Store_SpliceStaticData world__ResourceAccess (0x6e73000000000000000000000000000000000000000000000000000000000000,0x00000000000000000000000040d21680e49a1f969a53760ff488a9d1ad01ca89)",
162162
"Store_SetRecord world__FunctionSelector (0xb591186e00000000000000000000000000000000000000000000000000000000)",
163163
"Store_SetRecord world__FunctionSignatur (0xb591186e00000000000000000000000000000000000000000000000000000000)",
164164
"Store_SetRecord world__FunctionSignatur (0xb591186e00000000000000000000000000000000000000000000000000000000)",

packages/world-module-callwithsignature/ts/build.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,6 @@ const configPath = "../mud.config";
1515

1616
const { default: config } = await import(configPath);
1717
const rootDir = path.dirname(path.join(__dirname, configPath));
18-
await Promise.all([tablegen({ rootDir, config }), worldgen({ rootDir, config })]);
18+
19+
await tablegen({ rootDir, config });
20+
await worldgen({ rootDir, config });

0 commit comments

Comments
 (0)