diff --git a/.changeset/yellow-trains-do.md b/.changeset/yellow-trains-do.md new file mode 100644 index 0000000000..a642ad1e2d --- /dev/null +++ b/.changeset/yellow-trains-do.md @@ -0,0 +1,5 @@ +--- +"@latticexyz/world": patch +--- + +Add a SystemCall.staticcall function that performs a staticcall without executing hooks. diff --git a/docs/pages/world/reference/internal/systemcall.mdx b/docs/pages/world/reference/internal/systemcall.mdx index 2dafd8a466..f442c3ad21 100644 --- a/docs/pages/world/reference/internal/systemcall.mdx +++ b/docs/pages/world/reference/internal/systemcall.mdx @@ -42,6 +42,61 @@ function call( | `success` | `bool` | A flag indicating whether the system call was successful. | | `data` | `bytes` | The return data from the system call. | +#### staticcall + +Makes a staticcall to a system identified by its Resource ID while ensuring necessary access controls. + +_This function does not revert if the system staticcall fails. Instead, it returns a success flag._ + +```solidity +function staticcall( + address caller, + ResourceId systemId, + bytes memory callData +) internal view returns (bool success, bytes memory data); +``` + +**Parameters** + +| Name | Type | Description | +| ---------- | ------------ | -------------------------------------------------- | +| `caller` | `address` | The address initiating the system staticcall. | +| `systemId` | `ResourceId` | The unique Resource ID of the system being called. | +| `callData` | `bytes` | The calldata to be executed in the system. | + +**Returns** + +| Name | Type | Description | +| --------- | ------- | --------------------------------------------------------------- | +| `success` | `bool` | A flag indicating whether the system staticcall was successful. | +| `data` | `bytes` | The return data from the system staticcall. | + +#### staticcallOrRevert + +Makes a staticcall to a system identified by its Resource ID, ensures access controls, and reverts on failure. + +```solidity +function staticcallOrRevert( + address caller, + ResourceId systemId, + bytes memory callData +) internal view returns (bytes memory data); +``` + +**Parameters** + +| Name | Type | Description | +| ---------- | ------------ | -------------------------------------------------- | +| `caller` | `address` | The address initiating the system staticcall. | +| `systemId` | `ResourceId` | The unique Resource ID of the system being called. | +| `callData` | `bytes` | The calldata to be executed in the system. | + +**Returns** + +| Name | Type | Description | +| ------ | ------- | ------------------------------------------- | +| `data` | `bytes` | The return data from the system staticcall. | + #### callWithHooks Calls a system identified by its Resource ID, ensuring access controls, and triggers associated system hooks. diff --git a/docs/pages/world/reference/world-context.mdx b/docs/pages/world/reference/world-context.mdx index 512d8a7253..aca58289d0 100644 --- a/docs/pages/world/reference/world-context.mdx +++ b/docs/pages/world/reference/world-context.mdx @@ -198,6 +198,33 @@ function callWithContext( | `success` | `bool` | A boolean indicating whether the call was successful or not. | | `data` | `bytes` | The abi encoded return data from the call. | +#### staticcallWithContext + +Makes a staticcall to the target contract with context values appended to the calldata. + +```solidity +function staticcallWithContext( + address msgSender, + address target, + bytes memory callData +) internal view returns (bool success, bytes memory data); +``` + +**Parameters** + +| Name | Type | Description | +| ----------- | --------- | -------------------------------------- | +| `msgSender` | `address` | The address of the transaction sender. | +| `target` | `address` | The address of the contract to call. | +| `callData` | `bytes` | The calldata for the staticcall. | + +**Returns** + +| Name | Type | Description | +| --------- | ------- | ------------------------------------------------------------------ | +| `success` | `bool` | A boolean indicating whether the staticcall was successful or not. | +| `data` | `bytes` | The abi encoded return data from the staticcall. | + #### delegatecallWithContext Makes a delegatecall to the target contract with context values appended to the calldata. diff --git a/packages/store/ts/flattenStoreLogs.test.ts b/packages/store/ts/flattenStoreLogs.test.ts index 837b9deb9e..ad2ac5ddb1 100644 --- a/packages/store/ts/flattenStoreLogs.test.ts +++ b/packages/store/ts/flattenStoreLogs.test.ts @@ -135,8 +135,8 @@ describe("flattenStoreLogs", async () => { "Store_SetRecord store__ResourceIds (0x746200000000000000000000000000005465727261696e000000000000000000)", "Store_SetRecord store__ResourceIds (0x737900000000000000000000000000004d6f766553797374656d000000000000)", "Store_SetRecord world__Systems (0x737900000000000000000000000000004d6f766553797374656d000000000000)", - "Store_SetRecord world__SystemRegistry (0x000000000000000000000000c3082aa42cf81d515c45c3c2c59775f71dfe987c)", - "Store_SetRecord world__ResourceAccess (0x6e73000000000000000000000000000000000000000000000000000000000000,0x000000000000000000000000c3082aa42cf81d515c45c3c2c59775f71dfe987c)", + "Store_SetRecord world__SystemRegistry (0x00000000000000000000000008f2b45d8787be8a81869d9968f25323861352b0)", + "Store_SetRecord world__ResourceAccess (0x6e73000000000000000000000000000000000000000000000000000000000000,0x00000000000000000000000008f2b45d8787be8a81869d9968f25323861352b0)", "Store_SetRecord world__FunctionSelector (0xb591186e00000000000000000000000000000000000000000000000000000000)", "Store_SetRecord world__FunctionSignatur (0xb591186e00000000000000000000000000000000000000000000000000000000)", "Store_SetRecord store__Tables (0x7462000000000000000000000000000043616c6c576974685369676e61747572)", diff --git a/packages/store/ts/getStoreLogs.test.ts b/packages/store/ts/getStoreLogs.test.ts index 51c45476f4..c818ec33a3 100644 --- a/packages/store/ts/getStoreLogs.test.ts +++ b/packages/store/ts/getStoreLogs.test.ts @@ -158,8 +158,8 @@ describe("getStoreLogs", async () => { "Store_SpliceStaticData store__ResourceIds (0x746200000000000000000000000000005465727261696e000000000000000000)", "Store_SpliceStaticData store__ResourceIds (0x737900000000000000000000000000004d6f766553797374656d000000000000)", "Store_SetRecord world__Systems (0x737900000000000000000000000000004d6f766553797374656d000000000000)", - "Store_SpliceStaticData world__SystemRegistry (0x000000000000000000000000c3082aa42cf81d515c45c3c2c59775f71dfe987c)", - "Store_SpliceStaticData world__ResourceAccess (0x6e73000000000000000000000000000000000000000000000000000000000000,0x000000000000000000000000c3082aa42cf81d515c45c3c2c59775f71dfe987c)", + "Store_SpliceStaticData world__SystemRegistry (0x00000000000000000000000008f2b45d8787be8a81869d9968f25323861352b0)", + "Store_SpliceStaticData world__ResourceAccess (0x6e73000000000000000000000000000000000000000000000000000000000000,0x00000000000000000000000008f2b45d8787be8a81869d9968f25323861352b0)", "Store_SetRecord world__FunctionSelector (0xb591186e00000000000000000000000000000000000000000000000000000000)", "Store_SetRecord world__FunctionSignatur (0xb591186e00000000000000000000000000000000000000000000000000000000)", "Store_SetRecord world__FunctionSignatur (0xb591186e00000000000000000000000000000000000000000000000000000000)", diff --git a/packages/world/src/SystemCall.sol b/packages/world/src/SystemCall.sol index ac984fe3da..02bbc9575b 100644 --- a/packages/world/src/SystemCall.sol +++ b/packages/world/src/SystemCall.sol @@ -74,6 +74,58 @@ library SystemCall { }); } + /** + * @notice Makes a staticcall to a system identified by its Resource ID while ensuring necessary access controls. + * @dev This function does not revert if the system staticcall fails. Instead, it returns a success flag. + * @param caller The address initiating the system staticcall. + * @param systemId The unique Resource ID of the system being called. + * @param callData The calldata to be executed in the system. + * @return success A flag indicating whether the system staticcall was successful. + * @return data The return data from the system staticcall. + */ + function staticcall( + address caller, + ResourceId systemId, + bytes memory callData + ) internal view returns (bool success, bytes memory data) { + // Load the system data + (address systemAddress, bool publicAccess) = Systems._get(systemId); + + // Check if the system exists + if (systemAddress == address(0)) revert IWorldErrors.World_ResourceNotFound(systemId, systemId.toString()); + + // Staticcalls are not supported for root systems yet, as it would require a runtime check + // that we are in the context of a staticcall before performing the delegatecall + if (systemId.getNamespace() == ROOT_NAMESPACE) revert IWorldErrors.World_InvalidNamespace(ROOT_NAMESPACE); + + // Allow access if the system is public or the caller has access to the namespace or name + if (!publicAccess) AccessControl._requireAccess(systemId, caller); + + // Call the system and forward any return data + (success, data) = WorldContextProviderLib.staticcallWithContext({ + msgSender: caller, + target: systemAddress, + callData: callData + }); + } + + /** + * @notice Makes a staticcall to a system identified by its Resource ID, ensures access controls, and reverts on failure. + * @param caller The address initiating the system staticcall. + * @param systemId The unique Resource ID of the system being called. + * @param callData The calldata to be executed in the system. + * @return data The return data from the system staticcall. + */ + function staticcallOrRevert( + address caller, + ResourceId systemId, + bytes memory callData + ) internal view returns (bytes memory data) { + (bool success, bytes memory returnData) = staticcall({ caller: caller, systemId: systemId, callData: callData }); + if (!success) revertWithBytes(returnData); + return returnData; + } + /** * @notice Calls a system identified by its Resource ID, ensuring access controls, and triggers associated system hooks. * @dev This function does not revert if the system call fails. Instead, it returns a success flag. diff --git a/packages/world/src/WorldContext.sol b/packages/world/src/WorldContext.sol index be7be9f7ae..92637732c1 100644 --- a/packages/world/src/WorldContext.sol +++ b/packages/world/src/WorldContext.sol @@ -139,6 +139,22 @@ library WorldContextProviderLib { ); } + /** + * @notice Makes a staticcall to the target contract with context values appended to the calldata. + * @param msgSender The address of the transaction sender. + * @param target The address of the contract to call. + * @param callData The calldata for the staticcall. + * @return success A boolean indicating whether the staticcall was successful or not. + * @return data The abi encoded return data from the staticcall. + */ + function staticcallWithContext( + address msgSender, + address target, + bytes memory callData + ) internal view returns (bool success, bytes memory data) { + (success, data) = target.staticcall(appendContext({ callData: callData, msgSender: msgSender, msgValue: 0 })); + } + /** * @notice Makes a delegatecall to the target contract with context values appended to the calldata. * @param msgSender The address of the transaction sender.