diff --git a/packages/world/mud.config.ts b/packages/world/mud.config.ts index 79b98bf596..9003aa683e 100644 --- a/packages/world/mud.config.ts +++ b/packages/world/mud.config.ts @@ -48,7 +48,7 @@ export default mudConfig({ delegatee: "address", }, schema: { - delegationControl: "address", + delegationControl: "bytes32", }, }, /************************************************************************ diff --git a/packages/world/src/Call.sol b/packages/world/src/Call.sol deleted file mode 100644 index 5051102bf4..0000000000 --- a/packages/world/src/Call.sol +++ /dev/null @@ -1,90 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.0; - -import { ResourceSelector } from "./ResourceSelector.sol"; -import { WorldContextProvider } from "./WorldContext.sol"; -import { AccessControl } from "./AccessControl.sol"; -import { ResourceSelector } from "./ResourceSelector.sol"; -import { ROOT_NAMESPACE } from "./constants.sol"; - -import { IWorldErrors } from "./interfaces/IWorldErrors.sol"; -import { ISystemHook } from "./interfaces/ISystemHook.sol"; - -import { FunctionSelectors } from "./modules/core/tables/FunctionSelectors.sol"; -import { Systems } from "./modules/core/tables/Systems.sol"; -import { SystemHooks } from "./modules/core/tables/SystemHooks.sol"; - -library Call { - using ResourceSelector for bytes32; - - /** - * Call a contract with delegatecall/call and append the given msgSender to the calldata. - * If the call is successfall, return the returndata as bytes memory. - * Else, forward the error (with a revert) - */ - function withSender( - address msgSender, - address target, - bytes memory funcSelectorAndArgs, - bool delegate, - uint256 value - ) internal returns (bytes memory) { - // Call the target using `delegatecall` or `call` - (bool success, bytes memory data) = delegate - ? WorldContextProvider.delegatecallWithContext(target, funcSelectorAndArgs, msgSender) // root system - : WorldContextProvider.callWithContext(target, funcSelectorAndArgs, msgSender, value); // non-root system - - // Forward returned data if the call succeeded - if (success) return data; - - // Forward error if the call failed - assembly { - // data+32 is a pointer to the error message, mload(data) is the length of the error message - revert(add(data, 0x20), mload(data)) - } - } - - /** - * Call the system at the given namespace and name and pass the given value. - * If the system is not public, the caller must have access to the namespace or name. - */ - function viaResourceSelector( - address caller, - bytes32 resourceSelector, - bytes memory funcSelectorAndArgs, - uint256 value - ) internal returns (bytes memory data) { - // Load the system data - (address systemAddress, bool publicAccess) = Systems.get(resourceSelector); - - // Check if the system exists - if (systemAddress == address(0)) revert IWorldErrors.ResourceNotFound(resourceSelector.toString()); - - // Allow access if the system is public or the caller has access to the namespace or name - if (!publicAccess) AccessControl.requireAccess(resourceSelector, caller); - - // Get system hooks - address[] memory hooks = SystemHooks.get(resourceSelector); - - // Call onBeforeCallSystem hooks (before calling the system) - for (uint256 i; i < hooks.length; i++) { - ISystemHook hook = ISystemHook(hooks[i]); - hook.onBeforeCallSystem(caller, systemAddress, funcSelectorAndArgs); - } - - // Call the system and forward any return data - data = Call.withSender({ - msgSender: caller, - target: systemAddress, - funcSelectorAndArgs: funcSelectorAndArgs, - delegate: resourceSelector.getNamespace() == ROOT_NAMESPACE, // Use delegatecall for root systems (= registered in the root namespace) - value: value - }); - - // Call onAfterCallSystem hooks (after calling the system) - for (uint256 i; i < hooks.length; i++) { - ISystemHook hook = ISystemHook(hooks[i]); - hook.onAfterCallSystem(caller, systemAddress, funcSelectorAndArgs); - } - } -} diff --git a/packages/world/src/Delegation.sol b/packages/world/src/Delegation.sol index 3b149b1898..c4140ea0d7 100644 --- a/packages/world/src/Delegation.sol +++ b/packages/world/src/Delegation.sol @@ -3,15 +3,15 @@ pragma solidity >=0.8.0; import { UNLIMITED_DELEGATION } from "./constants.sol"; import { IDelegationControl } from "./interfaces/IDelegationControl.sol"; -import { WorldContextProvider } from "./WorldContext.sol"; +import { SystemCall } from "./SystemCall.sol"; -type Delegation is address; +type Delegation is bytes32; using DelegationInstance for Delegation global; library DelegationInstance { function exists(Delegation self) internal pure returns (bool) { - return Delegation.unwrap(self) != address(0); + return Delegation.unwrap(self) != bytes32(""); } function isUnlimited(Delegation self) internal pure returns (bool) { @@ -40,16 +40,16 @@ library DelegationInstance { // Early return if there is no valid delegation if (!exists(self)) return false; - // Call the delegation control contract to check if the delegator has granted access to the delegatee1 - (bool success, bytes memory data) = WorldContextProvider.callWithContext({ - target: Delegation.unwrap(self), + // Call the delegation control contract to check if the delegator has granted access to the delegatee + (bool success, bytes memory data) = SystemCall.call({ + caller: delegatee, + resourceSelector: Delegation.unwrap(self), funcSelectorAndArgs: abi.encodeWithSelector( IDelegationControl.verify.selector, delegator, systemId, funcSelectorAndArgs ), - msgSender: delegatee, value: 0 }); diff --git a/packages/world/src/SystemCall.sol b/packages/world/src/SystemCall.sol new file mode 100644 index 0000000000..4f50e8813f --- /dev/null +++ b/packages/world/src/SystemCall.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +import { ResourceSelector } from "./ResourceSelector.sol"; +import { WorldContextProvider } from "./WorldContext.sol"; +import { AccessControl } from "./AccessControl.sol"; +import { ResourceSelector } from "./ResourceSelector.sol"; +import { ROOT_NAMESPACE } from "./constants.sol"; +import { WorldContextProvider } from "./WorldContext.sol"; +import { revertWithBytes } from "./utils.sol"; + +import { IWorldErrors } from "./interfaces/IWorldErrors.sol"; +import { ISystemHook } from "./interfaces/ISystemHook.sol"; + +import { FunctionSelectors } from "./modules/core/tables/FunctionSelectors.sol"; +import { Systems } from "./modules/core/tables/Systems.sol"; +import { SystemHooks } from "./modules/core/tables/SystemHooks.sol"; + +library SystemCall { + using ResourceSelector for bytes32; + + /** + * Call the system at the given namespace. + * If the system is not public, the caller must have access to the namespace or name. + * Returns the success status and any return data. + */ + function call( + address caller, + bytes32 resourceSelector, + bytes memory funcSelectorAndArgs, + uint256 value + ) internal returns (bool success, bytes memory data) { + // Load the system data + (address systemAddress, bool publicAccess) = Systems.get(resourceSelector); + + // Check if the system exists + if (systemAddress == address(0)) revert IWorldErrors.ResourceNotFound(resourceSelector.toString()); + + // Allow access if the system is public or the caller has access to the namespace or name + if (!publicAccess) AccessControl.requireAccess(resourceSelector, caller); + + // Call the system and forward any return data + (success, data) = resourceSelector.getNamespace() == ROOT_NAMESPACE // Use delegatecall for root systems (= registered in the root namespace) + ? WorldContextProvider.delegatecallWithContext({ + msgSender: caller, + target: systemAddress, + funcSelectorAndArgs: funcSelectorAndArgs + }) + : WorldContextProvider.callWithContext({ + msgSender: caller, + target: systemAddress, + funcSelectorAndArgs: funcSelectorAndArgs, + value: value + }); + } + + /** + * Call the system at the given namespace and call any hooks registered for it. + * If the system is not public, the caller must have access to the namespace or name. + * Returns the success status and any return data. + */ + function callWithHooks( + address caller, + bytes32 resourceSelector, + bytes memory funcSelectorAndArgs, + uint256 value + ) internal returns (bool success, bytes memory data) { + // Get system hooks + address[] memory hooks = SystemHooks.get(resourceSelector); + + // Call onBeforeCallSystem hooks (before calling the system) + for (uint256 i; i < hooks.length; i++) { + ISystemHook hook = ISystemHook(hooks[i]); + hook.onBeforeCallSystem(caller, resourceSelector, funcSelectorAndArgs); + } + + // Call the system and forward any return data + (success, data) = call(caller, resourceSelector, funcSelectorAndArgs, value); + + // Call onAfterCallSystem hooks (after calling the system) + for (uint256 i; i < hooks.length; i++) { + ISystemHook hook = ISystemHook(hooks[i]); + hook.onAfterCallSystem(caller, resourceSelector, funcSelectorAndArgs); + } + } + + /** + * Call the system at the given namespace and call any hooks registered for it. + * If the system is not public, the caller must have access to the namespace or name. + * Reverts with the error if the call was not successful. + * Else returns any return data. + */ + function callWithHooksOrRevert( + address caller, + bytes32 resourceSelector, + bytes memory funcSelectorAndArgs, + uint256 value + ) internal returns (bytes memory data) { + (bool success, bytes memory returnData) = callWithHooks(caller, resourceSelector, funcSelectorAndArgs, value); + if (!success) revertWithBytes(returnData); + return returnData; + } +} diff --git a/packages/world/src/Utils.sol b/packages/world/src/Utils.sol index dd5a4138dc..b8f181a435 100644 --- a/packages/world/src/Utils.sol +++ b/packages/world/src/Utils.sol @@ -5,7 +5,7 @@ import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; import { ResourceSelector } from "./ResourceSelector.sol"; import { SystemRegistry } from "./Tables.sol"; -library Utils { +library SystemUtils { /** * Get the namespace of this system. * Must be used within the context of a system (either directly, or within libraries called by a system). @@ -22,3 +22,13 @@ library Utils { } } } + +/** + * Utility function to revert with raw bytes (eg. coming from a low level call or from a previously encoded error) + */ +function revertWithBytes(bytes memory reason) pure { + assembly { + // reason+32 is a pointer to the error message, mload(reason) is the length of the error message + revert(add(reason, 0x20), mload(reason)) + } +} diff --git a/packages/world/src/World.sol b/packages/world/src/World.sol index 5d46ac8538..65c62176b5 100644 --- a/packages/world/src/World.sol +++ b/packages/world/src/World.sol @@ -11,8 +11,10 @@ import { System } from "./System.sol"; import { ResourceSelector } from "./ResourceSelector.sol"; import { ROOT_NAMESPACE, ROOT_NAME } from "./constants.sol"; import { AccessControl } from "./AccessControl.sol"; -import { Call } from "./Call.sol"; +import { SystemCall } from "./SystemCall.sol"; +import { WorldContextProvider } from "./WorldContext.sol"; import { Delegation } from "./Delegation.sol"; +import { revertWithBytes } from "./utils.sol"; import { NamespaceOwner } from "./tables/NamespaceOwner.sol"; import { InstalledModules } from "./tables/InstalledModules.sol"; @@ -50,12 +52,10 @@ contract World is StoreRead, IStoreData, IWorldKernel { function installRootModule(IModule module, bytes memory args) public { AccessControl.requireOwnerOrSelf(ROOT_NAMESPACE, msg.sender); - Call.withSender({ + WorldContextProvider.delegatecallWithContextOrRevert({ msgSender: msg.sender, target: address(module), - funcSelectorAndArgs: abi.encodeWithSelector(IModule.install.selector, args), - delegate: true, // The module is delegate called so it can edit any table - value: 0 + funcSelectorAndArgs: abi.encodeWithSelector(IModule.install.selector, args) }); // Register the module in the InstalledModules table @@ -179,7 +179,7 @@ contract World is StoreRead, IStoreData, IWorldKernel { bytes32 resourceSelector, bytes memory funcSelectorAndArgs ) external payable virtual returns (bytes memory) { - return Call.viaResourceSelector(msg.sender, resourceSelector, funcSelectorAndArgs, msg.value); + return SystemCall.callWithHooksOrRevert(msg.sender, resourceSelector, funcSelectorAndArgs, msg.value); } /** @@ -196,14 +196,14 @@ contract World is StoreRead, IStoreData, IWorldKernel { if (explicitDelegation.verify(delegator, msg.sender, resourceSelector, funcSelectorAndArgs)) { // forward the call as `delegator` - return Call.viaResourceSelector(delegator, resourceSelector, funcSelectorAndArgs, msg.value); + return SystemCall.callWithHooksOrRevert(delegator, resourceSelector, funcSelectorAndArgs, msg.value); } // Check if the delegator has a fallback delegation control set Delegation fallbackDelegation = Delegation.wrap(Delegations.get({ delegator: delegator, delegatee: address(0) })); if (fallbackDelegation.verify(delegator, msg.sender, resourceSelector, funcSelectorAndArgs)) { // forward the call with `from` as `msgSender` - return Call.viaResourceSelector(delegator, resourceSelector, funcSelectorAndArgs, msg.value); + return SystemCall.callWithHooksOrRevert(delegator, resourceSelector, funcSelectorAndArgs, msg.value); } revert DelegationNotFound(delegator, msg.sender); @@ -231,8 +231,18 @@ contract World is StoreRead, IStoreData, IWorldKernel { // Replace function selector in the calldata with the system function selector bytes memory callData = Bytes.setBytes4(msg.data, 0, systemFunctionSelector); - // Call the function and forward the calldata and returndata - bytes memory returnData = Call.viaResourceSelector(msg.sender, resourceSelector, callData, msg.value); + // Call the function and forward the call data + (bool success, bytes memory returnData) = SystemCall.callWithHooks( + msg.sender, + resourceSelector, + callData, + msg.value + ); + + // If the call was not successful, revert with the return data + if (!success) revertWithBytes(returnData); + + // If the call was successful, return the return data assembly { return(add(returnData, 0x20), mload(returnData)) } diff --git a/packages/world/src/WorldContext.sol b/packages/world/src/WorldContext.sol index 8437a766fb..2e79efdc8f 100644 --- a/packages/world/src/WorldContext.sol +++ b/packages/world/src/WorldContext.sol @@ -2,6 +2,7 @@ pragma solidity >=0.8.0; import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; +import { revertWithBytes } from "./utils.sol"; // Similar to https://eips.ethereum.org/EIPS/eip-2771, but any contract can be the trusted forwarder. // This should only be used for contracts without own storage, like Systems. @@ -44,4 +45,25 @@ library WorldContextProvider { ) internal returns (bool success, bytes memory data) { (success, data) = target.delegatecall(appendContext(funcSelectorAndArgs, msgSender)); } + + function callWithContextOrRevert( + address target, + bytes memory funcSelectorAndArgs, + address msgSender, + uint256 value + ) internal returns (bytes memory data) { + (bool success, bytes memory _data) = callWithContext(target, funcSelectorAndArgs, msgSender, value); + if (!success) revertWithBytes(_data); + return _data; + } + + function delegatecallWithContextOrRevert( + address target, + bytes memory funcSelectorAndArgs, + address msgSender + ) internal returns (bytes memory data) { + (bool success, bytes memory _data) = delegatecallWithContext(target, funcSelectorAndArgs, msgSender); + if (!success) revertWithBytes(_data); + return _data; + } } diff --git a/packages/world/src/constants.sol b/packages/world/src/constants.sol index a2cc7177d1..a4a2538803 100644 --- a/packages/world/src/constants.sol +++ b/packages/world/src/constants.sol @@ -3,4 +3,4 @@ pragma solidity >=0.8.0; bytes16 constant ROOT_NAMESPACE = 0; bytes16 constant ROOT_NAME = 0; -address constant UNLIMITED_DELEGATION = address(1); +bytes32 constant UNLIMITED_DELEGATION = bytes32(abi.encodePacked(ROOT_NAMESPACE, bytes16("unlimited.d"))); diff --git a/packages/world/src/interfaces/ISystemHook.sol b/packages/world/src/interfaces/ISystemHook.sol index 6d7b1b8147..3810f571a6 100644 --- a/packages/world/src/interfaces/ISystemHook.sol +++ b/packages/world/src/interfaces/ISystemHook.sol @@ -2,7 +2,7 @@ pragma solidity >=0.8.0; interface ISystemHook { - function onBeforeCallSystem(address msgSender, address systemAddress, bytes memory funcSelectorAndArgs) external; + function onBeforeCallSystem(address msgSender, bytes32 resourceSelector, bytes memory funcSelectorAndArgs) external; - function onAfterCallSystem(address msgSender, address systemAddress, bytes memory funcSelectorAndArgs) external; + function onAfterCallSystem(address msgSender, bytes32 resourceSelector, bytes memory funcSelectorAndArgs) external; } diff --git a/packages/world/src/interfaces/IWorldRegistrationSystem.sol b/packages/world/src/interfaces/IWorldRegistrationSystem.sol index 9f6ddf01cb..d4479d1ba1 100644 --- a/packages/world/src/interfaces/IWorldRegistrationSystem.sol +++ b/packages/world/src/interfaces/IWorldRegistrationSystem.sol @@ -27,7 +27,7 @@ interface IWorldRegistrationSystem { function registerDelegation( address delegatee, - address delegationControl, + bytes32 delegationControl, bytes memory initFuncSelectorAndArgs ) external; } diff --git a/packages/world/src/modules/core/CoreModule.sol b/packages/world/src/modules/core/CoreModule.sol index 7919f956d5..99d2509f04 100644 --- a/packages/world/src/modules/core/CoreModule.sol +++ b/packages/world/src/modules/core/CoreModule.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.0; -import { Call } from "../../Call.sol"; +import { WorldContextProvider } from "../../WorldContext.sol"; import { ROOT_NAMESPACE } from "../../constants.sol"; import { WorldContextConsumer } from "../../WorldContext.sol"; import { Resource } from "../../Types.sol"; @@ -74,11 +74,9 @@ contract CoreModule is IModule, WorldContextConsumer { */ function _registerCoreSystem() internal { // Use the CoreSystem's `registerSystem` implementation to register itself on the World. - Call.withSender({ + WorldContextProvider.delegatecallWithContextOrRevert({ msgSender: _msgSender(), target: coreSystem, - delegate: true, - value: 0, funcSelectorAndArgs: abi.encodeWithSelector( WorldRegistrationSystem.registerSystem.selector, ResourceSelector.from(ROOT_NAMESPACE, CORE_SYSTEM_NAME), @@ -115,11 +113,9 @@ contract CoreModule is IModule, WorldContextConsumer { for (uint256 i = 0; i < functionSelectors.length; i++) { // Use the CoreSystem's `registerRootFunctionSelector` to register the // root function selectors in the World. - Call.withSender({ + WorldContextProvider.delegatecallWithContextOrRevert({ msgSender: _msgSender(), target: coreSystem, - delegate: true, - value: 0, funcSelectorAndArgs: abi.encodeWithSelector( WorldRegistrationSystem.registerRootFunctionSelector.selector, ResourceSelector.from(ROOT_NAMESPACE, CORE_SYSTEM_NAME), diff --git a/packages/world/src/modules/core/implementations/AccessManagementSystem.sol b/packages/world/src/modules/core/implementations/AccessManagementSystem.sol index df10f158e0..3fd2c695d8 100644 --- a/packages/world/src/modules/core/implementations/AccessManagementSystem.sol +++ b/packages/world/src/modules/core/implementations/AccessManagementSystem.sol @@ -5,7 +5,6 @@ import { IModule } from "../../../interfaces/IModule.sol"; import { System } from "../../../System.sol"; import { AccessControl } from "../../../AccessControl.sol"; import { ResourceSelector } from "../../../ResourceSelector.sol"; -import { Call } from "../../../Call.sol"; import { ResourceAccess } from "../../../tables/ResourceAccess.sol"; import { InstalledModules } from "../../../tables/InstalledModules.sol"; diff --git a/packages/world/src/modules/core/implementations/EphemeralRecordSystem.sol b/packages/world/src/modules/core/implementations/EphemeralRecordSystem.sol index f49bb8996b..d52278520c 100644 --- a/packages/world/src/modules/core/implementations/EphemeralRecordSystem.sol +++ b/packages/world/src/modules/core/implementations/EphemeralRecordSystem.sol @@ -3,13 +3,9 @@ pragma solidity >=0.8.0; import { IStoreEphemeral } from "@latticexyz/store/src/IStore.sol"; import { Schema } from "@latticexyz/store/src/Schema.sol"; -import { IModule } from "../../../interfaces/IModule.sol"; import { System } from "../../../System.sol"; import { ResourceSelector } from "../../../ResourceSelector.sol"; import { AccessControl } from "../../../AccessControl.sol"; -import { Call } from "../../../Call.sol"; -import { ResourceAccess } from "../../../tables/ResourceAccess.sol"; -import { InstalledModules } from "../../../tables/InstalledModules.sol"; import { StoreCore } from "@latticexyz/store/src/StoreCore.sol"; contract EphemeralRecordSystem is IStoreEphemeral, System { diff --git a/packages/world/src/modules/core/implementations/ModuleInstallationSystem.sol b/packages/world/src/modules/core/implementations/ModuleInstallationSystem.sol index 8639ece1a5..2e55da8b57 100644 --- a/packages/world/src/modules/core/implementations/ModuleInstallationSystem.sol +++ b/packages/world/src/modules/core/implementations/ModuleInstallationSystem.sol @@ -4,7 +4,7 @@ pragma solidity >=0.8.0; import { IModule } from "../../../interfaces/IModule.sol"; import { System } from "../../../System.sol"; import { AccessControl } from "../../../AccessControl.sol"; -import { Call } from "../../../Call.sol"; +import { WorldContextProvider } from "../../../WorldContext.sol"; import { ResourceAccess } from "../../../tables/ResourceAccess.sol"; import { InstalledModules } from "../../../tables/InstalledModules.sol"; @@ -16,11 +16,10 @@ contract ModuleInstallationSystem is System { * Install the given module at the given namespace in the World. */ function installModule(IModule module, bytes memory args) public { - Call.withSender({ + WorldContextProvider.callWithContextOrRevert({ msgSender: _msgSender(), target: address(module), funcSelectorAndArgs: abi.encodeWithSelector(IModule.install.selector, args), - delegate: false, value: 0 }); diff --git a/packages/world/src/modules/core/implementations/StoreRegistrationSystem.sol b/packages/world/src/modules/core/implementations/StoreRegistrationSystem.sol index 7486549f6a..c832ace089 100644 --- a/packages/world/src/modules/core/implementations/StoreRegistrationSystem.sol +++ b/packages/world/src/modules/core/implementations/StoreRegistrationSystem.sol @@ -10,7 +10,7 @@ import { ResourceSelector } from "../../../ResourceSelector.sol"; import { Resource } from "../../../Types.sol"; import { ROOT_NAMESPACE, ROOT_NAME } from "../../../constants.sol"; import { AccessControl } from "../../../AccessControl.sol"; -import { Call } from "../../../Call.sol"; +import { WorldContextProvider } from "../../../WorldContext.sol"; import { NamespaceOwner } from "../../../tables/NamespaceOwner.sol"; import { ResourceAccess } from "../../../tables/ResourceAccess.sol"; import { ISystemHook } from "../../../interfaces/ISystemHook.sol"; @@ -51,12 +51,10 @@ contract StoreRegistrationSystem is System, IWorldErrors { // We can't call IBaseWorld(this).registerSchema directly because it would be handled like // an external call, so msg.sender would be the address of the World contract (address systemAddress, ) = Systems.get(ResourceSelector.from(ROOT_NAMESPACE, CORE_SYSTEM_NAME)); - Call.withSender({ + WorldContextProvider.delegatecallWithContextOrRevert({ msgSender: _msgSender(), target: systemAddress, - funcSelectorAndArgs: abi.encodeWithSelector(WorldRegistrationSystem.registerNamespace.selector, namespace), - delegate: true, - value: 0 + funcSelectorAndArgs: abi.encodeWithSelector(WorldRegistrationSystem.registerNamespace.selector, namespace) }); } else { // otherwise require caller to own the namespace diff --git a/packages/world/src/modules/core/implementations/WorldRegistrationSystem.sol b/packages/world/src/modules/core/implementations/WorldRegistrationSystem.sol index 9f65fd34b1..97c3c415d1 100644 --- a/packages/world/src/modules/core/implementations/WorldRegistrationSystem.sol +++ b/packages/world/src/modules/core/implementations/WorldRegistrationSystem.sol @@ -5,7 +5,7 @@ import { System } from "../../../System.sol"; import { WorldContextConsumer } from "../../../WorldContext.sol"; import { ResourceSelector } from "../../../ResourceSelector.sol"; import { Resource } from "../../../Types.sol"; -import { WorldContextProvider } from "../../../WorldContext.sol"; +import { SystemCall } from "../../../SystemCall.sol"; import { ROOT_NAMESPACE, ROOT_NAME, UNLIMITED_DELEGATION } from "../../../constants.sol"; import { AccessControl } from "../../../AccessControl.sol"; import { NamespaceOwner } from "../../../tables/NamespaceOwner.sol"; @@ -159,7 +159,7 @@ contract WorldRegistrationSystem is System, IWorldErrors { */ function registerDelegation( address delegatee, - address delegationControl, + bytes32 delegationControl, bytes memory initFuncSelectorAndArgs ) public { // Store the delegation control contract address @@ -167,10 +167,10 @@ contract WorldRegistrationSystem is System, IWorldErrors { // If the delegation is not unlimited, call the delegation control contract's init function if (delegationControl != UNLIMITED_DELEGATION && initFuncSelectorAndArgs.length > 0) { - WorldContextProvider.callWithContext({ - target: address(delegationControl), + SystemCall.call({ + caller: _msgSender(), + resourceSelector: delegationControl, funcSelectorAndArgs: initFuncSelectorAndArgs, - msgSender: _msgSender(), value: 0 }); } diff --git a/packages/world/src/modules/delegations/DelegationsModule.sol b/packages/world/src/modules/delegations/DelegationsModule.sol index b61ec1ade2..3215638889 100644 --- a/packages/world/src/modules/delegations/DelegationsModule.sol +++ b/packages/world/src/modules/delegations/DelegationsModule.sol @@ -10,7 +10,7 @@ import { ResourceSelector } from "../../ResourceSelector.sol"; import { DisposableDelegations } from "./tables/DisposableDelegations.sol"; import { DisposableDelegationControl } from "./DisposableDelegationControl.sol"; -import { MODULE_NAME, NAMESPACE, DISPOSABLE_DELEGATION, DISPOSABLE_DELEGATION_TABLE } from "./constants.sol"; +import { MODULE_NAME, NAMESPACE, DISPOSABLE_DELEGATION, DISPOSABLE_DELEGATION_ROOT, DISPOSABLE_DELEGATION_TABLE, DISPOSABLE_DELEGATION_TABLE_ROOT } from "./constants.sol"; /** * This module registers tables and delegation control systems required for standard delegations @@ -25,10 +25,13 @@ contract DelegationsModule is IModule, WorldContextConsumer { function install(bytes memory) public { IBaseWorld world = IBaseWorld(_world()); - // Register table - DisposableDelegations.register(world, DISPOSABLE_DELEGATION_TABLE); - - // Register delegation control system - world.registerSystem(DISPOSABLE_DELEGATION, disposableDelegationControl, true); + // If this module is installed as a root module, register it in the root namespace + if (address(world) == address(this)) { + DisposableDelegations.register(world, DISPOSABLE_DELEGATION_TABLE_ROOT); + world.registerSystem(DISPOSABLE_DELEGATION_ROOT, disposableDelegationControl, true); + } else { + DisposableDelegations.register(world, DISPOSABLE_DELEGATION_TABLE); + world.registerSystem(DISPOSABLE_DELEGATION, disposableDelegationControl, true); + } } } diff --git a/packages/world/src/modules/delegations/constants.sol b/packages/world/src/modules/delegations/constants.sol index 8bbb3ed4d8..94eb147800 100644 --- a/packages/world/src/modules/delegations/constants.sol +++ b/packages/world/src/modules/delegations/constants.sol @@ -1,10 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.0; import { ResourceSelector } from "../../ResourceSelector.sol"; +import { ROOT_NAMESPACE } from "../../constants.sol"; -bytes16 constant NAMESPACE = bytes16("delegations"); bytes16 constant MODULE_NAME = bytes16("delegations.m"); +bytes16 constant NAMESPACE = bytes16("delegations"); bytes32 constant DISPOSABLE_DELEGATION = bytes32(abi.encodePacked(NAMESPACE, bytes16("disposable.d"))); - -// TODO: once the config supports multiple namespaces we don't need to manually define this id but can import it from the table library +bytes32 constant DISPOSABLE_DELEGATION_ROOT = bytes32(abi.encodePacked(ROOT_NAMESPACE, bytes16("disposable.d"))); bytes32 constant DISPOSABLE_DELEGATION_TABLE = bytes32(abi.encodePacked(NAMESPACE, bytes16("disposable.t"))); +bytes32 constant DISPOSABLE_DELEGATION_TABLE_ROOT = bytes32(abi.encodePacked(ROOT_NAMESPACE, bytes16("disposable.t"))); diff --git a/packages/world/src/tables/Delegations.sol b/packages/world/src/tables/Delegations.sol index ca14df6747..1a444ff748 100644 --- a/packages/world/src/tables/Delegations.sol +++ b/packages/world/src/tables/Delegations.sol @@ -33,7 +33,7 @@ library Delegations { /** Get the table's value schema */ function getValueSchema() internal pure returns (Schema) { SchemaType[] memory _schema = new SchemaType[](1); - _schema[0] = SchemaType.ADDRESS; + _schema[0] = SchemaType.BYTES32; return SchemaLib.encode(_schema); } @@ -62,27 +62,27 @@ library Delegations { } /** Get delegationControl */ - function get(address delegator, address delegatee) internal view returns (address delegationControl) { + function get(address delegator, address delegatee) internal view returns (bytes32 delegationControl) { bytes32[] memory _keyTuple = new bytes32[](2); _keyTuple[0] = bytes32(uint256(uint160(delegator))); _keyTuple[1] = bytes32(uint256(uint160(delegatee))); bytes memory _blob = StoreSwitch.getField(_tableId, _keyTuple, 0, getValueSchema()); - return (address(Bytes.slice20(_blob, 0))); + return (Bytes.slice32(_blob, 0)); } /** Get delegationControl (using the specified store) */ - function get(IStore _store, address delegator, address delegatee) internal view returns (address delegationControl) { + function get(IStore _store, address delegator, address delegatee) internal view returns (bytes32 delegationControl) { bytes32[] memory _keyTuple = new bytes32[](2); _keyTuple[0] = bytes32(uint256(uint160(delegator))); _keyTuple[1] = bytes32(uint256(uint160(delegatee))); bytes memory _blob = _store.getField(_tableId, _keyTuple, 0, getValueSchema()); - return (address(Bytes.slice20(_blob, 0))); + return (Bytes.slice32(_blob, 0)); } /** Set delegationControl */ - function set(address delegator, address delegatee, address delegationControl) internal { + function set(address delegator, address delegatee, bytes32 delegationControl) internal { bytes32[] memory _keyTuple = new bytes32[](2); _keyTuple[0] = bytes32(uint256(uint160(delegator))); _keyTuple[1] = bytes32(uint256(uint160(delegatee))); @@ -91,7 +91,7 @@ library Delegations { } /** Set delegationControl (using the specified store) */ - function set(IStore _store, address delegator, address delegatee, address delegationControl) internal { + function set(IStore _store, address delegator, address delegatee, bytes32 delegationControl) internal { bytes32[] memory _keyTuple = new bytes32[](2); _keyTuple[0] = bytes32(uint256(uint160(delegator))); _keyTuple[1] = bytes32(uint256(uint160(delegatee))); @@ -100,7 +100,7 @@ library Delegations { } /** Tightly pack full data using this table's schema */ - function encode(address delegationControl) internal pure returns (bytes memory) { + function encode(bytes32 delegationControl) internal pure returns (bytes memory) { return abi.encodePacked(delegationControl); } diff --git a/packages/world/test/DelegationsModule.t.sol b/packages/world/test/DelegationsModule.t.sol index 04e54db302..47382af0fa 100644 --- a/packages/world/test/DelegationsModule.t.sol +++ b/packages/world/test/DelegationsModule.t.sol @@ -12,7 +12,7 @@ import { CoreModule } from "../src/modules/core/CoreModule.sol"; import { Systems } from "../src/modules/core/tables/Systems.sol"; import { DelegationsModule } from "../src/modules/delegations/DelegationsModule.sol"; import { DisposableDelegationControl } from "../src/modules/delegations/DisposableDelegationControl.sol"; -import { DISPOSABLE_DELEGATION } from "../src/modules/delegations/DelegationsModule.sol"; +import { DISPOSABLE_DELEGATION, DISPOSABLE_DELEGATION_ROOT } from "../src/modules/delegations/DelegationsModule.sol"; import { WorldTestSystem } from "./World.t.sol"; @@ -25,24 +25,62 @@ contract DelegationsModuleTest is Test, GasReporter { function setUp() public { world = IBaseWorld(address(new World())); world.installRootModule(new CoreModule(), new bytes(0)); - world.installModule(new DelegationsModule(), new bytes(0)); // Register a new system WorldTestSystem system = new WorldTestSystem(); world.registerSystem(systemResourceSelector, system, true); } + function testCallFromDisposableDelegationRoot() public { + // Install Delegations module as root module + world.installRootModule(new DelegationsModule(), new bytes(0)); + + // Register the disposable delegation for one call to the system's msgSender function + vm.prank(delegator); + startGasReport("register a disposable delegation with a root module"); + world.registerDelegation( + delegatee, + DISPOSABLE_DELEGATION_ROOT, + abi.encodeWithSelector( + DisposableDelegationControl.initDelegation.selector, + delegatee, + systemResourceSelector, + abi.encodeWithSelector(WorldTestSystem.msgSender.selector), + 1 + ) + ); + endGasReport(); + + // Call a system from the delegatee on behalf of the delegator + vm.prank(delegatee); + startGasReport("call a system via a disposable delegation with a root module"); + bytes memory returnData = world.callFrom( + delegator, + systemResourceSelector, + abi.encodeWithSelector(WorldTestSystem.msgSender.selector) + ); + endGasReport(); + address returnedAddress = abi.decode(returnData, (address)); + + // Expect the system to have received the delegator's address + assertEq(returnedAddress, delegator); + + // Expect the delegation to have been used up + vm.expectRevert(abi.encodeWithSelector(IWorldErrors.DelegationNotFound.selector, delegator, delegatee)); + vm.prank(delegatee); + world.callFrom(delegator, systemResourceSelector, abi.encodeWithSelector(WorldTestSystem.msgSender.selector)); + } + function testCallFromDisposableDelegation() public { - // Get the address for the disposable delegation - // TODO: make it easier to register a delegation if the delegation control system's address is not known - (address disposableDelegation, ) = Systems.get(world, DISPOSABLE_DELEGATION); + // Install Delegations module as root module + world.installModule(new DelegationsModule(), new bytes(0)); // Register the disposable delegation for one call to the system's msgSender function vm.prank(delegator); - startGasReport("register a disposable delegation"); + startGasReport("register a disposable delegation with a non-root module"); world.registerDelegation( delegatee, - disposableDelegation, + DISPOSABLE_DELEGATION, abi.encodeWithSelector( DisposableDelegationControl.initDelegation.selector, delegatee, @@ -55,7 +93,7 @@ contract DelegationsModuleTest is Test, GasReporter { // Call a system from the delegatee on behalf of the delegator vm.prank(delegatee); - startGasReport("call a system via a disposable delegation"); + startGasReport("call a system via a disposable delegation with a non-root module"); bytes memory returnData = world.callFrom( delegator, systemResourceSelector, diff --git a/packages/world/test/Utils.t.sol b/packages/world/test/Utils.t.sol index 55d066d32c..7affddff18 100644 --- a/packages/world/test/Utils.t.sol +++ b/packages/world/test/Utils.t.sol @@ -3,7 +3,7 @@ pragma solidity >=0.8.0; import { Test } from "forge-std/Test.sol"; -import { Utils } from "../src/Utils.sol"; +import { SystemUtils, revertWithBytes } from "../src/utils.sol"; import { System } from "../src/System.sol"; import { World } from "../src/World.sol"; import { IBaseWorld } from "../src/interfaces/IBaseWorld.sol"; @@ -13,7 +13,7 @@ import { CoreModule } from "../src/modules/core/CoreModule.sol"; contract UtilsTestSystem is System { function systemNamespace() public view returns (bytes16) { - return Utils.systemNamespace(); + return SystemUtils.systemNamespace(); } } @@ -21,6 +21,8 @@ contract UtilsTest is Test { using ResourceSelector for bytes32; IBaseWorld internal world; + error SomeError(uint256 someValue, string someString); + function setUp() public { world = IBaseWorld(address(new World())); world.installRootModule(new CoreModule(), new bytes(0)); @@ -54,4 +56,12 @@ contract UtilsTest is Test { returnedNamespace = _registerAndGetNamespace(namespace); assertEq(returnedNamespace, namespace); } + + function testRevertWithBytes() public { + vm.expectRevert(abi.encodeWithSelector(SomeError.selector, 1, "test")); + revert SomeError(1, "test"); + + vm.expectRevert(abi.encodeWithSelector(SomeError.selector, 1, "test")); + revertWithBytes(abi.encodeWithSelector(SomeError.selector, 1, "test")); + } } diff --git a/packages/world/test/World.t.sol b/packages/world/test/World.t.sol index 48b7749bed..58ba3aeef1 100644 --- a/packages/world/test/World.t.sol +++ b/packages/world/test/World.t.sol @@ -141,12 +141,12 @@ contract WorldTestTableHook is IStoreHook { contract WorldTestSystemHook is ISystemHook { event SystemHookCalled(bytes data); - function onBeforeCallSystem(address msgSender, address systemAddress, bytes memory funcSelectorAndArgs) public { - emit SystemHookCalled(abi.encode("before", msgSender, systemAddress, funcSelectorAndArgs)); + function onBeforeCallSystem(address msgSender, bytes32 resourceSelector, bytes memory funcSelectorAndArgs) public { + emit SystemHookCalled(abi.encode("before", msgSender, resourceSelector, funcSelectorAndArgs)); } - function onAfterCallSystem(address msgSender, address systemAddress, bytes memory funcSelectorAndArgs) public { - emit SystemHookCalled(abi.encode("after", msgSender, systemAddress, funcSelectorAndArgs)); + function onAfterCallSystem(address msgSender, bytes32 resourceSelector, bytes memory funcSelectorAndArgs) public { + emit SystemHookCalled(abi.encode("after", msgSender, resourceSelector, funcSelectorAndArgs)); } } @@ -664,30 +664,30 @@ contract WorldTest is Test, GasReporter { } function testRegisterSystemHook() public { - bytes32 tableId = ResourceSelector.from("namespace", "testTable"); + bytes32 systemId = ResourceSelector.from("namespace", "testTable"); // Register a new system WorldTestSystem system = new WorldTestSystem(); - world.registerSystem(tableId, system, false); + world.registerSystem(systemId, system, false); // Register a new hook ISystemHook systemHook = new WorldTestSystemHook(); - world.registerSystemHook(tableId, systemHook); + world.registerSystemHook(systemId, systemHook); bytes memory funcSelectorAndArgs = abi.encodeWithSelector(bytes4(keccak256("fallbackselector"))); // Expect the hooks to be called in correct order vm.expectEmit(true, true, true, true); - emit SystemHookCalled(abi.encode("before", address(this), address(system), funcSelectorAndArgs)); + emit SystemHookCalled(abi.encode("before", address(this), systemId, funcSelectorAndArgs)); vm.expectEmit(true, true, true, true); emit WorldTestSystemLog("fallback"); vm.expectEmit(true, true, true, true); - emit SystemHookCalled(abi.encode("after", address(this), address(system), funcSelectorAndArgs)); + emit SystemHookCalled(abi.encode("after", address(this), systemId, funcSelectorAndArgs)); // Call a system fallback function without arguments via the World - world.call(tableId, funcSelectorAndArgs); + world.call(systemId, funcSelectorAndArgs); } function testWriteRootSystem() public {