Skip to content

Commit

Permalink
refactor Call to SystemCall, use SystemCall to call delegation checks
Browse files Browse the repository at this point in the history
  • Loading branch information
alvrs committed Aug 29, 2023
1 parent 10ec350 commit d0e2456
Show file tree
Hide file tree
Showing 22 changed files with 270 additions and 175 deletions.
2 changes: 1 addition & 1 deletion packages/world/mud.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export default mudConfig({
delegatee: "address",
},
schema: {
delegationControl: "address",
delegationControl: "bytes32",
},
},
/************************************************************************
Expand Down
90 changes: 0 additions & 90 deletions packages/world/src/Call.sol

This file was deleted.

14 changes: 7 additions & 7 deletions packages/world/src/Delegation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
});

Expand Down
103 changes: 103 additions & 0 deletions packages/world/src/SystemCall.sol
Original file line number Diff line number Diff line change
@@ -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;
}
}
12 changes: 11 additions & 1 deletion packages/world/src/Utils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand All @@ -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))
}
}
30 changes: 20 additions & 10 deletions packages/world/src/World.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
}

/**
Expand All @@ -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);
Expand Down Expand Up @@ -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))
}
Expand Down
22 changes: 22 additions & 0 deletions packages/world/src/WorldContext.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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;
}
}
2 changes: 1 addition & 1 deletion packages/world/src/constants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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")));
4 changes: 2 additions & 2 deletions packages/world/src/interfaces/ISystemHook.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
2 changes: 1 addition & 1 deletion packages/world/src/interfaces/IWorldRegistrationSystem.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ interface IWorldRegistrationSystem {

function registerDelegation(
address delegatee,
address delegationControl,
bytes32 delegationControl,
bytes memory initFuncSelectorAndArgs
) external;
}
Loading

0 comments on commit d0e2456

Please sign in to comment.