diff --git a/packages/world/ts/node/render-solidity/renderSystemLibrary.ts b/packages/world/ts/node/render-solidity/renderSystemLibrary.ts index dfa8301a46..38a3687c0f 100644 --- a/packages/world/ts/node/render-solidity/renderSystemLibrary.ts +++ b/packages/world/ts/node/render-solidity/renderSystemLibrary.ts @@ -14,7 +14,8 @@ export function renderSystemLibrary(options: RenderSystemLibraryOptions) { libraryName, systemLabel, resourceId, - functions, + namespace, + functions: systemFunctions, errors: systemErrors, worldImportPath, storeImportPath, @@ -49,11 +50,15 @@ export function renderSystemLibrary(options: RenderSystemLibraryOptions) { }, ]; - const errors = [...systemErrors]; + const callingFromRootSystemErrorName = `${libraryName}_CallingFromRootSystem`; + const errors = [{ name: callingFromRootSystemErrorName, parameters: [] }, ...systemErrors]; const camelCaseSystemLabel = systemLabel.charAt(0).toLowerCase() + systemLabel.slice(1); const userTypeName = `${systemLabel}Type`; + // Remove view functions for root systems + const functions = systemFunctions.filter(({ stateMutability }) => namespace !== "" || stateMutability === ""); + return ` ${renderedSolidityHeader} @@ -81,11 +86,11 @@ export function renderSystemLibrary(options: RenderSystemLibraryOptions) { library ${libraryName} { ${renderErrors(errors)} - ${renderUserTypeFunctions(functions, userTypeName)} + ${renderList(functions, (contractFunction) => renderUserTypeFunction(contractFunction, userTypeName))} - ${renderCallWrapperFunctions(functions, systemLabel)} + ${renderList(functions, (contractFunction) => renderCallWrapperFunction(contractFunction, systemLabel, callingFromRootSystemErrorName))} - ${renderRootCallWrapperFunctions(functions, systemLabel)} + ${renderList(functions, (contractFunction) => renderRootCallWrapperFunction(contractFunction, systemLabel))} function callFrom(${userTypeName} self, address from) internal pure returns (CallWrapper memory) { return CallWrapper(self.toResourceId(), from); @@ -130,10 +135,6 @@ function renderErrors(errors: ContractInterfaceError[]) { return renderList(errors, ({ name, parameters }) => ` error ${name}(${renderArguments(parameters)});`); } -function renderUserTypeFunctions(functions: ContractInterfaceFunction[], userTypeName: string) { - return renderList(functions, (contractFunction) => renderUserTypeFunction(contractFunction, userTypeName)); -} - function renderUserTypeFunction(contractFunction: ContractInterfaceFunction, userTypeName: string) { const { name, parameters, stateMutability, returnParameters } = contractFunction; @@ -156,11 +157,11 @@ function renderUserTypeFunction(contractFunction: ContractInterfaceFunction, use `; } -function renderCallWrapperFunctions(functions: ContractInterfaceFunction[], systemLabel: string) { - return renderList(functions, (contractFunction) => renderCallWrapperFunction(contractFunction, systemLabel)); -} - -function renderCallWrapperFunction(contractFunction: ContractInterfaceFunction, systemLabel: string) { +function renderCallWrapperFunction( + contractFunction: ContractInterfaceFunction, + systemLabel: string, + callingFromRootSystemErrorName: string, +) { const { name, parameters, stateMutability, returnParameters } = contractFunction; const functionArguments = [`CallWrapper memory self`, ...parameters]; @@ -173,11 +174,17 @@ function renderCallWrapperFunction(contractFunction: ContractInterfaceFunction, ${renderReturnParameters(returnParameters)} `; + const rootSystemCheck = ` + // if the contract calling this function is a root system, it should use \`callAsRoot\` + if (address(_world()) == address(this)) revert ${callingFromRootSystemErrorName}(); + `; + const encodedSystemCall = renderEncodeSystemCall(systemLabel, name, parameters); if (stateMutability === "") { return ` ${functionSignature} { + ${rootSystemCheck} bytes memory systemCall = ${encodedSystemCall}; bytes memory result = self.from == address(0) ? _world().call(self.systemId, systemCall) : _world().callFrom(self.from, self.systemId, systemCall); ${renderAbiDecode(returnParameters)} @@ -186,6 +193,7 @@ function renderCallWrapperFunction(contractFunction: ContractInterfaceFunction, } else { return ` ${functionSignature} { + ${rootSystemCheck} bytes memory systemCall = ${encodedSystemCall}; bytes memory worldCall = self.from == address(0) ? abi.encodeCall(IWorldCall.call, (self.systemId, systemCall)) @@ -199,20 +207,16 @@ function renderCallWrapperFunction(contractFunction: ContractInterfaceFunction, } } -function renderRootCallWrapperFunctions(functions: ContractInterfaceFunction[], systemLabel: string) { - return renderList(functions, (contractFunction) => renderRootCallWrapperFunction(contractFunction, systemLabel)); -} - function renderRootCallWrapperFunction(contractFunction: ContractInterfaceFunction, systemLabel: string) { const { name, parameters, stateMutability, returnParameters } = contractFunction; - const functionArguments = [`RootCallWrapper memory ${stateMutability === "" ? "self" : ""}`, ...parameters]; + const functionArguments = ["RootCallWrapper memory self", ...parameters]; const functionSignature = ` function ${name}( ${renderArguments(functionArguments)} ) internal - ${stateMutability === "view" ? "pure" : stateMutability} + ${stateMutability === "pure" ? "view" : stateMutability} ${renderReturnParameters(returnParameters)} `; @@ -229,7 +233,9 @@ function renderRootCallWrapperFunction(contractFunction: ContractInterfaceFuncti } else { return ` ${functionSignature} { - revert("Static calls not implemented for root systems"); + bytes memory systemCall = ${encodedSystemCall}; + bytes memory result = SystemCall.staticcallOrRevert(self.from, self.systemId, systemCall); + ${renderAbiDecode(returnParameters)} } `; } diff --git a/packages/world/ts/node/render-solidity/types.ts b/packages/world/ts/node/render-solidity/types.ts index 09d502add7..8d0315d097 100644 --- a/packages/world/ts/node/render-solidity/types.ts +++ b/packages/world/ts/node/render-solidity/types.ts @@ -16,6 +16,7 @@ export interface RenderSystemLibraryOptions { interfaceName: string; libraryName: string; resourceId: string; + namespace: string; functions: ContractInterfaceFunction[]; errors: ContractInterfaceError[]; diff --git a/packages/world/ts/node/render-solidity/worldgen.ts b/packages/world/ts/node/render-solidity/worldgen.ts index cc6d32f503..7053008e3e 100644 --- a/packages/world/ts/node/render-solidity/worldgen.ts +++ b/packages/world/ts/node/render-solidity/worldgen.ts @@ -109,6 +109,7 @@ export async function worldgen({ interfaceName: system.interfaceName, systemLabel: system.label, resourceId: resourceToHex({ type: "system", namespace: system.namespace, name: system.name }), + namespace: system.namespace, functions, errors, imports: [systemImport, ...imports], diff --git a/test/system-libraries/src/codegen/world/IRootSystem.sol b/test/system-libraries/src/codegen/world/IRootSystem.sol index 040d261b3d..e7d8d7c8fd 100644 --- a/test/system-libraries/src/codegen/world/IRootSystem.sol +++ b/test/system-libraries/src/codegen/world/IRootSystem.sol @@ -10,4 +10,6 @@ pragma solidity >=0.8.24; */ interface IRootSystem { function setValueInA(uint256 value) external; + + function getValueFromA() external view returns (uint256); } diff --git a/test/system-libraries/src/namespaces/a/codegen/libraries/ASystemLib.sol b/test/system-libraries/src/namespaces/a/codegen/libraries/ASystemLib.sol index eaa2a0b4c5..23c1e4597d 100644 --- a/test/system-libraries/src/namespaces/a/codegen/libraries/ASystemLib.sol +++ b/test/system-libraries/src/namespaces/a/codegen/libraries/ASystemLib.sol @@ -31,6 +31,8 @@ struct RootCallWrapper { * @dev This library is automatically generated from the corresponding system contract. Do not edit manually. */ library ASystemLib { + error ASystemLib_CallingFromRootSystem(); + function setValue(ASystemType self, uint256 value) internal { return CallWrapper(self.toResourceId(), address(0)).setValue(value); } @@ -48,6 +50,9 @@ library ASystemLib { } function setValue(CallWrapper memory self, uint256 value) internal { + // if the contract calling this function is a root system, it should use `callAsRoot` + if (address(_world()) == address(this)) revert ASystemLib_CallingFromRootSystem(); + bytes memory systemCall = abi.encodeCall(ASystem.setValue, (value)); bytes memory result = self.from == address(0) ? _world().call(self.systemId, systemCall) @@ -56,6 +61,9 @@ library ASystemLib { } function getValue(CallWrapper memory self) internal view returns (uint256) { + // if the contract calling this function is a root system, it should use `callAsRoot` + if (address(_world()) == address(this)) revert ASystemLib_CallingFromRootSystem(); + bytes memory systemCall = abi.encodeCall(ASystem.getValue, ()); bytes memory worldCall = self.from == address(0) ? abi.encodeCall(IWorldCall.call, (self.systemId, systemCall)) @@ -67,6 +75,9 @@ library ASystemLib { } function getTwoValues(CallWrapper memory self) internal view returns (uint256, uint256) { + // if the contract calling this function is a root system, it should use `callAsRoot` + if (address(_world()) == address(this)) revert ASystemLib_CallingFromRootSystem(); + bytes memory systemCall = abi.encodeCall(ASystem.getTwoValues, ()); bytes memory worldCall = self.from == address(0) ? abi.encodeCall(IWorldCall.call, (self.systemId, systemCall)) @@ -78,6 +89,9 @@ library ASystemLib { } function setAddress(CallWrapper memory self) internal returns (address) { + // if the contract calling this function is a root system, it should use `callAsRoot` + if (address(_world()) == address(this)) revert ASystemLib_CallingFromRootSystem(); + bytes memory systemCall = abi.encodeCall(ASystem.setAddress, ()); bytes memory result = self.from == address(0) ? _world().call(self.systemId, systemCall) @@ -91,12 +105,16 @@ library ASystemLib { result; } - function getValue(RootCallWrapper memory) internal pure returns (uint256) { - revert("Static calls not implemented for root systems"); + function getValue(RootCallWrapper memory self) internal view returns (uint256) { + bytes memory systemCall = abi.encodeCall(ASystem.getValue, ()); + bytes memory result = SystemCall.staticcallOrRevert(self.from, self.systemId, systemCall); + return abi.decode(result, (uint256)); } - function getTwoValues(RootCallWrapper memory) internal pure returns (uint256, uint256) { - revert("Static calls not implemented for root systems"); + function getTwoValues(RootCallWrapper memory self) internal view returns (uint256, uint256) { + bytes memory systemCall = abi.encodeCall(ASystem.getTwoValues, ()); + bytes memory result = SystemCall.staticcallOrRevert(self.from, self.systemId, systemCall); + return abi.decode(result, (uint256, uint256)); } function setAddress(RootCallWrapper memory self) internal returns (address) { diff --git a/test/system-libraries/src/namespaces/b/codegen/libraries/BSystemLib.sol b/test/system-libraries/src/namespaces/b/codegen/libraries/BSystemLib.sol index d51e2177a9..c503a34f1f 100644 --- a/test/system-libraries/src/namespaces/b/codegen/libraries/BSystemLib.sol +++ b/test/system-libraries/src/namespaces/b/codegen/libraries/BSystemLib.sol @@ -31,6 +31,8 @@ struct RootCallWrapper { * @dev This library is automatically generated from the corresponding system contract. Do not edit manually. */ library BSystemLib { + error BSystemLib_CallingFromRootSystem(); + function setValueInA(BSystemType self, uint256 value) internal { return CallWrapper(self.toResourceId(), address(0)).setValueInA(value); } @@ -40,6 +42,9 @@ library BSystemLib { } function setValueInA(CallWrapper memory self, uint256 value) internal { + // if the contract calling this function is a root system, it should use `callAsRoot` + if (address(_world()) == address(this)) revert BSystemLib_CallingFromRootSystem(); + bytes memory systemCall = abi.encodeCall(BSystem.setValueInA, (value)); bytes memory result = self.from == address(0) ? _world().call(self.systemId, systemCall) @@ -48,6 +53,9 @@ library BSystemLib { } function getValueFromA(CallWrapper memory self) internal view returns (uint256) { + // if the contract calling this function is a root system, it should use `callAsRoot` + if (address(_world()) == address(this)) revert BSystemLib_CallingFromRootSystem(); + bytes memory systemCall = abi.encodeCall(BSystem.getValueFromA, ()); bytes memory worldCall = self.from == address(0) ? abi.encodeCall(IWorldCall.call, (self.systemId, systemCall)) @@ -64,8 +72,10 @@ library BSystemLib { result; } - function getValueFromA(RootCallWrapper memory) internal pure returns (uint256) { - revert("Static calls not implemented for root systems"); + function getValueFromA(RootCallWrapper memory self) internal view returns (uint256) { + bytes memory systemCall = abi.encodeCall(BSystem.getValueFromA, ()); + bytes memory result = SystemCall.staticcallOrRevert(self.from, self.systemId, systemCall); + return abi.decode(result, (uint256)); } function callFrom(BSystemType self, address from) internal pure returns (CallWrapper memory) { diff --git a/test/system-libraries/src/namespaces/root/RootSystem.sol b/test/system-libraries/src/namespaces/root/RootSystem.sol index d2c28c0d23..4030b10d59 100644 --- a/test/system-libraries/src/namespaces/root/RootSystem.sol +++ b/test/system-libraries/src/namespaces/root/RootSystem.sol @@ -8,4 +8,9 @@ contract RootSystem is System { function setValueInA(uint256 value) external { aSystem.callAsRoot().setValue(value); } + + // this function should not be present in the library (staticcalls disabled for root system) + function getValueFromA() external view returns (uint256) { + return aSystem.callAsRoot().getValue(); + } } diff --git a/test/system-libraries/src/namespaces/root/codegen/libraries/RootSystemLib.sol b/test/system-libraries/src/namespaces/root/codegen/libraries/RootSystemLib.sol index f0be5f46a8..37115e5b02 100644 --- a/test/system-libraries/src/namespaces/root/codegen/libraries/RootSystemLib.sol +++ b/test/system-libraries/src/namespaces/root/codegen/libraries/RootSystemLib.sol @@ -33,11 +33,16 @@ struct RootCallWrapper { * @dev This library is automatically generated from the corresponding system contract. Do not edit manually. */ library RootSystemLib { + error RootSystemLib_CallingFromRootSystem(); + function setValueInA(RootSystemType self, uint256 value) internal { return CallWrapper(self.toResourceId(), address(0)).setValueInA(value); } function setValueInA(CallWrapper memory self, uint256 value) internal { + // if the contract calling this function is a root system, it should use `callAsRoot` + if (address(_world()) == address(this)) revert RootSystemLib_CallingFromRootSystem(); + bytes memory systemCall = abi.encodeCall(RootSystem.setValueInA, (value)); bytes memory result = self.from == address(0) ? _world().call(self.systemId, systemCall) diff --git a/test/system-libraries/test/Libraries.t.sol b/test/system-libraries/test/Libraries.t.sol index 7e8b525e71..d89263124e 100644 --- a/test/system-libraries/test/Libraries.t.sol +++ b/test/system-libraries/test/Libraries.t.sol @@ -34,8 +34,8 @@ contract LibrariesTest is MudTest { function testCanCallSystemFromOtherSystem() public { uint256 value = 0xDEADBEEF; bSystem.setValueInA(value); - assertEq(Value.get(), value, "Value.get"); - assertEq(bSystem.getValueFromA(), value, "getValueFromA"); + assertEq(Value.get(), value); + assertEq(bSystem.getValueFromA(), value); } function testCallFrom() public { @@ -56,6 +56,7 @@ contract LibrariesTest is MudTest { function testCanCallFromRootSystemWithLibrary() public { uint256 value = 0xDEADBEEF; + // internally, rootSystem uses callAsRoot to call aSystem rootSystem.setValueInA(value); assertEq(Value.get(), value); assertEq(aSystem.getValue(), value);