Skip to content

Commit

Permalink
fix(world): library imports and overloaded system functions (#3395)
Browse files Browse the repository at this point in the history
Co-authored-by: Andy Cernera <[email protected]>
Co-authored-by: Kevin Ingersoll <[email protected]>
  • Loading branch information
3 people authored Jan 3, 2025
1 parent 8f5f4a7 commit ba5191c
Show file tree
Hide file tree
Showing 17 changed files with 683 additions and 54 deletions.
5 changes: 5 additions & 0 deletions .changeset/real-pigs-bake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@latticexyz/world": patch
---

Fixes relative system imports in system libraries and adds support for overloaded system functions.
56 changes: 44 additions & 12 deletions packages/world/ts/node/render-solidity/renderSystemLibrary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,9 @@ export function renderSystemLibrary(options: RenderSystemLibraryOptions) {
${renderList(functions, (contractFunction) => renderUserTypeFunction(contractFunction, userTypeName))}
${renderList(functions, (contractFunction) => renderCallWrapperFunction(contractFunction, systemLabel, callingFromRootSystemErrorName))}
${renderList(functions, (contractFunction) => renderCallWrapperFunction(contractFunction, callingFromRootSystemErrorName))}
${renderList(functions, (contractFunction) => renderRootCallWrapperFunction(contractFunction, systemLabel, namespace))}
${renderList(functions, (contractFunction) => renderRootCallWrapperFunction(contractFunction, namespace))}
function callFrom(${userTypeName} self, address from) internal pure returns (CallWrapper memory) {
return CallWrapper(self.toResourceId(), from);
Expand Down Expand Up @@ -120,6 +120,21 @@ export function renderSystemLibrary(options: RenderSystemLibraryOptions) {
}
}
${
functions.length > 0
? `/**
* System Function Interfaces
*
* We generate an interface for each system function, which is then used for encoding system calls.
* This is necessary to handle function overloading correctly (which abi.encodeCall cannot).
*
* Each interface is uniquely named based on the function name and parameters to prevent collisions.
*/`
: ""
}
${renderList(functions, (contractFunction) => renderFunctionInterface(contractFunction))}
using ${libraryName} for ${userTypeName} global;
using ${libraryName} for CallWrapper global;
using ${libraryName} for RootCallWrapper global;
Expand Down Expand Up @@ -154,7 +169,6 @@ function renderUserTypeFunction(contractFunction: ContractInterfaceFunction, use

function renderCallWrapperFunction(
contractFunction: ContractInterfaceFunction,
systemLabel: string,
callingFromRootSystemErrorName: string,
) {
const { name, parameters, stateMutability, returnParameters } = contractFunction;
Expand All @@ -174,7 +188,7 @@ function renderCallWrapperFunction(
if (address(_world()) == address(this)) revert ${callingFromRootSystemErrorName}();
`;

const encodedSystemCall = renderEncodeSystemCall(systemLabel, name, parameters);
const encodedSystemCall = renderEncodeSystemCall(contractFunction);

if (stateMutability === "") {
return `
Expand Down Expand Up @@ -203,11 +217,7 @@ function renderCallWrapperFunction(
}
}

function renderRootCallWrapperFunction(
contractFunction: ContractInterfaceFunction,
systemLabel: string,
namespace: string,
) {
function renderRootCallWrapperFunction(contractFunction: ContractInterfaceFunction, namespace: string) {
const { name, parameters, stateMutability, returnParameters } = contractFunction;

// Staticcalls are not supported between root systems yet, due to the additional
Expand All @@ -226,7 +236,7 @@ function renderRootCallWrapperFunction(
${renderReturnParameters(returnParameters)}
`;

const encodedSystemCall = renderEncodeSystemCall(systemLabel, name, parameters);
const encodedSystemCall = renderEncodeSystemCall(contractFunction);

if (stateMutability === "") {
return `
Expand All @@ -248,9 +258,31 @@ function renderRootCallWrapperFunction(
}
}

function renderEncodeSystemCall(interfaceName: string, functionName: string, parameters: string[]) {
function renderFunctionInterface(contractFunction: ContractInterfaceFunction) {
const { name, parameters } = contractFunction;

return `
interface ${functionInterfaceName(contractFunction)} {
function ${name}(
${renderArguments(parameters)}
) external;
}
`;
}

function functionInterfaceName(contractFunction: ContractInterfaceFunction) {
const { name, parameters } = contractFunction;
const paramTypes = parameters
.map((param) => param.split(" ")[0])
.map((type) => type.replace("[]", "Array"))
.join("_");
return `_${name}${paramTypes.length === 0 ? "" : `_${paramTypes}`}`;
}

function renderEncodeSystemCall(contractFunction: ContractInterfaceFunction) {
const { name, parameters } = contractFunction;
const paramNames = parameters.map((param) => param.split(" ").slice(-1)[0]).join(", ");
return `abi.encodeCall(${interfaceName}.${functionName}, (${paramNames}))`;
return `abi.encodeCall(${functionInterfaceName(contractFunction)}.${name}, (${paramNames}))`;
}

function renderAbiDecode(expression: string, returnParameters: string[]) {
Expand Down
30 changes: 22 additions & 8 deletions packages/world/ts/node/render-solidity/worldgen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,30 +80,44 @@ export async function worldgen({
const source = await fs.readFile(path.join(rootDir, system.sourcePath), "utf8");
// get external functions from a contract
const { functions, errors, symbolImports } = contractToInterface(source, system.label);
const imports = symbolImports.map(
const interfaceImports = symbolImports.map(
({ symbol, path: importPath }): ImportDatum => ({
symbol,
path: importPath.startsWith(".")
? "./" + path.relative(worldgenOutDir, path.join(rootDir, path.dirname(system.sourcePath), importPath))
: importPath,
}),
);

const systemInterface = renderSystemInterface({
name: system.interfaceName,
functionPrefix: system.namespace === "" ? "" : `${system.namespace}__`,
functions,
errors,
imports,
imports: interfaceImports,
});
// write to file
await formatAndWriteSolidity(systemInterface, system.interfacePath, "Generated system interface");

const systemImport = {
symbol: system.label,
path: "./" + path.relative(path.dirname(system.libraryPath), system.sourcePath),
};

if (config.codegen.generateSystemLibraries) {
const systemImport = {
symbol: system.label,
path: "./" + path.relative(path.dirname(system.libraryPath), system.sourcePath),
};

const libraryImports = symbolImports.map(
({ symbol, path: importPath }): ImportDatum => ({
symbol,
path: importPath.startsWith(".")
? "./" +
path.relative(
path.dirname(system.libraryPath),
path.join(rootDir, path.dirname(system.sourcePath), importPath),
)
: importPath,
}),
);

const systemLibrary = renderSystemLibrary({
libraryName: system.libraryName,
interfaceName: system.interfaceName,
Expand All @@ -113,7 +127,7 @@ export async function worldgen({
resourceId: resourceToHex({ type: "system", namespace: system.namespace, name: system.name }),
functions,
errors,
imports: [systemImport, ...imports],
imports: [systemImport, ...libraryImports],
storeImportPath,
worldImportPath,
});
Expand Down
8 changes: 8 additions & 0 deletions test/system-libraries/mud.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ export default defineWorld({
},
key: [],
},
PositionValue: {
schema: {
x: "uint256",
y: "uint256",
z: "uint256",
},
key: [],
},
},
},
b: {},
Expand Down
10 changes: 10 additions & 0 deletions test/system-libraries/src/codegen/world/IASystem.sol

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion test/system-libraries/src/codegen/world/IBSystem.sol

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion test/system-libraries/src/codegen/world/IRootSystem.sol

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions test/system-libraries/src/namespaces/a/ASystem.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,33 @@ pragma solidity >=0.8.28;

import { System } from "@latticexyz/world/src/System.sol";
import { Value } from "./codegen/tables/Value.sol";
import { PositionValue } from "./codegen/tables/PositionValue.sol";
import { AddressValue } from "./codegen/tables/AddressValue.sol";
import { ASystemThing, Position } from "./ASystemTypes.sol";

contract ASystem is System {
function setValue(ASystemThing memory value) external {
Value.set(value.a);
}

function setValue(uint256 value) external {
Value.set(value);
}

function setPosition(Position memory position) external {
PositionValue.set(position.x, position.y, position.z);
}

function setPosition(uint256 x, uint256 y, uint256 z) external {
PositionValue.set(x, y, z);
}

function setPositions(Position[] memory positions) external {
for (uint256 i = 0; i < positions.length; i++) {
PositionValue.set(positions[i].x, positions[i].y, positions[i].z);
}
}

function getValue() external view returns (uint256) {
return Value.get();
}
Expand Down
12 changes: 12 additions & 0 deletions test/system-libraries/src/namespaces/a/ASystemTypes.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.28;

struct ASystemThing {
uint256 a;
}

struct Position {
uint256 x;
uint256 y;
uint256 z;
}
1 change: 1 addition & 0 deletions test/system-libraries/src/namespaces/a/codegen/index.sol

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit ba5191c

Please sign in to comment.