From 9638e395d6fff4c2cd2ef2c5c344f5e4c52a3aa0 Mon Sep 17 00:00:00 2001 From: Fraser Scott Date: Wed, 20 Mar 2024 12:54:02 +0000 Subject: [PATCH 01/70] wip --- .../interfaces/IWorldRegistrationSystem.sol | 2 + .../world/src/modules/init/InitModule.sol | 2 +- .../src/modules/init/functionSignatures.sol | 3 +- .../WorldRegistrationSystem.sol | 45 +++++++++++++++++++ packages/world/test/InitSystems.t.sol | 2 +- packages/world/test/World.t.sol | 25 +++++++++++ 6 files changed, 76 insertions(+), 3 deletions(-) diff --git a/packages/world/src/codegen/interfaces/IWorldRegistrationSystem.sol b/packages/world/src/codegen/interfaces/IWorldRegistrationSystem.sol index 7db5058421..9dfc35f3b2 100644 --- a/packages/world/src/codegen/interfaces/IWorldRegistrationSystem.sol +++ b/packages/world/src/codegen/interfaces/IWorldRegistrationSystem.sol @@ -34,6 +34,8 @@ interface IWorldRegistrationSystem { function registerDelegation(address delegatee, ResourceId delegationControlId, bytes memory initCallData) external; + function registerDelegationWithSignature(bytes memory signature) external; + function unregisterDelegation(address delegatee) external; function registerNamespaceDelegation( diff --git a/packages/world/src/modules/init/InitModule.sol b/packages/world/src/modules/init/InitModule.sol index f2085d0c34..0c09ea9295 100644 --- a/packages/world/src/modules/init/InitModule.sol +++ b/packages/world/src/modules/init/InitModule.sol @@ -151,7 +151,7 @@ contract InitModule is Module { _registerRootFunctionSelector(BATCH_CALL_SYSTEM_ID, functionSignaturesBatchCall[i]); } - string[14] memory functionSignaturesRegistration = getFunctionSignaturesRegistration(); + string[15] memory functionSignaturesRegistration = getFunctionSignaturesRegistration(); for (uint256 i = 0; i < functionSignaturesRegistration.length; i++) { _registerRootFunctionSelector(REGISTRATION_SYSTEM_ID, functionSignaturesRegistration[i]); } diff --git a/packages/world/src/modules/init/functionSignatures.sol b/packages/world/src/modules/init/functionSignatures.sol index e9a7fe13bd..4ca033d535 100644 --- a/packages/world/src/modules/init/functionSignatures.sol +++ b/packages/world/src/modules/init/functionSignatures.sol @@ -39,7 +39,7 @@ function getFunctionSignaturesBatchCall() pure returns (string[2] memory) { /** * @dev Function signatures for registration system */ -function getFunctionSignaturesRegistration() pure returns (string[14] memory) { +function getFunctionSignaturesRegistration() pure returns (string[15] memory) { return [ // --- ModuleInstallationSystem --- "installModule(address,bytes)", @@ -55,6 +55,7 @@ function getFunctionSignaturesRegistration() pure returns (string[14] memory) { "registerFunctionSelector(bytes32,string)", "registerRootFunctionSelector(bytes32,string,string)", "registerDelegation(address,bytes32,bytes)", + "registerDelegationWithSignature(bytes)", "unregisterDelegation(address)", "registerNamespaceDelegation(bytes32,bytes32,bytes)", "unregisterNamespaceDelegation(bytes32)" diff --git a/packages/world/src/modules/init/implementations/WorldRegistrationSystem.sol b/packages/world/src/modules/init/implementations/WorldRegistrationSystem.sol index a7d3dc1fe3..5a33165985 100644 --- a/packages/world/src/modules/init/implementations/WorldRegistrationSystem.sol +++ b/packages/world/src/modules/init/implementations/WorldRegistrationSystem.sol @@ -32,6 +32,44 @@ import { requireValidNamespace } from "../../../requireValidNamespace.sol"; import { LimitedCallContext } from "../LimitedCallContext.sol"; +function getMessageHash() pure returns (bytes32) { + return keccak256(""); +} + +function getEthSignedMessageHash(bytes32 _messageHash) pure returns (bytes32) { + return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", _messageHash)); +} + +function recoverSigner(bytes32 _ethSignedMessageHash, bytes memory _signature) pure returns (address) { + (bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature); + + return ecrecover(_ethSignedMessageHash, v, r, s); +} + +function splitSignature(bytes memory sig) pure returns (bytes32 r, bytes32 s, uint8 v) { + require(sig.length == 65, "invalid signature length"); + + assembly { + /* + First 32 bytes stores the length of the signature + + add(sig, 32) = pointer of sig + 32 + effectively, skips first 32 bytes of signature + + mload(p) loads next 32 bytes starting at the memory address p into memory + */ + + // first 32 bytes, after the length prefix + r := mload(add(sig, 32)) + // second 32 bytes + s := mload(add(sig, 64)) + // final byte (first byte of the next 32 bytes) + v := byte(0, mload(add(sig, 96))) + } + + // implicitly return (r, s, v) +} + /** * @title WorldRegistrationSystem * @author MUD (https://mud.dev) by Lattice (https://lattice.xyz) @@ -287,6 +325,13 @@ contract WorldRegistrationSystem is System, IWorldErrors, LimitedCallContext { } } + function registerDelegationWithSignature(bytes memory signature) public onlyDelegatecall { + bytes32 messageHash = getMessageHash(); + bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash); + + require(recoverSigner(ethSignedMessageHash, signature) == msg.sender, "not valid"); + } + /** * @notice Unregisters a delegation * @dev Deletes the new delegation from the caller to the specified delegatee diff --git a/packages/world/test/InitSystems.t.sol b/packages/world/test/InitSystems.t.sol index 99c9714911..0f59f49468 100644 --- a/packages/world/test/InitSystems.t.sol +++ b/packages/world/test/InitSystems.t.sol @@ -63,7 +63,7 @@ contract LimitedCallContextTest is Test { } function testRegistrationSystem() public { - string[14] memory functionSignaturesRegistration = getFunctionSignaturesRegistration(); + string[15] memory functionSignaturesRegistration = getFunctionSignaturesRegistration(); for (uint256 i; i < functionSignaturesRegistration.length; i++) { callSystem(REGISTRATION_SYSTEM_ID, functionSignaturesRegistration[i]); diff --git a/packages/world/test/World.t.sol b/packages/world/test/World.t.sol index de3d2d734f..0674131b9f 100644 --- a/packages/world/test/World.t.sol +++ b/packages/world/test/World.t.sol @@ -1161,6 +1161,31 @@ contract WorldTest is Test, GasReporter { ); } + function testRegisterDelegationWithSignature() public { + // Register a new system + WorldTestSystem system = new WorldTestSystem(); + ResourceId systemId = WorldResourceIdLib.encode({ + typeId: RESOURCE_SYSTEM, + namespace: "namespace", + name: "testSystem" + }); + world.registerNamespace(systemId.getNamespaceId()); + world.registerSystem(systemId, system, true); + + uint256 privateKey = 1; + + // Register a limited delegation + address delegator = vm.addr(privateKey); + address delegatee = address(2); + + bytes32 digest = keccak256(""); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + vm.prank(delegator); + world.registerDelegationWithSignature(signature); + } + function testUnregisterUnlimitedDelegation() public { // Register a new system WorldTestSystem system = new WorldTestSystem(); From 0dd31e29f1e9b8db4e7e38610c25551d6a3b9356 Mon Sep 17 00:00:00 2001 From: Fraser Scott Date: Wed, 20 Mar 2024 13:14:16 +0000 Subject: [PATCH 02/70] feat: use v, r, s --- packages/world/gas-report.json | 18 ++++- packages/world/package.json | 2 +- .../interfaces/IWorldRegistrationSystem.sol | 10 ++- .../src/modules/init/functionSignatures.sol | 2 +- .../WorldRegistrationSystem.sol | 78 +++++++++---------- packages/world/test/World.t.sol | 26 +++++-- 6 files changed, 82 insertions(+), 54 deletions(-) diff --git a/packages/world/gas-report.json b/packages/world/gas-report.json index dfc6b5ab43..e32b7f2ca8 100644 --- a/packages/world/gas-report.json +++ b/packages/world/gas-report.json @@ -63,7 +63,7 @@ "file": "test/Factories.t.sol", "test": "testWorldFactoryGas", "name": "deploy world via WorldFactory", - "gasUsed": 12796213 + "gasUsed": 12869027 }, { "file": "test/World.t.sol", @@ -81,7 +81,7 @@ "file": "test/World.t.sol", "test": "testCallFromUnlimitedDelegation", "name": "register an unlimited delegation", - "gasUsed": 74727 + "gasUsed": 74733 }, { "file": "test/World.t.sol", @@ -101,6 +101,18 @@ "name": "Push data to the table", "gasUsed": 110775 }, + { + "file": "test/World.t.sol", + "test": "testRegisterDelegationWithSignature", + "name": "register an unlimited delegation with signature", + "gasUsed": 80942 + }, + { + "file": "test/World.t.sol", + "test": "testRegisterDelegationWithSignature", + "name": "call a system via an unlimited delegation", + "gasUsed": 40891 + }, { "file": "test/World.t.sol", "test": "testRegisterFunctionSelector", @@ -117,7 +129,7 @@ "file": "test/World.t.sol", "test": "testRegisterRootFunctionSelector", "name": "Register a root function selector", - "gasUsed": 113693 + "gasUsed": 113716 }, { "file": "test/World.t.sol", diff --git a/packages/world/package.json b/packages/world/package.json index b4e4d9696c..26032ea063 100644 --- a/packages/world/package.json +++ b/packages/world/package.json @@ -46,7 +46,7 @@ "dev": "tsup --watch", "gas-report": "gas-report --save gas-report.json", "lint": "solhint --config ./.solhint.json 'src/**/*.sol'", - "test": "tsc --noEmit && vitest --run --passWithNoTests && forge test", + "test": "forge test", "test:ci": "pnpm run test" }, "dependencies": { diff --git a/packages/world/src/codegen/interfaces/IWorldRegistrationSystem.sol b/packages/world/src/codegen/interfaces/IWorldRegistrationSystem.sol index 9dfc35f3b2..41df6fa571 100644 --- a/packages/world/src/codegen/interfaces/IWorldRegistrationSystem.sol +++ b/packages/world/src/codegen/interfaces/IWorldRegistrationSystem.sol @@ -34,7 +34,15 @@ interface IWorldRegistrationSystem { function registerDelegation(address delegatee, ResourceId delegationControlId, bytes memory initCallData) external; - function registerDelegationWithSignature(bytes memory signature) external; + function registerDelegationWithSignature( + address delegatee, + ResourceId delegationControlId, + bytes memory initCallData, + address delegator, + uint8 v, + bytes32 r, + bytes32 s + ) external; function unregisterDelegation(address delegatee) external; diff --git a/packages/world/src/modules/init/functionSignatures.sol b/packages/world/src/modules/init/functionSignatures.sol index 4ca033d535..337d60377d 100644 --- a/packages/world/src/modules/init/functionSignatures.sol +++ b/packages/world/src/modules/init/functionSignatures.sol @@ -55,7 +55,7 @@ function getFunctionSignaturesRegistration() pure returns (string[15] memory) { "registerFunctionSelector(bytes32,string)", "registerRootFunctionSelector(bytes32,string,string)", "registerDelegation(address,bytes32,bytes)", - "registerDelegationWithSignature(bytes)", + "registerDelegationWithSignature(address,bytes32,bytes,address,uint8,bytes32,bytes32)", "unregisterDelegation(address)", "registerNamespaceDelegation(bytes32,bytes32,bytes)", "unregisterNamespaceDelegation(bytes32)" diff --git a/packages/world/src/modules/init/implementations/WorldRegistrationSystem.sol b/packages/world/src/modules/init/implementations/WorldRegistrationSystem.sol index 5a33165985..96c3fe1ba1 100644 --- a/packages/world/src/modules/init/implementations/WorldRegistrationSystem.sol +++ b/packages/world/src/modules/init/implementations/WorldRegistrationSystem.sol @@ -32,42 +32,12 @@ import { requireValidNamespace } from "../../../requireValidNamespace.sol"; import { LimitedCallContext } from "../LimitedCallContext.sol"; -function getMessageHash() pure returns (bytes32) { - return keccak256(""); -} - -function getEthSignedMessageHash(bytes32 _messageHash) pure returns (bytes32) { - return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", _messageHash)); -} - -function recoverSigner(bytes32 _ethSignedMessageHash, bytes memory _signature) pure returns (address) { - (bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature); - - return ecrecover(_ethSignedMessageHash, v, r, s); -} - -function splitSignature(bytes memory sig) pure returns (bytes32 r, bytes32 s, uint8 v) { - require(sig.length == 65, "invalid signature length"); - - assembly { - /* - First 32 bytes stores the length of the signature - - add(sig, 32) = pointer of sig + 32 - effectively, skips first 32 bytes of signature - - mload(p) loads next 32 bytes starting at the memory address p into memory - */ - - // first 32 bytes, after the length prefix - r := mload(add(sig, 32)) - // second 32 bytes - s := mload(add(sig, 64)) - // final byte (first byte of the next 32 bytes) - v := byte(0, mload(add(sig, 96))) - } - - // implicitly return (r, s, v) +function getMessageHash( + address delegatee, + ResourceId delegationControlId, + bytes memory initCallData +) pure returns (bytes32) { + return keccak256(abi.encode(delegatee, delegationControlId, initCallData)); } /** @@ -325,11 +295,39 @@ contract WorldRegistrationSystem is System, IWorldErrors, LimitedCallContext { } } - function registerDelegationWithSignature(bytes memory signature) public onlyDelegatecall { - bytes32 messageHash = getMessageHash(); - bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash); + function registerDelegationWithSignature( + address delegatee, + ResourceId delegationControlId, + bytes memory initCallData, + address delegator, + uint8 v, + bytes32 r, + bytes32 s + ) public onlyDelegatecall { + bytes32 hash = getMessageHash(delegatee, delegationControlId, initCallData); + require(ecrecover(hash, v, r, s) == delegator, "not valid"); - require(recoverSigner(ethSignedMessageHash, signature) == msg.sender, "not valid"); + // Store the delegation control contract address + UserDelegationControl._set({ + delegator: delegator, + delegatee: delegatee, + delegationControlId: delegationControlId + }); + + // If the delegation is limited... + if (Delegation.isLimited(delegationControlId)) { + // Require the delegationControl contract to implement the IDelegationControl interface + address delegationControl = Systems._getSystem(delegationControlId); + requireInterface(delegationControl, type(IDelegationControl).interfaceId); + + // Call the delegation control contract's init function + SystemCall.callWithHooksOrRevert({ + caller: delegator, + systemId: delegationControlId, + callData: initCallData, + value: 0 + }); + } } /** diff --git a/packages/world/test/World.t.sol b/packages/world/test/World.t.sol index 0674131b9f..e14bf3797c 100644 --- a/packages/world/test/World.t.sol +++ b/packages/world/test/World.t.sol @@ -58,6 +58,8 @@ import { DelegationControlMock } from "./DelegationControlMock.sol"; import { createWorld } from "./createWorld.sol"; import { createInitModule } from "./createInitModule.sol"; +import { getMessageHash } from "../src/modules/init/implementations/WorldRegistrationSystem.sol"; + interface IWorldTestSystem { function testNamespace__err(string memory input) external pure; } @@ -1172,18 +1174,26 @@ contract WorldTest is Test, GasReporter { world.registerNamespace(systemId.getNamespaceId()); world.registerSystem(systemId, system, true); - uint256 privateKey = 1; - // Register a limited delegation - address delegator = vm.addr(privateKey); + (address delegator, uint256 delegatorPk) = makeAddrAndKey("delegator"); address delegatee = address(2); - bytes32 digest = keccak256(""); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest); - bytes memory signature = abi.encodePacked(r, s, v); + bytes32 hash = getMessageHash(delegatee, UNLIMITED_DELEGATION, new bytes(0)); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(delegatorPk, hash); - vm.prank(delegator); - world.registerDelegationWithSignature(signature); + startGasReport("register an unlimited delegation with signature"); + world.registerDelegationWithSignature(delegatee, UNLIMITED_DELEGATION, new bytes(0), delegator, v, r, s); + endGasReport(); + + // Call a system from the delegatee on behalf of the delegator + vm.prank(delegatee); + startGasReport("call a system via an unlimited delegation"); + bytes memory returnData = world.callFrom(delegator, systemId, abi.encodeCall(WorldTestSystem.msgSender, ())); + endGasReport(); + address returnedAddress = abi.decode(returnData, (address)); + + // Expect the system to have received the delegator's address + assertEq(returnedAddress, delegator); } function testUnregisterUnlimitedDelegation() public { From 991f9214f058e20e5943964cbb60b53c27185a99 Mon Sep 17 00:00:00 2001 From: Fraser Scott Date: Wed, 20 Mar 2024 13:16:28 +0000 Subject: [PATCH 03/70] fix: test command --- packages/world/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/world/package.json b/packages/world/package.json index 26032ea063..b4e4d9696c 100644 --- a/packages/world/package.json +++ b/packages/world/package.json @@ -46,7 +46,7 @@ "dev": "tsup --watch", "gas-report": "gas-report --save gas-report.json", "lint": "solhint --config ./.solhint.json 'src/**/*.sol'", - "test": "forge test", + "test": "tsc --noEmit && vitest --run --passWithNoTests && forge test", "test:ci": "pnpm run test" }, "dependencies": { From cfed6b6d8f1045d81e55dcd0ead14f3025ef6bae Mon Sep 17 00:00:00 2001 From: Fraser Scott Date: Wed, 20 Mar 2024 13:19:55 +0000 Subject: [PATCH 04/70] refactor: add helper --- packages/world/gas-report.json | 4 +- packages/world/package.json | 2 +- .../WorldRegistrationSystem.sol | 69 ++++++++----------- 3 files changed, 30 insertions(+), 45 deletions(-) diff --git a/packages/world/gas-report.json b/packages/world/gas-report.json index e32b7f2ca8..ef42ae890f 100644 --- a/packages/world/gas-report.json +++ b/packages/world/gas-report.json @@ -81,7 +81,7 @@ "file": "test/World.t.sol", "test": "testCallFromUnlimitedDelegation", "name": "register an unlimited delegation", - "gasUsed": 74733 + "gasUsed": 74777 }, { "file": "test/World.t.sol", @@ -105,7 +105,7 @@ "file": "test/World.t.sol", "test": "testRegisterDelegationWithSignature", "name": "register an unlimited delegation with signature", - "gasUsed": 80942 + "gasUsed": 80986 }, { "file": "test/World.t.sol", diff --git a/packages/world/package.json b/packages/world/package.json index b4e4d9696c..26032ea063 100644 --- a/packages/world/package.json +++ b/packages/world/package.json @@ -46,7 +46,7 @@ "dev": "tsup --watch", "gas-report": "gas-report --save gas-report.json", "lint": "solhint --config ./.solhint.json 'src/**/*.sol'", - "test": "tsc --noEmit && vitest --run --passWithNoTests && forge test", + "test": "forge test", "test:ci": "pnpm run test" }, "dependencies": { diff --git a/packages/world/src/modules/init/implementations/WorldRegistrationSystem.sol b/packages/world/src/modules/init/implementations/WorldRegistrationSystem.sol index 96c3fe1ba1..9decba6102 100644 --- a/packages/world/src/modules/init/implementations/WorldRegistrationSystem.sol +++ b/packages/world/src/modules/init/implementations/WorldRegistrationSystem.sol @@ -40,6 +40,31 @@ function getMessageHash( return keccak256(abi.encode(delegatee, delegationControlId, initCallData)); } +function registerDelegationHelper( + address delegator, + address delegatee, + ResourceId delegationControlId, + bytes memory initCallData +) { + // Store the delegation control contract address + UserDelegationControl._set({ delegator: delegator, delegatee: delegatee, delegationControlId: delegationControlId }); + + // If the delegation is limited... + if (Delegation.isLimited(delegationControlId)) { + // Require the delegationControl contract to implement the IDelegationControl interface + address delegationControl = Systems._getSystem(delegationControlId); + requireInterface(delegationControl, type(IDelegationControl).interfaceId); + + // Call the delegation control contract's init function + SystemCall.callWithHooksOrRevert({ + caller: delegator, + systemId: delegationControlId, + callData: initCallData, + value: 0 + }); + } +} + /** * @title WorldRegistrationSystem * @author MUD (https://mud.dev) by Lattice (https://lattice.xyz) @@ -272,27 +297,7 @@ contract WorldRegistrationSystem is System, IWorldErrors, LimitedCallContext { ResourceId delegationControlId, bytes memory initCallData ) public onlyDelegatecall { - // Store the delegation control contract address - UserDelegationControl._set({ - delegator: _msgSender(), - delegatee: delegatee, - delegationControlId: delegationControlId - }); - - // If the delegation is limited... - if (Delegation.isLimited(delegationControlId)) { - // Require the delegationControl contract to implement the IDelegationControl interface - address delegationControl = Systems._getSystem(delegationControlId); - requireInterface(delegationControl, type(IDelegationControl).interfaceId); - - // Call the delegation control contract's init function - SystemCall.callWithHooksOrRevert({ - caller: _msgSender(), - systemId: delegationControlId, - callData: initCallData, - value: 0 - }); - } + registerDelegationHelper(_msgSender(), delegatee, delegationControlId, initCallData); } function registerDelegationWithSignature( @@ -307,27 +312,7 @@ contract WorldRegistrationSystem is System, IWorldErrors, LimitedCallContext { bytes32 hash = getMessageHash(delegatee, delegationControlId, initCallData); require(ecrecover(hash, v, r, s) == delegator, "not valid"); - // Store the delegation control contract address - UserDelegationControl._set({ - delegator: delegator, - delegatee: delegatee, - delegationControlId: delegationControlId - }); - - // If the delegation is limited... - if (Delegation.isLimited(delegationControlId)) { - // Require the delegationControl contract to implement the IDelegationControl interface - address delegationControl = Systems._getSystem(delegationControlId); - requireInterface(delegationControl, type(IDelegationControl).interfaceId); - - // Call the delegation control contract's init function - SystemCall.callWithHooksOrRevert({ - caller: delegator, - systemId: delegationControlId, - callData: initCallData, - value: 0 - }); - } + registerDelegationHelper(delegator, delegatee, delegationControlId, initCallData); } /** From 7e010498cef9ae9dfc3bf0a31bdcee4f78696ad7 Mon Sep 17 00:00:00 2001 From: Fraser Scott Date: Wed, 20 Mar 2024 13:29:43 +0000 Subject: [PATCH 05/70] chore: artifacts --- .../internal/init-module-implementation.mdx | 39 +++++++++++++++++++ docs/pages/world/reference/world-external.mdx | 39 +++++++++++++++++++ packages/world-modules/gas-report.json | 8 ++-- 3 files changed, 82 insertions(+), 4 deletions(-) diff --git a/docs/pages/world/reference/internal/init-module-implementation.mdx b/docs/pages/world/reference/internal/init-module-implementation.mdx index 5f6ff5226a..4943da741b 100644 --- a/docs/pages/world/reference/internal/init-module-implementation.mdx +++ b/docs/pages/world/reference/internal/init-module-implementation.mdx @@ -475,6 +475,20 @@ function registerDelegation( | `delegationControlId` | `ResourceId` | The ID controlling the delegation | | `initCallData` | `bytes` | The initialization data for the delegation | +#### registerDelegationWithSignature + +```solidity +function registerDelegationWithSignature( + address delegatee, + ResourceId delegationControlId, + bytes memory initCallData, + address delegator, + uint8 v, + bytes32 r, + bytes32 s +) public onlyDelegatecall; +``` + #### unregisterDelegation Unregisters a delegation @@ -528,3 +542,28 @@ function unregisterNamespaceDelegation(ResourceId namespaceId) public onlyDelega | Name | Type | Description | | ------------- | ------------ | ----------------------- | | `namespaceId` | `ResourceId` | The ID of the namespace | + +## getMessageHash + +[Git Source](https://github.com/latticexyz/mud/blob/main/packages/world/src/modules/init/implementations/WorldRegistrationSystem.sol) + +```solidity +function getMessageHash( + address delegatee, + ResourceId delegationControlId, + bytes memory initCallData +) pure returns (bytes32); +``` + +## registerDelegationHelper + +[Git Source](https://github.com/latticexyz/mud/blob/main/packages/world/src/modules/init/implementations/WorldRegistrationSystem.sol) + +```solidity +function registerDelegationHelper( + address delegator, + address delegatee, + ResourceId delegationControlId, + bytes memory initCallData +); +``` diff --git a/docs/pages/world/reference/world-external.mdx b/docs/pages/world/reference/world-external.mdx index 5e39647f00..a03162f938 100644 --- a/docs/pages/world/reference/world-external.mdx +++ b/docs/pages/world/reference/world-external.mdx @@ -397,6 +397,20 @@ function registerDelegation( | `delegationControlId` | `ResourceId` | The ID controlling the delegation | | `initCallData` | `bytes` | The initialization data for the delegation | +#### registerDelegationWithSignature + +```solidity +function registerDelegationWithSignature( + address delegatee, + ResourceId delegationControlId, + bytes memory initCallData, + address delegator, + uint8 v, + bytes32 r, + bytes32 s +) public onlyDelegatecall; +``` + #### unregisterDelegation Unregisters a delegation @@ -451,6 +465,31 @@ function unregisterNamespaceDelegation(ResourceId namespaceId) public onlyDelega | ------------- | ------------ | ----------------------- | | `namespaceId` | `ResourceId` | The ID of the namespace | +## getMessageHash + +[Git Source](https://github.com/latticexyz/mud/blob/main/packages/world/src/modules/init/implementations/WorldRegistrationSystem.sol) + +```solidity +function getMessageHash( + address delegatee, + ResourceId delegationControlId, + bytes memory initCallData +) pure returns (bytes32); +``` + +## registerDelegationHelper + +[Git Source](https://github.com/latticexyz/mud/blob/main/packages/world/src/modules/init/implementations/WorldRegistrationSystem.sol) + +```solidity +function registerDelegationHelper( + address delegator, + address delegatee, + ResourceId delegationControlId, + bytes memory initCallData +); +``` + ### Errors #### Store_TableAlreadyExists diff --git a/packages/world-modules/gas-report.json b/packages/world-modules/gas-report.json index 8bfcd3de5e..89c6c69df8 100644 --- a/packages/world-modules/gas-report.json +++ b/packages/world-modules/gas-report.json @@ -267,7 +267,7 @@ "file": "test/StandardDelegationsModule.t.sol", "test": "testCallFromCallboundDelegation", "name": "register a callbound delegation", - "gasUsed": 139044 + "gasUsed": 138994 }, { "file": "test/StandardDelegationsModule.t.sol", @@ -279,7 +279,7 @@ "file": "test/StandardDelegationsModule.t.sol", "test": "testCallFromSystemDelegation", "name": "register a systembound delegation", - "gasUsed": 136166 + "gasUsed": 136116 }, { "file": "test/StandardDelegationsModule.t.sol", @@ -291,7 +291,7 @@ "file": "test/StandardDelegationsModule.t.sol", "test": "testCallFromTimeboundDelegation", "name": "register a timebound delegation", - "gasUsed": 132709 + "gasUsed": 132659 }, { "file": "test/StandardDelegationsModule.t.sol", @@ -303,7 +303,7 @@ "file": "test/UniqueEntityModule.t.sol", "test": "testInstall", "name": "install unique entity module", - "gasUsed": 715495 + "gasUsed": 715518 }, { "file": "test/UniqueEntityModule.t.sol", From 3184d086f4961d54d9475062cdcece0ed76ba662 Mon Sep 17 00:00:00 2001 From: Fraser Scott Date: Wed, 20 Mar 2024 13:49:34 +0000 Subject: [PATCH 06/70] feat: signature nonces --- packages/world/gas-report.json | 10 +- packages/world/mud.config.ts | 4 + packages/world/package.json | 2 +- packages/world/src/IWorldErrors.sol | 2 + packages/world/src/codegen/index.sol | 1 + .../codegen/tables/UserDelegationNonces.sol | 199 ++++++++++++++++++ .../world/src/modules/init/InitModule.sol | 2 + .../WorldRegistrationSystem.sol | 16 +- packages/world/test/World.t.sol | 38 +++- 9 files changed, 256 insertions(+), 18 deletions(-) create mode 100644 packages/world/src/codegen/tables/UserDelegationNonces.sol diff --git a/packages/world/gas-report.json b/packages/world/gas-report.json index ef42ae890f..66f57b1c88 100644 --- a/packages/world/gas-report.json +++ b/packages/world/gas-report.json @@ -63,7 +63,7 @@ "file": "test/Factories.t.sol", "test": "testWorldFactoryGas", "name": "deploy world via WorldFactory", - "gasUsed": 12869027 + "gasUsed": 13238212 }, { "file": "test/World.t.sol", @@ -105,13 +105,7 @@ "file": "test/World.t.sol", "test": "testRegisterDelegationWithSignature", "name": "register an unlimited delegation with signature", - "gasUsed": 80986 - }, - { - "file": "test/World.t.sol", - "test": "testRegisterDelegationWithSignature", - "name": "call a system via an unlimited delegation", - "gasUsed": 40891 + "gasUsed": 115589 }, { "file": "test/World.t.sol", diff --git a/packages/world/mud.config.ts b/packages/world/mud.config.ts index dce3430995..2003e06bb6 100644 --- a/packages/world/mud.config.ts +++ b/packages/world/mud.config.ts @@ -44,6 +44,10 @@ export default mudConfig({ delegationControlId: "ResourceId", }, }, + UserDelegationNonces: { + keySchema: { delegator: "address" }, + valueSchema: { nonce: "uint256" }, + }, NamespaceDelegationControl: { keySchema: { namespaceId: "ResourceId", diff --git a/packages/world/package.json b/packages/world/package.json index 26032ea063..b4e4d9696c 100644 --- a/packages/world/package.json +++ b/packages/world/package.json @@ -46,7 +46,7 @@ "dev": "tsup --watch", "gas-report": "gas-report --save gas-report.json", "lint": "solhint --config ./.solhint.json 'src/**/*.sol'", - "test": "forge test", + "test": "tsc --noEmit && vitest --run --passWithNoTests && forge test", "test:ci": "pnpm run test" }, "dependencies": { diff --git a/packages/world/src/IWorldErrors.sol b/packages/world/src/IWorldErrors.sol index e702f86695..65a35581d1 100644 --- a/packages/world/src/IWorldErrors.sol +++ b/packages/world/src/IWorldErrors.sol @@ -107,4 +107,6 @@ interface IWorldErrors { * @param functionSelector The function selector of the disallowed callback. */ error World_CallbackNotAllowed(bytes4 functionSelector); + + error World_InvalidSignature(); } diff --git a/packages/world/src/codegen/index.sol b/packages/world/src/codegen/index.sol index b00d58a490..859bf5f615 100644 --- a/packages/world/src/codegen/index.sol +++ b/packages/world/src/codegen/index.sol @@ -7,6 +7,7 @@ import { NamespaceOwner } from "./tables/NamespaceOwner.sol"; import { ResourceAccess } from "./tables/ResourceAccess.sol"; import { InstalledModules } from "./tables/InstalledModules.sol"; import { UserDelegationControl } from "./tables/UserDelegationControl.sol"; +import { UserDelegationNonces } from "./tables/UserDelegationNonces.sol"; import { NamespaceDelegationControl } from "./tables/NamespaceDelegationControl.sol"; import { Balances } from "./tables/Balances.sol"; import { Systems } from "./tables/Systems.sol"; diff --git a/packages/world/src/codegen/tables/UserDelegationNonces.sol b/packages/world/src/codegen/tables/UserDelegationNonces.sol new file mode 100644 index 0000000000..f012200ebc --- /dev/null +++ b/packages/world/src/codegen/tables/UserDelegationNonces.sol @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +/* Autogenerated file. Do not edit manually. */ + +// Import store internals +import { IStore } from "@latticexyz/store/src/IStore.sol"; +import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; +import { StoreCore } from "@latticexyz/store/src/StoreCore.sol"; +import { Bytes } from "@latticexyz/store/src/Bytes.sol"; +import { Memory } from "@latticexyz/store/src/Memory.sol"; +import { SliceLib } from "@latticexyz/store/src/Slice.sol"; +import { EncodeArray } from "@latticexyz/store/src/tightcoder/EncodeArray.sol"; +import { FieldLayout } from "@latticexyz/store/src/FieldLayout.sol"; +import { Schema } from "@latticexyz/store/src/Schema.sol"; +import { PackedCounter, PackedCounterLib } from "@latticexyz/store/src/PackedCounter.sol"; +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; + +library UserDelegationNonces { + // Hex below is the result of `WorldResourceIdLib.encode({ namespace: "world", name: "UserDelegationNo", typeId: RESOURCE_TABLE });` + ResourceId constant _tableId = ResourceId.wrap(0x7462776f726c640000000000000000005573657244656c65676174696f6e4e6f); + + FieldLayout constant _fieldLayout = + FieldLayout.wrap(0x0020010020000000000000000000000000000000000000000000000000000000); + + // Hex-encoded key schema of (address) + Schema constant _keySchema = Schema.wrap(0x0014010061000000000000000000000000000000000000000000000000000000); + // Hex-encoded value schema of (uint256) + Schema constant _valueSchema = Schema.wrap(0x002001001f000000000000000000000000000000000000000000000000000000); + + /** + * @notice Get the table's key field names. + * @return keyNames An array of strings with the names of key fields. + */ + function getKeyNames() internal pure returns (string[] memory keyNames) { + keyNames = new string[](1); + keyNames[0] = "delegator"; + } + + /** + * @notice Get the table's value field names. + * @return fieldNames An array of strings with the names of value fields. + */ + function getFieldNames() internal pure returns (string[] memory fieldNames) { + fieldNames = new string[](1); + fieldNames[0] = "nonce"; + } + + /** + * @notice Register the table with its config. + */ + function register() internal { + StoreSwitch.registerTable(_tableId, _fieldLayout, _keySchema, _valueSchema, getKeyNames(), getFieldNames()); + } + + /** + * @notice Register the table with its config. + */ + function _register() internal { + StoreCore.registerTable(_tableId, _fieldLayout, _keySchema, _valueSchema, getKeyNames(), getFieldNames()); + } + + /** + * @notice Get nonce. + */ + function getNonce(address delegator) internal view returns (uint256 nonce) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(uint160(delegator))); + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (uint256(bytes32(_blob))); + } + + /** + * @notice Get nonce. + */ + function _getNonce(address delegator) internal view returns (uint256 nonce) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(uint160(delegator))); + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (uint256(bytes32(_blob))); + } + + /** + * @notice Get nonce. + */ + function get(address delegator) internal view returns (uint256 nonce) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(uint160(delegator))); + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (uint256(bytes32(_blob))); + } + + /** + * @notice Get nonce. + */ + function _get(address delegator) internal view returns (uint256 nonce) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(uint160(delegator))); + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (uint256(bytes32(_blob))); + } + + /** + * @notice Set nonce. + */ + function setNonce(address delegator, uint256 nonce) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(uint160(delegator))); + + StoreSwitch.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((nonce)), _fieldLayout); + } + + /** + * @notice Set nonce. + */ + function _setNonce(address delegator, uint256 nonce) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(uint160(delegator))); + + StoreCore.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((nonce)), _fieldLayout); + } + + /** + * @notice Set nonce. + */ + function set(address delegator, uint256 nonce) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(uint160(delegator))); + + StoreSwitch.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((nonce)), _fieldLayout); + } + + /** + * @notice Set nonce. + */ + function _set(address delegator, uint256 nonce) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(uint160(delegator))); + + StoreCore.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((nonce)), _fieldLayout); + } + + /** + * @notice Delete all data for given keys. + */ + function deleteRecord(address delegator) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(uint160(delegator))); + + StoreSwitch.deleteRecord(_tableId, _keyTuple); + } + + /** + * @notice Delete all data for given keys. + */ + function _deleteRecord(address delegator) internal { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(uint160(delegator))); + + StoreCore.deleteRecord(_tableId, _keyTuple, _fieldLayout); + } + + /** + * @notice Tightly pack static (fixed length) data using this table's schema. + * @return The static data, encoded into a sequence of bytes. + */ + function encodeStatic(uint256 nonce) internal pure returns (bytes memory) { + return abi.encodePacked(nonce); + } + + /** + * @notice Encode all of a record's fields. + * @return The static (fixed length) data, encoded into a sequence of bytes. + * @return The lengths of the dynamic fields (packed into a single bytes32 value). + * @return The dynamic (variable length) data, encoded into a sequence of bytes. + */ + function encode(uint256 nonce) internal pure returns (bytes memory, PackedCounter, bytes memory) { + bytes memory _staticData = encodeStatic(nonce); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + return (_staticData, _encodedLengths, _dynamicData); + } + + /** + * @notice Encode keys as a bytes32 array using this table's field layout. + */ + function encodeKeyTuple(address delegator) internal pure returns (bytes32[] memory) { + bytes32[] memory _keyTuple = new bytes32[](1); + _keyTuple[0] = bytes32(uint256(uint160(delegator))); + + return _keyTuple; + } +} diff --git a/packages/world/src/modules/init/InitModule.sol b/packages/world/src/modules/init/InitModule.sol index 0c09ea9295..d6da4afcbb 100644 --- a/packages/world/src/modules/init/InitModule.sol +++ b/packages/world/src/modules/init/InitModule.sol @@ -14,6 +14,7 @@ import { NamespaceOwner } from "../../codegen/tables/NamespaceOwner.sol"; import { ResourceAccess } from "../../codegen/tables/ResourceAccess.sol"; import { InstalledModules } from "../../codegen/tables/InstalledModules.sol"; import { UserDelegationControl } from "../../codegen/tables/UserDelegationControl.sol"; +import { UserDelegationNonces } from "../../codegen/tables/UserDelegationNonces.sol"; import { NamespaceDelegationControl } from "../../codegen/tables/NamespaceDelegationControl.sol"; import { AccessManagementSystem } from "./implementations/AccessManagementSystem.sol"; @@ -86,6 +87,7 @@ contract InitModule is Module { Balances.register(); InstalledModules.register(); UserDelegationControl.register(); + UserDelegationNonces.register(); NamespaceDelegationControl.register(); ResourceAccess.register(); Systems.register(); diff --git a/packages/world/src/modules/init/implementations/WorldRegistrationSystem.sol b/packages/world/src/modules/init/implementations/WorldRegistrationSystem.sol index 9decba6102..cc162373ff 100644 --- a/packages/world/src/modules/init/implementations/WorldRegistrationSystem.sol +++ b/packages/world/src/modules/init/implementations/WorldRegistrationSystem.sol @@ -17,6 +17,7 @@ import { requireInterface } from "../../../requireInterface.sol"; import { NamespaceOwner } from "../../../codegen/tables/NamespaceOwner.sol"; import { ResourceAccess } from "../../../codegen/tables/ResourceAccess.sol"; import { UserDelegationControl } from "../../../codegen/tables/UserDelegationControl.sol"; +import { UserDelegationNonces } from "../../../codegen/tables/UserDelegationNonces.sol"; import { NamespaceDelegationControl } from "../../../codegen/tables/NamespaceDelegationControl.sol"; import { ISystemHook } from "../../../ISystemHook.sol"; import { IWorldErrors } from "../../../IWorldErrors.sol"; @@ -35,9 +36,10 @@ import { LimitedCallContext } from "../LimitedCallContext.sol"; function getMessageHash( address delegatee, ResourceId delegationControlId, - bytes memory initCallData + bytes memory initCallData, + uint256 nonce ) pure returns (bytes32) { - return keccak256(abi.encode(delegatee, delegationControlId, initCallData)); + return keccak256(abi.encode(delegatee, delegationControlId, initCallData, nonce)); } function registerDelegationHelper( @@ -309,8 +311,14 @@ contract WorldRegistrationSystem is System, IWorldErrors, LimitedCallContext { bytes32 r, bytes32 s ) public onlyDelegatecall { - bytes32 hash = getMessageHash(delegatee, delegationControlId, initCallData); - require(ecrecover(hash, v, r, s) == delegator, "not valid"); + uint256 nonce = UserDelegationNonces.get(delegator); + bytes32 hash = getMessageHash(delegatee, delegationControlId, initCallData, nonce); + // If the message was not signed by the delegator or is invalid, revert + if (ecrecover(hash, v, r, s) != delegator) { + revert World_InvalidSignature(); + } + + UserDelegationNonces.set(delegator, nonce + 1); registerDelegationHelper(delegator, delegatee, delegationControlId, initCallData); } diff --git a/packages/world/test/World.t.sol b/packages/world/test/World.t.sol index e14bf3797c..5ce18a3ca6 100644 --- a/packages/world/test/World.t.sol +++ b/packages/world/test/World.t.sol @@ -225,7 +225,7 @@ contract WorldTest is Test, GasReporter { // Should have registered the core system function selectors RegistrationSystem registrationSystem = RegistrationSystem(Systems.getSystem(REGISTRATION_SYSTEM_ID)); - bytes4[22] memory funcSelectors = [ + bytes4[23] memory funcSelectors = [ // --- AccessManagementSystem --- AccessManagementSystem.grantAccess.selector, AccessManagementSystem.revokeAccess.selector, @@ -251,6 +251,7 @@ contract WorldTest is Test, GasReporter { registrationSystem.registerFunctionSelector.selector, registrationSystem.registerRootFunctionSelector.selector, registrationSystem.registerDelegation.selector, + registrationSystem.registerDelegationWithSignature.selector, registrationSystem.unregisterDelegation.selector, registrationSystem.registerNamespaceDelegation.selector, registrationSystem.unregisterNamespaceDelegation.selector @@ -1174,11 +1175,11 @@ contract WorldTest is Test, GasReporter { world.registerNamespace(systemId.getNamespaceId()); world.registerSystem(systemId, system, true); - // Register a limited delegation + // Register a limited delegation using signature (address delegator, uint256 delegatorPk) = makeAddrAndKey("delegator"); address delegatee = address(2); - bytes32 hash = getMessageHash(delegatee, UNLIMITED_DELEGATION, new bytes(0)); + bytes32 hash = getMessageHash(delegatee, UNLIMITED_DELEGATION, new bytes(0), 0); (uint8 v, bytes32 r, bytes32 s) = vm.sign(delegatorPk, hash); startGasReport("register an unlimited delegation with signature"); @@ -1187,13 +1188,40 @@ contract WorldTest is Test, GasReporter { // Call a system from the delegatee on behalf of the delegator vm.prank(delegatee); - startGasReport("call a system via an unlimited delegation"); bytes memory returnData = world.callFrom(delegator, systemId, abi.encodeCall(WorldTestSystem.msgSender, ())); - endGasReport(); address returnedAddress = abi.decode(returnData, (address)); // Expect the system to have received the delegator's address assertEq(returnedAddress, delegator); + + // Unregister delegation + vm.prank(delegator); + world.unregisterDelegation(delegatee); + + // Expect a revert when attempting to perform a call via callFrom after a delegation was unregistered + vm.expectRevert(abi.encodeWithSelector(IWorldErrors.World_DelegationNotFound.selector, delegator, delegatee)); + vm.prank(delegatee); + world.callFrom(delegator, systemId, abi.encodeCall(WorldTestSystem.msgSender, ())); + + // Attempt to register a limited delegation using an old signature + vm.expectRevert(abi.encodeWithSelector(IWorldErrors.World_InvalidSignature.selector)); + world.registerDelegationWithSignature(delegatee, UNLIMITED_DELEGATION, new bytes(0), delegator, v, r, s); + + // Expect a revert when attempting to perform a call via callFrom after a delegation was unregistered + vm.expectRevert(abi.encodeWithSelector(IWorldErrors.World_DelegationNotFound.selector, delegator, delegatee)); + vm.prank(delegatee); + world.callFrom(delegator, systemId, abi.encodeCall(WorldTestSystem.msgSender, ())); + + // Register a limited delegation using a new signature + hash = getMessageHash(delegatee, UNLIMITED_DELEGATION, new bytes(0), 1); + (v, r, s) = vm.sign(delegatorPk, hash); + + world.registerDelegationWithSignature(delegatee, UNLIMITED_DELEGATION, new bytes(0), delegator, v, r, s); + + // Call a system from the delegatee on behalf of the delegator + vm.prank(delegatee); + returnData = world.callFrom(delegator, systemId, abi.encodeCall(WorldTestSystem.msgSender, ())); + returnedAddress = abi.decode(returnData, (address)); } function testUnregisterUnlimitedDelegation() public { From 4700302b7f17cbd7dc5acd7591514db7173253ce Mon Sep 17 00:00:00 2001 From: Fraser Scott Date: Wed, 20 Mar 2024 14:39:52 +0000 Subject: [PATCH 07/70] chore: docs --- .../reference/internal/init-module-implementation.mdx | 3 ++- docs/pages/world/reference/world-external.mdx | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/pages/world/reference/internal/init-module-implementation.mdx b/docs/pages/world/reference/internal/init-module-implementation.mdx index 4943da741b..8f589b3a9b 100644 --- a/docs/pages/world/reference/internal/init-module-implementation.mdx +++ b/docs/pages/world/reference/internal/init-module-implementation.mdx @@ -551,7 +551,8 @@ function unregisterNamespaceDelegation(ResourceId namespaceId) public onlyDelega function getMessageHash( address delegatee, ResourceId delegationControlId, - bytes memory initCallData + bytes memory initCallData, + uint256 nonce ) pure returns (bytes32); ``` diff --git a/docs/pages/world/reference/world-external.mdx b/docs/pages/world/reference/world-external.mdx index a03162f938..9c93edd1fb 100644 --- a/docs/pages/world/reference/world-external.mdx +++ b/docs/pages/world/reference/world-external.mdx @@ -473,7 +473,8 @@ function unregisterNamespaceDelegation(ResourceId namespaceId) public onlyDelega function getMessageHash( address delegatee, ResourceId delegationControlId, - bytes memory initCallData + bytes memory initCallData, + uint256 nonce ) pure returns (bytes32); ``` @@ -1041,6 +1042,12 @@ error World_CallbackNotAllowed(bytes4 functionSelector); | ------------------ | -------- | ------------------------------------------------- | | `functionSelector` | `bytes4` | The function selector of the disallowed callback. | +#### World_InvalidSignature + +```solidity +error World_InvalidSignature(); +``` + ## IWorldFactory [Git Source](https://github.com/latticexyz/mud/blob/main/packages/world/src/IWorldFactory.sol) From 9da694d4331ab7278b2a7acf054e9f04b4480f1a Mon Sep 17 00:00:00 2001 From: Fraser Scott Date: Wed, 20 Mar 2024 15:14:36 +0000 Subject: [PATCH 08/70] refactor: arguments on error --- docs/pages/world/reference/world-external.mdx | 4 ++-- packages/world/src/IWorldErrors.sol | 5 ++++- .../implementations/WorldRegistrationSystem.sol | 16 +++++----------- .../src/modules/init/implementations/permit.sol | 15 +++++++++++++++ packages/world/test/World.t.sol | 2 +- packages/world/ts/protocol-snapshots/2.0.0.snap | 3 ++- 6 files changed, 29 insertions(+), 16 deletions(-) create mode 100644 packages/world/src/modules/init/implementations/permit.sol diff --git a/docs/pages/world/reference/world-external.mdx b/docs/pages/world/reference/world-external.mdx index 9c93edd1fb..4ffe4912e6 100644 --- a/docs/pages/world/reference/world-external.mdx +++ b/docs/pages/world/reference/world-external.mdx @@ -1042,10 +1042,10 @@ error World_CallbackNotAllowed(bytes4 functionSelector); | ------------------ | -------- | ------------------------------------------------- | | `functionSelector` | `bytes4` | The function selector of the disallowed callback. | -#### World_InvalidSignature +#### World_InvalidSigner ```solidity -error World_InvalidSignature(); +error World_InvalidSigner(); ``` ## IWorldFactory diff --git a/packages/world/src/IWorldErrors.sol b/packages/world/src/IWorldErrors.sol index 65a35581d1..416f00e51e 100644 --- a/packages/world/src/IWorldErrors.sol +++ b/packages/world/src/IWorldErrors.sol @@ -108,5 +108,8 @@ interface IWorldErrors { */ error World_CallbackNotAllowed(bytes4 functionSelector); - error World_InvalidSignature(); + /** + * @dev Mismatched signature. + */ + error World_InvalidSigner(address delegator, address delegatee); } diff --git a/packages/world/src/modules/init/implementations/WorldRegistrationSystem.sol b/packages/world/src/modules/init/implementations/WorldRegistrationSystem.sol index cc162373ff..21868d46e8 100644 --- a/packages/world/src/modules/init/implementations/WorldRegistrationSystem.sol +++ b/packages/world/src/modules/init/implementations/WorldRegistrationSystem.sol @@ -32,15 +32,7 @@ import { requireNamespace } from "../../../requireNamespace.sol"; import { requireValidNamespace } from "../../../requireValidNamespace.sol"; import { LimitedCallContext } from "../LimitedCallContext.sol"; - -function getMessageHash( - address delegatee, - ResourceId delegationControlId, - bytes memory initCallData, - uint256 nonce -) pure returns (bytes32) { - return keccak256(abi.encode(delegatee, delegationControlId, initCallData, nonce)); -} +import { getMessageHash } from "./permit.sol"; function registerDelegationHelper( address delegator, @@ -313,9 +305,11 @@ contract WorldRegistrationSystem is System, IWorldErrors, LimitedCallContext { ) public onlyDelegatecall { uint256 nonce = UserDelegationNonces.get(delegator); bytes32 hash = getMessageHash(delegatee, delegationControlId, initCallData, nonce); + // If the message was not signed by the delegator or is invalid, revert - if (ecrecover(hash, v, r, s) != delegator) { - revert World_InvalidSignature(); + address signer = ecrecover(hash, v, r, s); + if (signer != delegator) { + revert World_InvalidSigner(delegator, delegatee); } UserDelegationNonces.set(delegator, nonce + 1); diff --git a/packages/world/src/modules/init/implementations/permit.sol b/packages/world/src/modules/init/implementations/permit.sol new file mode 100644 index 0000000000..33b3dca913 --- /dev/null +++ b/packages/world/src/modules/init/implementations/permit.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; + +// Inspired by ERC20Permit https://eips.ethereum.org/EIPS/eip-2612 + +function getMessageHash( + address delegatee, + ResourceId delegationControlId, + bytes memory initCallData, + uint256 nonce +) pure returns (bytes32) { + return keccak256(abi.encode(delegatee, delegationControlId, initCallData, nonce)); +} diff --git a/packages/world/test/World.t.sol b/packages/world/test/World.t.sol index 5ce18a3ca6..4bfc1e4c24 100644 --- a/packages/world/test/World.t.sol +++ b/packages/world/test/World.t.sol @@ -1204,7 +1204,7 @@ contract WorldTest is Test, GasReporter { world.callFrom(delegator, systemId, abi.encodeCall(WorldTestSystem.msgSender, ())); // Attempt to register a limited delegation using an old signature - vm.expectRevert(abi.encodeWithSelector(IWorldErrors.World_InvalidSignature.selector)); + vm.expectRevert(abi.encodeWithSelector(IWorldErrors.World_InvalidSigner.selector, delegator, delegatee)); world.registerDelegationWithSignature(delegatee, UNLIMITED_DELEGATION, new bytes(0), delegator, v, r, s); // Expect a revert when attempting to perform a call via callFrom after a delegation was unregistered diff --git a/packages/world/ts/protocol-snapshots/2.0.0.snap b/packages/world/ts/protocol-snapshots/2.0.0.snap index 4ee517930f..b42b43d04d 100644 --- a/packages/world/ts/protocol-snapshots/2.0.0.snap +++ b/packages/world/ts/protocol-snapshots/2.0.0.snap @@ -22,6 +22,7 @@ "error World_InvalidNamespace(bytes14 namespace)", "error World_InvalidResourceId(bytes32 resourceId, string resourceIdString)", "error World_InvalidResourceType(bytes2 expected, bytes32 resourceId, string resourceIdString)", + "error World_InvalidSigner(address delegator, address delegatee)", "error World_ResourceAlreadyExists(bytes32 resourceId, string resourceIdString)", "error World_ResourceNotFound(bytes32 resourceId, string resourceIdString)", "error World_SystemAlreadyExists(address system)", @@ -64,4 +65,4 @@ "function storeVersion() pure returns (bytes32)", "function worldVersion() pure returns (bytes32)", "receive() external payable", -] \ No newline at end of file +] From 5fd4630269d5df0c00a112e079eebf0072c0e36b Mon Sep 17 00:00:00 2001 From: Fraser Scott Date: Wed, 20 Mar 2024 15:21:46 +0000 Subject: [PATCH 09/70] refactor: encodePacked --- docs/pages/world/reference/world-external.mdx | 4 +++- packages/world/gas-report.json | 2 +- packages/world/src/modules/init/implementations/permit.sol | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/pages/world/reference/world-external.mdx b/docs/pages/world/reference/world-external.mdx index 4ffe4912e6..c30f5000b3 100644 --- a/docs/pages/world/reference/world-external.mdx +++ b/docs/pages/world/reference/world-external.mdx @@ -1044,8 +1044,10 @@ error World_CallbackNotAllowed(bytes4 functionSelector); #### World_InvalidSigner +_Mismatched signature._ + ```solidity -error World_InvalidSigner(); +error World_InvalidSigner(address delegator, address delegatee); ``` ## IWorldFactory diff --git a/packages/world/gas-report.json b/packages/world/gas-report.json index 66f57b1c88..c3c12ebc59 100644 --- a/packages/world/gas-report.json +++ b/packages/world/gas-report.json @@ -105,7 +105,7 @@ "file": "test/World.t.sol", "test": "testRegisterDelegationWithSignature", "name": "register an unlimited delegation with signature", - "gasUsed": 115589 + "gasUsed": 115489 }, { "file": "test/World.t.sol", diff --git a/packages/world/src/modules/init/implementations/permit.sol b/packages/world/src/modules/init/implementations/permit.sol index 33b3dca913..074e4eebf4 100644 --- a/packages/world/src/modules/init/implementations/permit.sol +++ b/packages/world/src/modules/init/implementations/permit.sol @@ -11,5 +11,5 @@ function getMessageHash( bytes memory initCallData, uint256 nonce ) pure returns (bytes32) { - return keccak256(abi.encode(delegatee, delegationControlId, initCallData, nonce)); + return keccak256(abi.encodePacked(delegatee, delegationControlId, initCallData, nonce)); } From 4b88f2fd8ef7baf8832e85721b9ec1ac1967cf06 Mon Sep 17 00:00:00 2001 From: Fraser Scott Date: Wed, 20 Mar 2024 15:44:29 +0000 Subject: [PATCH 10/70] wip: example of signature --- .../minimal/packages/client-react/src/App.tsx | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/examples/minimal/packages/client-react/src/App.tsx b/examples/minimal/packages/client-react/src/App.tsx index c6f347b983..ab4babd9c5 100644 --- a/examples/minimal/packages/client-react/src/App.tsx +++ b/examples/minimal/packages/client-react/src/App.tsx @@ -3,14 +3,22 @@ import { useComponentValue, useEntityQuery } from "@latticexyz/react"; import { Has, getComponentValueStrict } from "@latticexyz/recs"; import { decodeEntity, singletonEntity } from "@latticexyz/store-sync/recs"; import { useMUD } from "./MUDContext"; +import { encodePacked, Hex, keccak256, verifyMessage } from "viem"; +import { resourceToHex } from "@latticexyz/common"; const ITEMS = ["cup", "spoon", "fork"]; const VARIANTS = ["yellow", "green", "red"]; +function getMessageHash(delegatee: Hex, delegationControlId: Hex, initCallData: Hex, nonce: bigint) { + return keccak256( + encodePacked(["address", "bytes32", "bytes", "uint256"], [delegatee, delegationControlId, initCallData, nonce]), + ); +} + export const App = () => { const { components: { CounterTable, Inventory, MessageTable }, - network: { worldContract, waitForTransaction }, + network: { walletClient, worldContract, waitForTransaction }, } = useMUD(); const counter = useComponentValue(CounterTable, singletonEntity); @@ -32,6 +40,40 @@ export const App = () => {
Counter: {counter?.value ?? "??"}
+