Skip to content

Commit

Permalink
feat(world): add registerNamespaceDelegation for namespace-bound fa…
Browse files Browse the repository at this point in the history
…llback delegation controls (#1590)
  • Loading branch information
alvrs committed Sep 24, 2023
1 parent f9f9609 commit 1f80a0b
Show file tree
Hide file tree
Showing 13 changed files with 495 additions and 41 deletions.
16 changes: 16 additions & 0 deletions .changeset/khaki-houses-whisper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
"@latticexyz/world": minor
---

It is now possible for namespace owners to register a fallback delegation control system for the namespace.
This fallback delegation control system is used to verify a delegation in `IBaseWorld.callFrom`, after the user's individual and fallback delegations have been checked.

```solidity
IBaseWorld {
function registerNamespaceDelegation(
ResourceId namespaceId,
ResourceId delegationControlId,
bytes memory initCallData
) external;
}
```
46 changes: 26 additions & 20 deletions packages/world/gas-report.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,13 @@
"file": "test/KeysInTableModule.t.sol",
"test": "testInstallComposite",
"name": "install keys in table module",
"gasUsed": 1408340
"gasUsed": 1408400
},
{
"file": "test/KeysInTableModule.t.sol",
"test": "testInstallGas",
"name": "install keys in table module",
"gasUsed": 1408340
"gasUsed": 1408400
},
{
"file": "test/KeysInTableModule.t.sol",
Expand All @@ -69,13 +69,13 @@
"file": "test/KeysInTableModule.t.sol",
"test": "testInstallSingleton",
"name": "install keys in table module",
"gasUsed": 1408340
"gasUsed": 1408400
},
{
"file": "test/KeysInTableModule.t.sol",
"test": "testSetAndDeleteRecordHookCompositeGas",
"name": "install keys in table module",
"gasUsed": 1408340
"gasUsed": 1408400
},
{
"file": "test/KeysInTableModule.t.sol",
Expand All @@ -93,7 +93,7 @@
"file": "test/KeysInTableModule.t.sol",
"test": "testSetAndDeleteRecordHookGas",
"name": "install keys in table module",
"gasUsed": 1408340
"gasUsed": 1408400
},
{
"file": "test/KeysInTableModule.t.sol",
Expand All @@ -111,7 +111,7 @@
"file": "test/KeysWithValueModule.t.sol",
"test": "testGetKeysWithValueGas",
"name": "install keys with value module",
"gasUsed": 649497
"gasUsed": 649560
},
{
"file": "test/KeysWithValueModule.t.sol",
Expand All @@ -129,7 +129,7 @@
"file": "test/KeysWithValueModule.t.sol",
"test": "testInstall",
"name": "install keys with value module",
"gasUsed": 649497
"gasUsed": 649560
},
{
"file": "test/KeysWithValueModule.t.sol",
Expand All @@ -141,7 +141,7 @@
"file": "test/KeysWithValueModule.t.sol",
"test": "testSetAndDeleteRecordHook",
"name": "install keys with value module",
"gasUsed": 649497
"gasUsed": 649560
},
{
"file": "test/KeysWithValueModule.t.sol",
Expand All @@ -159,7 +159,7 @@
"file": "test/KeysWithValueModule.t.sol",
"test": "testSetField",
"name": "install keys with value module",
"gasUsed": 649497
"gasUsed": 649560
},
{
"file": "test/KeysWithValueModule.t.sol",
Expand Down Expand Up @@ -243,7 +243,7 @@
"file": "test/StandardDelegationsModule.t.sol",
"test": "testCallFromCallboundDelegation",
"name": "register a callbound delegation",
"gasUsed": 112955
"gasUsed": 117579
},
{
"file": "test/StandardDelegationsModule.t.sol",
Expand All @@ -255,7 +255,7 @@
"file": "test/StandardDelegationsModule.t.sol",
"test": "testCallFromTimeboundDelegation",
"name": "register a timebound delegation",
"gasUsed": 107450
"gasUsed": 112073
},
{
"file": "test/StandardDelegationsModule.t.sol",
Expand All @@ -267,7 +267,7 @@
"file": "test/UniqueEntityModule.t.sol",
"test": "testInstall",
"name": "install unique entity module",
"gasUsed": 676006
"gasUsed": 676208
},
{
"file": "test/UniqueEntityModule.t.sol",
Expand All @@ -279,7 +279,7 @@
"file": "test/UniqueEntityModule.t.sol",
"test": "testInstallRoot",
"name": "installRoot unique entity module",
"gasUsed": 643280
"gasUsed": 643472
},
{
"file": "test/UniqueEntityModule.t.sol",
Expand All @@ -293,17 +293,23 @@
"name": "call a system via the World",
"gasUsed": 12409
},
{
"file": "test/World.t.sol",
"test": "testCallFromNamespaceDelegation",
"name": "call a system via a namespace fallback delegation",
"gasUsed": 26144
},
{
"file": "test/World.t.sol",
"test": "testCallFromUnlimitedDelegation",
"name": "register an unlimited delegation",
"gasUsed": 47676
"gasUsed": 47695
},
{
"file": "test/World.t.sol",
"test": "testCallFromUnlimitedDelegation",
"name": "call a system via an unlimited delegation",
"gasUsed": 12839
"gasUsed": 12845
},
{
"file": "test/World.t.sol",
Expand All @@ -321,31 +327,31 @@
"file": "test/World.t.sol",
"test": "testRegisterFunctionSelector",
"name": "Register a function selector",
"gasUsed": 83271
"gasUsed": 83287
},
{
"file": "test/World.t.sol",
"test": "testRegisterNamespace",
"name": "Register a new namespace",
"gasUsed": 121037
"gasUsed": 121072
},
{
"file": "test/World.t.sol",
"test": "testRegisterRootFunctionSelector",
"name": "Register a root function selector",
"gasUsed": 80532
"gasUsed": 80571
},
{
"file": "test/World.t.sol",
"test": "testRegisterSystem",
"name": "register a system",
"gasUsed": 162835
"gasUsed": 162954
},
{
"file": "test/World.t.sol",
"test": "testRegisterTable",
"name": "Register a new table in the namespace",
"gasUsed": 637671
"gasUsed": 637709
},
{
"file": "test/World.t.sol",
Expand Down
10 changes: 9 additions & 1 deletion packages/world/mud.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export default mudConfig({
moduleAddress: "address",
},
},
Delegations: {
UserDelegationControl: {
keySchema: {
delegator: "address",
delegatee: "address",
Expand All @@ -49,6 +49,14 @@ export default mudConfig({
delegationControlId: "ResourceId",
},
},
NamespaceDelegationControl: {
keySchema: {
namespaceId: "ResourceId",
},
valueSchema: {
delegationControlId: "ResourceId",
},
},
/************************************************************************
*
* MODULE TABLES
Expand Down
22 changes: 15 additions & 7 deletions packages/world/src/World.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ import { requireInterface } from "./requireInterface.sol";

import { NamespaceOwner } from "./tables/NamespaceOwner.sol";
import { InstalledModules } from "./tables/InstalledModules.sol";
import { Delegations } from "./tables/Delegations.sol";
import { UserDelegationControl } from "./tables/UserDelegationControl.sol";
import { NamespaceDelegationControl } from "./tables/NamespaceDelegationControl.sol";

import { IModule, MODULE_INTERFACE_ID } from "./interfaces/IModule.sol";
import { IWorldKernel } from "./interfaces/IWorldKernel.sol";
Expand Down Expand Up @@ -302,18 +303,25 @@ contract World is StoreRead, IStoreData, IWorldKernel {
return SystemCall.callWithHooksOrRevert(msg.sender, systemId, callData, msg.value);
}

// Check if there is an explicit authorization for this caller to perform actions on behalf of the delegator
ResourceId explicitDelegationId = Delegations._get({ delegator: delegator, delegatee: msg.sender });
// Check if there is an individual authorization for this caller to perform actions on behalf of the delegator
ResourceId individualDelegationId = UserDelegationControl._get({ delegator: delegator, delegatee: msg.sender });

if (Delegation.verify(explicitDelegationId, delegator, msg.sender, systemId, callData)) {
if (Delegation.verify(individualDelegationId, delegator, msg.sender, systemId, callData)) {
// forward the call as `delegator`
return SystemCall.callWithHooksOrRevert(delegator, systemId, callData, msg.value);
}

// Check if the delegator has a fallback delegation control set
ResourceId fallbackDelegationId = Delegations._get({ delegator: delegator, delegatee: address(0) });
if (Delegation.verify(fallbackDelegationId, delegator, msg.sender, systemId, callData)) {
// forward the call with `from` as `msgSender`
ResourceId userFallbackDelegationId = UserDelegationControl._get({ delegator: delegator, delegatee: address(0) });
if (Delegation.verify(userFallbackDelegationId, delegator, msg.sender, systemId, callData)) {
// forward the call as `delegator`
return SystemCall.callWithHooksOrRevert(delegator, systemId, callData, msg.value);
}

// Check if the namespace has a fallback delegation control set
ResourceId namespaceFallbackDelegationId = NamespaceDelegationControl._get(systemId.getNamespaceId());
if (Delegation.verify(namespaceFallbackDelegationId, delegator, msg.sender, systemId, callData)) {
// forward the call as `delegator`
return SystemCall.callWithHooksOrRevert(delegator, systemId, callData, msg.value);
}

Expand Down
3 changes: 2 additions & 1 deletion packages/world/src/index.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ pragma solidity >=0.8.21;
import { NamespaceOwner, NamespaceOwnerTableId } from "./tables/NamespaceOwner.sol";
import { ResourceAccess, ResourceAccessTableId } from "./tables/ResourceAccess.sol";
import { InstalledModules, InstalledModulesTableId } from "./tables/InstalledModules.sol";
import { Delegations, DelegationsTableId } from "./tables/Delegations.sol";
import { UserDelegationControl, UserDelegationControlTableId } from "./tables/UserDelegationControl.sol";
import { NamespaceDelegationControl, NamespaceDelegationControlTableId } from "./tables/NamespaceDelegationControl.sol";
import { Balances, BalancesTableId } from "./modules/core/tables/Balances.sol";
import { Systems, SystemsTableId } from "./modules/core/tables/Systems.sol";
import { SystemRegistry, SystemRegistryTableId } from "./modules/core/tables/SystemRegistry.sol";
Expand Down
1 change: 1 addition & 0 deletions packages/world/src/interfaces/IWorldErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ interface IWorldErrors {
error World_FunctionSelectorAlreadyExists(bytes4 functionSelector);
error World_FunctionSelectorNotFound(bytes4 functionSelector);
error World_DelegationNotFound(address delegator, address delegatee);
error World_UnlimitedDelegationNotAllowed();
error World_InsufficientBalance(uint256 balance, uint256 amount);
error World_InterfaceNotSupported(address contractAddress, bytes4 interfaceId);
error World_InvalidResourceType(bytes2 expected, ResourceId resourceId, string resourceIdString);
Expand Down
6 changes: 6 additions & 0 deletions packages/world/src/interfaces/IWorldRegistrationSystem.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,10 @@ interface IWorldRegistrationSystem {
) external returns (bytes4 worldFunctionSelector);

function registerDelegation(address delegatee, ResourceId delegationControlId, bytes memory initCallData) external;

function registerNamespaceDelegation(
ResourceId namespaceId,
ResourceId delegationControlId,
bytes memory initCallData
) external;
}
11 changes: 7 additions & 4 deletions packages/world/src/modules/core/CoreModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import { RESOURCE_SYSTEM } from "../../worldResourceTypes.sol";
import { NamespaceOwner } from "../../tables/NamespaceOwner.sol";
import { ResourceAccess } from "../../tables/ResourceAccess.sol";
import { InstalledModules } from "../../tables/InstalledModules.sol";
import { Delegations } from "../../tables/Delegations.sol";
import { UserDelegationControl } from "../../tables/UserDelegationControl.sol";
import { NamespaceDelegationControl } from "../../tables/NamespaceDelegationControl.sol";

import { CoreSystem } from "./CoreSystem.sol";
import { CORE_MODULE_NAME, CORE_SYSTEM_ID } from "./constants.sol";
Expand Down Expand Up @@ -67,7 +68,8 @@ contract CoreModule is Module {
NamespaceOwner.register();
Balances.register();
InstalledModules.register();
Delegations.register();
UserDelegationControl.register();
NamespaceDelegationControl.register();
ResourceAccess.register();
Systems.register();
FunctionSelectors.register();
Expand Down Expand Up @@ -96,7 +98,7 @@ contract CoreModule is Module {
* Register function selectors for all CoreSystem functions in the World
*/
function _registerFunctionSelectors() internal {
string[17] memory functionSignatures = [
string[18] memory functionSignatures = [
// --- AccessManagementSystem ---
"grantAccess(bytes32,address)",
"revokeAccess(bytes32,address)",
Expand All @@ -119,7 +121,8 @@ contract CoreModule is Module {
"registerSystem(bytes32,address,bool)",
"registerFunctionSelector(bytes32,string)",
"registerRootFunctionSelector(bytes32,string,bytes4)",
"registerDelegation(address,bytes32,bytes)"
"registerDelegation(address,bytes32,bytes)",
"registerNamespaceDelegation(bytes32,bytes32,bytes)"
];

for (uint256 i = 0; i < functionSignatures.length; i++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import { Delegation } from "../../../Delegation.sol";
import { requireInterface } from "../../../requireInterface.sol";
import { NamespaceOwner } from "../../../tables/NamespaceOwner.sol";
import { ResourceAccess } from "../../../tables/ResourceAccess.sol";
import { Delegations } from "../../../tables/Delegations.sol";
import { UserDelegationControl } from "../../../tables/UserDelegationControl.sol";
import { NamespaceDelegationControl } from "../../../tables/NamespaceDelegationControl.sol";
import { ISystemHook, SYSTEM_HOOK_INTERFACE_ID } from "../../../interfaces/ISystemHook.sol";
import { IWorldErrors } from "../../../interfaces/IWorldErrors.sol";
import { IDelegationControl, DELEGATION_CONTROL_INTERFACE_ID } from "../../../interfaces/IDelegationControl.sol";
Expand Down Expand Up @@ -214,7 +215,11 @@ contract WorldRegistrationSystem is System, IWorldErrors {
*/
function registerDelegation(address delegatee, ResourceId delegationControlId, bytes memory initCallData) public {
// Store the delegation control contract address
Delegations._set({ delegator: _msgSender(), delegatee: delegatee, delegationControlId: delegationControlId });
UserDelegationControl._set({
delegator: _msgSender(),
delegatee: delegatee,
delegationControlId: delegationControlId
});

// If the delegation is limited...
if (Delegation.isLimited(delegationControlId) && initCallData.length > 0) {
Expand All @@ -223,7 +228,48 @@ contract WorldRegistrationSystem is System, IWorldErrors {
requireInterface(delegationControl, DELEGATION_CONTROL_INTERFACE_ID);

// Call the delegation control contract's init function
SystemCall.call({ caller: _msgSender(), systemId: delegationControlId, callData: initCallData, value: 0 });
SystemCall.callWithHooksOrRevert({
caller: _msgSender(),
systemId: delegationControlId,
callData: initCallData,
value: 0
});
}
}

function registerNamespaceDelegation(
ResourceId namespaceId,
ResourceId delegationControlId,
bytes memory initCallData
) public {
// Require the namespaceId to be a valid namespace ID
if (namespaceId.getType() != RESOURCE_NAMESPACE) {
revert World_InvalidResourceType(RESOURCE_NAMESPACE, namespaceId, namespaceId.toString());
}

// Require the delegation to not be unlimited
if (!Delegation.isLimited(delegationControlId)) {
revert World_UnlimitedDelegationNotAllowed();
}

// Require the caller to own the namespace
AccessControl.requireOwner(namespaceId, _msgSender());

// Require the delegationControl contract to implement the IDelegationControl interface
(address delegationControl, ) = Systems._get(delegationControlId);
requireInterface(delegationControl, DELEGATION_CONTROL_INTERFACE_ID);

// Register the delegation control
NamespaceDelegationControl._set(namespaceId, delegationControlId);

// Call the delegation control contract's init function
if (initCallData.length > 0) {
SystemCall.callWithHooksOrRevert({
caller: _msgSender(),
systemId: delegationControlId,
callData: initCallData,
value: 0
});
}
}
}
Loading

0 comments on commit 1f80a0b

Please sign in to comment.