From cd7f1c14705933f24a6b748e410c05bddd235629 Mon Sep 17 00:00:00 2001 From: yonada Date: Wed, 3 Apr 2024 14:05:14 +0100 Subject: [PATCH] feat(world-modules): add `callWithSignature` (#2592) --- e2e/packages/contracts/mud.config.ts | 2 +- .../callRegisterDelegationWithSignature.ts | 108 ------------------ .../sync-test/data/callWithSignature.ts | 37 ++++++ e2e/packages/sync-test/package.json | 1 + .../registerDelegationWithSignature.test.ts | 40 ++++--- e2e/pnpm-lock.yaml | 3 + .../cli/src/utils/defaultModuleContracts.ts | 12 +- packages/world-modules/gas-report.json | 8 +- packages/world-modules/mud.config.ts | 8 +- packages/world-modules/src/index.sol | 2 +- ...Unstable_DelegationWithSignatureSystem.sol | 23 ---- ...l => Unstable_CallWithSignatureModule.sol} | 17 ++- .../Unstable_CallWithSignatureSystem.sol | 46 ++++++++ ...Unstable_DelegationWithSignatureSystem.sol | 47 -------- .../delegation/getSignedMessageHash.sol | 30 ++--- ...Nonces.sol => CallWithSignatureNonces.sol} | 52 ++++----- ...le.t.sol => CallWithSignatureModule.t.sol} | 51 ++++----- packages/world/ts/actions/callFrom.ts | 2 +- packages/world/ts/callWithSignatureTypes.ts | 10 ++ .../world/ts/delegationWithSignatureTypes.ts | 11 -- packages/world/ts/exports/internal.ts | 2 +- 21 files changed, 207 insertions(+), 305 deletions(-) delete mode 100644 e2e/packages/sync-test/data/callRegisterDelegationWithSignature.ts create mode 100644 e2e/packages/sync-test/data/callWithSignature.ts delete mode 100644 packages/world-modules/src/interfaces/IUnstable_DelegationWithSignatureSystem.sol rename packages/world-modules/src/modules/delegation/{Unstable_DelegationWithSignatureModule.sol => Unstable_CallWithSignatureModule.sol} (63%) create mode 100644 packages/world-modules/src/modules/delegation/Unstable_CallWithSignatureSystem.sol delete mode 100644 packages/world-modules/src/modules/delegation/Unstable_DelegationWithSignatureSystem.sol rename packages/world-modules/src/modules/delegation/tables/{UserDelegationNonces.sol => CallWithSignatureNonces.sol} (77%) rename packages/world-modules/test/{DelegationWithSignatureModule.t.sol => CallWithSignatureModule.t.sol} (73%) create mode 100644 packages/world/ts/callWithSignatureTypes.ts delete mode 100644 packages/world/ts/delegationWithSignatureTypes.ts diff --git a/e2e/packages/contracts/mud.config.ts b/e2e/packages/contracts/mud.config.ts index 6fb9e0471e..337c37e3e2 100644 --- a/e2e/packages/contracts/mud.config.ts +++ b/e2e/packages/contracts/mud.config.ts @@ -52,7 +52,7 @@ export default defineWorld({ }, modules: [ { - name: "Unstable_DelegationWithSignatureModule", + name: "Unstable_CallWithSignatureModule", root: true, args: [], }, diff --git a/e2e/packages/sync-test/data/callRegisterDelegationWithSignature.ts b/e2e/packages/sync-test/data/callRegisterDelegationWithSignature.ts deleted file mode 100644 index 78611c3e2d..0000000000 --- a/e2e/packages/sync-test/data/callRegisterDelegationWithSignature.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { Page } from "@playwright/test"; -import { GetContractReturnType, PublicClient, WalletClient } from "viem"; -import { AbiParametersToPrimitiveTypes, ExtractAbiFunction, ExtractAbiFunctionNames } from "abitype"; - -const DelegationAbi = [ - { - type: "function", - name: "registerDelegationWithSignature", - inputs: [ - { - name: "delegatee", - type: "address", - internalType: "address", - }, - { - name: "delegationControlId", - type: "bytes32", - internalType: "ResourceId", - }, - { - name: "initCallData", - type: "bytes", - internalType: "bytes", - }, - { - name: "delegator", - type: "address", - internalType: "address", - }, - { - name: "signature", - type: "bytes", - internalType: "bytes", - }, - ], - outputs: [], - stateMutability: "nonpayable", - }, -] as const; - -type DelegationAbi = typeof DelegationAbi; - -type WorldContract = GetContractReturnType; - -type WriteMethodName = ExtractAbiFunctionNames; -type WriteMethod = ExtractAbiFunction; -type WriteArgs = AbiParametersToPrimitiveTypes["inputs"]>; - -export function callRegisterDelegationWithSignature(page: Page, args?: WriteArgs<"registerDelegationWithSignature">) { - return page.evaluate( - ([_args]) => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const walletClient = (window as any).walletClient as WalletClient; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const worldContract = (window as any).worldContract as WorldContract; - - return walletClient - .writeContract({ - address: worldContract.address, - abi: [ - { - type: "function", - name: "registerDelegationWithSignature", - inputs: [ - { - name: "delegatee", - type: "address", - internalType: "address", - }, - { - name: "delegationControlId", - type: "bytes32", - internalType: "ResourceId", - }, - { - name: "initCallData", - type: "bytes", - internalType: "bytes", - }, - { - name: "delegator", - type: "address", - internalType: "address", - }, - { - name: "signature", - type: "bytes", - internalType: "bytes", - }, - ], - outputs: [], - stateMutability: "nonpayable", - }, - ], - functionName: "registerDelegationWithSignature", - args: _args, - }) - .then((tx) => window["waitForTransaction"](tx)) - .catch((error) => { - console.error(error); - throw new Error( - [`Error executing registerDelegationWithSignature with args:`, JSON.stringify(_args), error].join("\n\n"), - ); - }); - }, - [args], - ); -} diff --git a/e2e/packages/sync-test/data/callWithSignature.ts b/e2e/packages/sync-test/data/callWithSignature.ts new file mode 100644 index 0000000000..6bdaf327c0 --- /dev/null +++ b/e2e/packages/sync-test/data/callWithSignature.ts @@ -0,0 +1,37 @@ +import { Page } from "@playwright/test"; +import { GetContractReturnType, PublicClient, WalletClient } from "viem"; +import { AbiParametersToPrimitiveTypes, ExtractAbiFunction, ExtractAbiFunctionNames } from "abitype"; +import CallWithSignatureAbi from "@latticexyz/world-modules/out/Unstable_CallWithSignatureSystem.sol/Unstable_CallWithSignatureSystem.abi.json"; + +type CallWithSignatureAbi = typeof CallWithSignatureAbi; + +type WorldContract = GetContractReturnType; + +type WriteMethodName = ExtractAbiFunctionNames; +type WriteMethod = ExtractAbiFunction; +type WriteArgs = AbiParametersToPrimitiveTypes["inputs"]>; + +export function callWithSignature(page: Page, args?: WriteArgs<"callWithSignature">) { + return page.evaluate( + ([_args, _CallWithSignatureAbi]) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const walletClient = (window as any).walletClient as WalletClient; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const worldContract = (window as any).worldContract as WorldContract; + + return walletClient + .writeContract({ + address: worldContract.address, + abi: _CallWithSignatureAbi, + functionName: "callWithSignature", + args: _args, + }) + .then((tx) => window["waitForTransaction"](tx)) + .catch((error) => { + console.error(error); + throw new Error([`Error executing callWithSignature with args:`, JSON.stringify(_args), error].join("\n\n")); + }); + }, + [args, CallWithSignatureAbi], + ); +} diff --git a/e2e/packages/sync-test/package.json b/e2e/packages/sync-test/package.json index 0e342593e1..53c6d22a74 100644 --- a/e2e/packages/sync-test/package.json +++ b/e2e/packages/sync-test/package.json @@ -20,6 +20,7 @@ "@latticexyz/store-sync": "link:../../../packages/store-sync", "@latticexyz/utils": "link:../../../packages/utils", "@latticexyz/world": "link:../../../packages/world", + "@latticexyz/world-modules": "link:../../../packages/world-modules", "@viem/anvil": "^0.0.6", "abitype": "1.0.0", "chalk": "^5.2.0", diff --git a/e2e/packages/sync-test/registerDelegationWithSignature.test.ts b/e2e/packages/sync-test/registerDelegationWithSignature.test.ts index 857c692e1d..2ee24aab38 100644 --- a/e2e/packages/sync-test/registerDelegationWithSignature.test.ts +++ b/e2e/packages/sync-test/registerDelegationWithSignature.test.ts @@ -6,21 +6,22 @@ import { deployContracts, startViteServer, startBrowserAndPage, openClientWithRo import { rpcHttpUrl } from "./setup/constants"; import { waitForInitialSync } from "./data/waitForInitialSync"; import { createBurnerAccount, resourceToHex, transportObserver } from "@latticexyz/common"; -import { http, createWalletClient, ClientConfig } from "viem"; +import { http, createWalletClient, ClientConfig, encodeFunctionData } from "viem"; import { mudFoundry } from "@latticexyz/common/chains"; import { encodeEntity } from "@latticexyz/store-sync/recs"; import { callPageFunction } from "./data/callPageFunction"; import worldConfig from "@latticexyz/world/mud.config"; import { worldToV1 } from "@latticexyz/world/config/v2"; -import { delegationWithSignatureTypes } from "@latticexyz/world/internal"; +import { callWithSignatureTypes } from "@latticexyz/world/internal"; import { getWorld } from "./data/getWorld"; -import { callRegisterDelegationWithSignature } from "./data/callRegisterDelegationWithSignature"; +import { callWithSignature } from "./data/callWithSignature"; +import IWorldAbi from "../contracts/out/IWorld.sol/IWorld.abi.json"; const DELEGATOR_PRIVATE_KEY = "0x67bbd1575ecc79b3247c7d7b87a5bc533ccb6a63955a9fefdfaf75853f7cd543"; const worldConfigV1 = worldToV1(worldConfig); -describe("registerDelegationWithSignature", async () => { +describe("callWithSignature", async () => { const asyncErrorHandler = createAsyncErrorHandler(); let webserver: ViteDevServer; let browser: Browser; @@ -60,38 +61,39 @@ describe("registerDelegationWithSignature", async () => { }); const worldContract = await getWorld(page); + const systemId = resourceToHex({ type: "system", namespace: "", name: "Registration" }); // Declare delegation parameters const delegatee = "0x7203e7ADfDF38519e1ff4f8Da7DCdC969371f377"; const delegationControlId = resourceToHex({ type: "system", namespace: "", name: "unlimited" }); const initCallData = "0x"; + + const callData = encodeFunctionData({ + abi: IWorldAbi, + functionName: "registerDelegation", + args: [delegatee, delegationControlId, initCallData], + }); + const nonce = 0n; - // Sign registration message + // Sign registration call message const signature = await delegatorWalletClient.signTypedData({ domain: { chainId: delegatorWalletClient.chain.id, verifyingContract: worldContract.address, }, - types: delegationWithSignatureTypes, - primaryType: "Delegation", + types: callWithSignatureTypes, + primaryType: "Call", message: { - delegatee, - delegationControlId, - initCallData, - delegator: delegator.address, + signer: delegator.address, + systemId, + callData, nonce, }, }); - // Register the delegation - await callRegisterDelegationWithSignature(page, [ - delegatee, - delegationControlId, - initCallData, - delegator.address, - signature, - ]); + // Register a delegation + await callWithSignature(page, [delegator.address, systemId, callData, signature]); // Expect delegation to have been created const value = await callPageFunction(page, "getComponentValue", [ diff --git a/e2e/pnpm-lock.yaml b/e2e/pnpm-lock.yaml index d3a9bbe58e..003c35a3ed 100644 --- a/e2e/pnpm-lock.yaml +++ b/e2e/pnpm-lock.yaml @@ -156,6 +156,9 @@ importers: '@latticexyz/world': specifier: link:../../../packages/world version: link:../../../packages/world + '@latticexyz/world-modules': + specifier: link:../../../packages/world-modules + version: link:../../../packages/world-modules '@viem/anvil': specifier: ^0.0.6 version: 0.0.6 diff --git a/packages/cli/src/utils/defaultModuleContracts.ts b/packages/cli/src/utils/defaultModuleContracts.ts index fba76f5355..9356a51b5b 100644 --- a/packages/cli/src/utils/defaultModuleContracts.ts +++ b/packages/cli/src/utils/defaultModuleContracts.ts @@ -2,7 +2,7 @@ import KeysWithValueModuleData from "@latticexyz/world-modules/out/KeysWithValue import KeysInTableModuleData from "@latticexyz/world-modules/out/KeysInTableModule.sol/KeysInTableModule.json" assert { type: "json" }; import UniqueEntityModuleData from "@latticexyz/world-modules/out/UniqueEntityModule.sol/UniqueEntityModule.json" assert { type: "json" }; // eslint-disable-next-line max-len -import Unstable_DelegationWithSignatureModuleData from "@latticexyz/world-modules/out/Unstable_DelegationWithSignatureModule.sol/Unstable_DelegationWithSignatureModule.json" assert { type: "json" }; +import Unstable_CallWithSignatureModuleData from "@latticexyz/world-modules/out/Unstable_CallWithSignatureModule.sol/Unstable_CallWithSignatureModule.json" assert { type: "json" }; import { Abi, Hex, size } from "viem"; import { findPlaceholders } from "./findPlaceholders"; @@ -30,10 +30,10 @@ export const defaultModuleContracts = [ deployedBytecodeSize: size(UniqueEntityModuleData.deployedBytecode.object as Hex), }, { - name: "Unstable_DelegationWithSignatureModule", - abi: Unstable_DelegationWithSignatureModuleData.abi as Abi, - bytecode: Unstable_DelegationWithSignatureModuleData.bytecode.object as Hex, - placeholders: findPlaceholders(Unstable_DelegationWithSignatureModuleData.bytecode.linkReferences), - deployedBytecodeSize: size(Unstable_DelegationWithSignatureModuleData.deployedBytecode.object as Hex), + name: "Unstable_CallWithSignatureModule", + abi: Unstable_CallWithSignatureModuleData.abi as Abi, + bytecode: Unstable_CallWithSignatureModuleData.bytecode.object as Hex, + placeholders: findPlaceholders(Unstable_CallWithSignatureModuleData.bytecode.linkReferences), + deployedBytecodeSize: size(Unstable_CallWithSignatureModuleData.deployedBytecode.object as Hex), }, ]; diff --git a/packages/world-modules/gas-report.json b/packages/world-modules/gas-report.json index 61eee5d9ad..79d3974954 100644 --- a/packages/world-modules/gas-report.json +++ b/packages/world-modules/gas-report.json @@ -1,15 +1,15 @@ [ { - "file": "test/DelegationWithSignatureModule.t.sol", + "file": "test/CallWithSignatureModule.t.sol", "test": "testInstallRoot", "name": "install delegation module", - "gasUsed": 689194 + "gasUsed": 687912 }, { - "file": "test/DelegationWithSignatureModule.t.sol", + "file": "test/CallWithSignatureModule.t.sol", "test": "testRegisterDelegationWithSignature", "name": "register an unlimited delegation with signature", - "gasUsed": 117588 + "gasUsed": 133222 }, { "file": "test/ERC20.t.sol", diff --git a/packages/world-modules/mud.config.ts b/packages/world-modules/mud.config.ts index bd6368d233..e4a75fd37c 100644 --- a/packages/world-modules/mud.config.ts +++ b/packages/world-modules/mud.config.ts @@ -278,9 +278,9 @@ export default defineWorld({ * REGISTER DELEGATION WITH SIGNATURE MODULE * ************************************************************************/ - UserDelegationNonces: { - schema: { delegator: "address", nonce: "uint256" }, - key: ["delegator"], + CallWithSignatureNonces: { + schema: { signer: "address", nonce: "uint256" }, + key: ["signer"], codegen: { outputDirectory: "modules/delegation/tables", }, @@ -291,6 +291,6 @@ export default defineWorld({ "PuppetFactorySystem", "ERC20System", "ERC721System", - "Unstable_DelegationWithSignatureSystem", + "Unstable_CallWithSignatureSystem", ], }); diff --git a/packages/world-modules/src/index.sol b/packages/world-modules/src/index.sol index 3834739512..1d323d8fe4 100644 --- a/packages/world-modules/src/index.sol +++ b/packages/world-modules/src/index.sol @@ -22,4 +22,4 @@ import { Owners } from "./modules/erc721-puppet/tables/Owners.sol"; import { TokenApproval } from "./modules/erc721-puppet/tables/TokenApproval.sol"; import { OperatorApproval } from "./modules/erc721-puppet/tables/OperatorApproval.sol"; import { ERC721Registry } from "./modules/erc721-puppet/tables/ERC721Registry.sol"; -import { UserDelegationNonces } from "./modules/delegation/tables/UserDelegationNonces.sol"; +import { CallWithSignatureNonces } from "./modules/delegation/tables/CallWithSignatureNonces.sol"; diff --git a/packages/world-modules/src/interfaces/IUnstable_DelegationWithSignatureSystem.sol b/packages/world-modules/src/interfaces/IUnstable_DelegationWithSignatureSystem.sol deleted file mode 100644 index b00fa22860..0000000000 --- a/packages/world-modules/src/interfaces/IUnstable_DelegationWithSignatureSystem.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.24; - -/* Autogenerated file. Do not edit manually. */ - -import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; - -/** - * @title IUnstable_DelegationWithSignatureSystem - * @author MUD (https://mud.dev) by Lattice (https://lattice.xyz) - * @dev This interface is automatically generated from the corresponding system contract. Do not edit manually. - */ -interface IUnstable_DelegationWithSignatureSystem { - error InvalidSignature(address signer); - - function registerDelegationWithSignature( - address delegatee, - ResourceId delegationControlId, - bytes memory initCallData, - address delegator, - bytes memory signature - ) external; -} diff --git a/packages/world-modules/src/modules/delegation/Unstable_DelegationWithSignatureModule.sol b/packages/world-modules/src/modules/delegation/Unstable_CallWithSignatureModule.sol similarity index 63% rename from packages/world-modules/src/modules/delegation/Unstable_DelegationWithSignatureModule.sol rename to packages/world-modules/src/modules/delegation/Unstable_CallWithSignatureModule.sol index 58c2ee1770..12563d6025 100644 --- a/packages/world-modules/src/modules/delegation/Unstable_DelegationWithSignatureModule.sol +++ b/packages/world-modules/src/modules/delegation/Unstable_CallWithSignatureModule.sol @@ -6,14 +6,13 @@ import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld. import { Module } from "@latticexyz/world/src/Module.sol"; import { revertWithBytes } from "@latticexyz/world/src/revertWithBytes.sol"; -import { UserDelegationNonces } from "./tables/UserDelegationNonces.sol"; -import { Unstable_DelegationWithSignatureSystem } from "./Unstable_DelegationWithSignatureSystem.sol"; +import { CallWithSignatureNonces } from "./tables/CallWithSignatureNonces.sol"; +import { Unstable_CallWithSignatureSystem } from "./Unstable_CallWithSignatureSystem.sol"; import { DELEGATION_SYSTEM_ID } from "./constants.sol"; -contract Unstable_DelegationWithSignatureModule is Module { - Unstable_DelegationWithSignatureSystem private immutable delegationWithSignatureSystem = - new Unstable_DelegationWithSignatureSystem(); +contract Unstable_CallWithSignatureModule is Module { + Unstable_CallWithSignatureSystem private immutable callWithSignatureSystem = new Unstable_CallWithSignatureSystem(); function installRoot(bytes memory encodedArgs) public { requireNotInstalled(__self, encodedArgs); @@ -21,11 +20,11 @@ contract Unstable_DelegationWithSignatureModule is Module { IBaseWorld world = IBaseWorld(_world()); // Register table - UserDelegationNonces._register(); + CallWithSignatureNonces._register(); // Register system (bool success, bytes memory data) = address(world).delegatecall( - abi.encodeCall(world.registerSystem, (DELEGATION_SYSTEM_ID, delegationWithSignatureSystem, true)) + abi.encodeCall(world.registerSystem, (DELEGATION_SYSTEM_ID, callWithSignatureSystem, true)) ); if (!success) revertWithBytes(data); @@ -35,8 +34,8 @@ contract Unstable_DelegationWithSignatureModule is Module { world.registerRootFunctionSelector, ( DELEGATION_SYSTEM_ID, - "registerDelegationWithSignature(address,bytes32,bytes,address,bytes)", - "registerDelegationWithSignature(address,bytes32,bytes,address,bytes)" + "callWithSignature(address,bytes32,bytes,bytes)", + "callWithSignature(address,bytes32,bytes,bytes)" ) ) ); diff --git a/packages/world-modules/src/modules/delegation/Unstable_CallWithSignatureSystem.sol b/packages/world-modules/src/modules/delegation/Unstable_CallWithSignatureSystem.sol new file mode 100644 index 0000000000..43fa0d12e1 --- /dev/null +++ b/packages/world-modules/src/modules/delegation/Unstable_CallWithSignatureSystem.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; +import { System } from "@latticexyz/world/src/System.sol"; +import { SystemCall } from "@latticexyz/world/src/SystemCall.sol"; +import { createDelegation } from "@latticexyz/world/src/modules/init/implementations/createDelegation.sol"; + +import { CallWithSignatureNonces } from "./tables/CallWithSignatureNonces.sol"; +import { getSignedMessageHash } from "./getSignedMessageHash.sol"; +import { ECDSA } from "./ECDSA.sol"; + +contract Unstable_CallWithSignatureSystem is System { + /** + * @dev Mismatched signature. + */ + error InvalidSignature(address signer); + + /** + * @notice Calls a system with a given system ID using the given signature. + * @param signer The address on whose behalf the system is called. + * @param systemId The ID of the system to be called. + * @param callData The ABI data for the system call. + * @param signature The EIP712 signature. + * @return Return data from the system call. + */ + function callWithSignature( + address signer, + ResourceId systemId, + bytes memory callData, + bytes memory signature + ) external payable returns (bytes memory) { + uint256 nonce = CallWithSignatureNonces.get(signer); + bytes32 hash = getSignedMessageHash(signer, systemId, callData, nonce, _world()); + + // If the message was not signed by the delegator or is invalid, revert + address recoveredSigner = ECDSA.recover(hash, signature); + if (recoveredSigner != signer) { + revert InvalidSignature(recoveredSigner); + } + + CallWithSignatureNonces.set(signer, nonce + 1); + + return SystemCall.callWithHooksOrRevert(signer, systemId, callData, _msgValue()); + } +} diff --git a/packages/world-modules/src/modules/delegation/Unstable_DelegationWithSignatureSystem.sol b/packages/world-modules/src/modules/delegation/Unstable_DelegationWithSignatureSystem.sol deleted file mode 100644 index 292457b69d..0000000000 --- a/packages/world-modules/src/modules/delegation/Unstable_DelegationWithSignatureSystem.sol +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.24; - -import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; -import { System } from "@latticexyz/world/src/System.sol"; -import { createDelegation } from "@latticexyz/world/src/modules/init/implementations/createDelegation.sol"; - -import { UserDelegationNonces } from "./tables/UserDelegationNonces.sol"; -import { getSignedMessageHash } from "./getSignedMessageHash.sol"; -import { ECDSA } from "./ECDSA.sol"; - -contract Unstable_DelegationWithSignatureSystem is System { - /** - * @dev Mismatched signature. - */ - error InvalidSignature(address signer); - - /** - * @notice Registers a delegation for `delegator` with a signature - * @dev Creates a new delegation from the caller to the specified delegatee - * @param delegatee The address of the delegatee - * @param delegationControlId The ID controlling the delegation - * @param initCallData The initialization data for the delegation - * @param delegator The address of the delegator - * @param signature The EIP712 signature - */ - function registerDelegationWithSignature( - address delegatee, - ResourceId delegationControlId, - bytes memory initCallData, - address delegator, - bytes memory signature - ) public { - uint256 nonce = UserDelegationNonces.get(delegator); - bytes32 hash = getSignedMessageHash(delegatee, delegationControlId, initCallData, delegator, nonce, _world()); - - // If the message was not signed by the delegator or is invalid, revert - address signer = ECDSA.recover(hash, signature); - if (signer != delegator) { - revert InvalidSignature(signer); - } - - UserDelegationNonces.set(delegator, nonce + 1); - - createDelegation(delegator, delegatee, delegationControlId, initCallData); - } -} diff --git a/packages/world-modules/src/modules/delegation/getSignedMessageHash.sol b/packages/world-modules/src/modules/delegation/getSignedMessageHash.sol index 8fe4bbf008..617b304484 100644 --- a/packages/world-modules/src/modules/delegation/getSignedMessageHash.sol +++ b/packages/world-modules/src/modules/delegation/getSignedMessageHash.sol @@ -3,27 +3,23 @@ pragma solidity >=0.8.24; import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; -// Implements EIP712 signatures https://eips.ethereum.org/EIPS/eip-712 - -bytes32 constant DELEGATION_TYPEHASH = keccak256( - "Delegation(address delegatee,bytes32 delegationControlId,bytes initCallData,address delegator,uint256 nonce)" -); +bytes32 constant CALL_TYPEHASH = keccak256("Call(address signer,bytes32 systemId,bytes callData,uint256 nonce)"); /** * @notice Generate the message hash for a given delegation signature. - * @dev We include the delegator to prevent generating a garbage signature and registering a delegation for the corresponding recovered address. - * @param delegatee The address of the delegatee - * @param delegationControlId The ID controlling the delegation - * @param initCallData The initialization data for the delegation - * @param delegator The address of the delegator - * @param nonce The nonce of the delegator + * For EIP712 signatures https://eips.ethereum.org/EIPS/eip-712 + * @dev We include the signer address to prevent generating a signature that recovers to a random address that didn't sign the message. + * @param signer The address on whose behalf the system is called. + * @param systemId The ID of the system to be called. + * @param callData The ABI data for the system call. + * @param nonce The nonce of the signer * @param worldAddress The world address + * @return Return the message hash. */ function getSignedMessageHash( - address delegatee, - ResourceId delegationControlId, - bytes memory initCallData, - address delegator, + address signer, + ResourceId systemId, + bytes memory callData, uint256 nonce, address worldAddress ) view returns (bytes32) { @@ -36,9 +32,7 @@ function getSignedMessageHash( abi.encodePacked( "\x19\x01", domainSeperator, - keccak256( - abi.encode(DELEGATION_TYPEHASH, delegatee, delegationControlId, keccak256(initCallData), delegator, nonce) - ) + keccak256(abi.encode(CALL_TYPEHASH, signer, systemId, keccak256(callData), nonce)) ) ); } diff --git a/packages/world-modules/src/modules/delegation/tables/UserDelegationNonces.sol b/packages/world-modules/src/modules/delegation/tables/CallWithSignatureNonces.sol similarity index 77% rename from packages/world-modules/src/modules/delegation/tables/UserDelegationNonces.sol rename to packages/world-modules/src/modules/delegation/tables/CallWithSignatureNonces.sol index c629d55f06..554b33df9f 100644 --- a/packages/world-modules/src/modules/delegation/tables/UserDelegationNonces.sol +++ b/packages/world-modules/src/modules/delegation/tables/CallWithSignatureNonces.sol @@ -16,9 +16,9 @@ import { Schema } from "@latticexyz/store/src/Schema.sol"; import { EncodedLengths, EncodedLengthsLib } from "@latticexyz/store/src/EncodedLengths.sol"; import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; -library UserDelegationNonces { - // Hex below is the result of `WorldResourceIdLib.encode({ namespace: "", name: "UserDelegationNo", typeId: RESOURCE_TABLE });` - ResourceId constant _tableId = ResourceId.wrap(0x746200000000000000000000000000005573657244656c65676174696f6e4e6f); +library CallWithSignatureNonces { + // Hex below is the result of `WorldResourceIdLib.encode({ namespace: "", name: "CallWithSignatur", typeId: RESOURCE_TABLE });` + ResourceId constant _tableId = ResourceId.wrap(0x7462000000000000000000000000000043616c6c576974685369676e61747572); FieldLayout constant _fieldLayout = FieldLayout.wrap(0x0020010020000000000000000000000000000000000000000000000000000000); @@ -34,7 +34,7 @@ library UserDelegationNonces { */ function getKeyNames() internal pure returns (string[] memory keyNames) { keyNames = new string[](1); - keyNames[0] = "delegator"; + keyNames[0] = "signer"; } /** @@ -63,9 +63,9 @@ library UserDelegationNonces { /** * @notice Get nonce. */ - function getNonce(address delegator) internal view returns (uint256 nonce) { + function getNonce(address signer) internal view returns (uint256 nonce) { bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = bytes32(uint256(uint160(delegator))); + _keyTuple[0] = bytes32(uint256(uint160(signer))); bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); return (uint256(bytes32(_blob))); @@ -74,9 +74,9 @@ library UserDelegationNonces { /** * @notice Get nonce. */ - function _getNonce(address delegator) internal view returns (uint256 nonce) { + function _getNonce(address signer) internal view returns (uint256 nonce) { bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = bytes32(uint256(uint160(delegator))); + _keyTuple[0] = bytes32(uint256(uint160(signer))); bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); return (uint256(bytes32(_blob))); @@ -85,9 +85,9 @@ library UserDelegationNonces { /** * @notice Get nonce. */ - function get(address delegator) internal view returns (uint256 nonce) { + function get(address signer) internal view returns (uint256 nonce) { bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = bytes32(uint256(uint160(delegator))); + _keyTuple[0] = bytes32(uint256(uint160(signer))); bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); return (uint256(bytes32(_blob))); @@ -96,9 +96,9 @@ library UserDelegationNonces { /** * @notice Get nonce. */ - function _get(address delegator) internal view returns (uint256 nonce) { + function _get(address signer) internal view returns (uint256 nonce) { bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = bytes32(uint256(uint160(delegator))); + _keyTuple[0] = bytes32(uint256(uint160(signer))); bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); return (uint256(bytes32(_blob))); @@ -107,9 +107,9 @@ library UserDelegationNonces { /** * @notice Set nonce. */ - function setNonce(address delegator, uint256 nonce) internal { + function setNonce(address signer, uint256 nonce) internal { bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = bytes32(uint256(uint160(delegator))); + _keyTuple[0] = bytes32(uint256(uint160(signer))); StoreSwitch.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((nonce)), _fieldLayout); } @@ -117,9 +117,9 @@ library UserDelegationNonces { /** * @notice Set nonce. */ - function _setNonce(address delegator, uint256 nonce) internal { + function _setNonce(address signer, uint256 nonce) internal { bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = bytes32(uint256(uint160(delegator))); + _keyTuple[0] = bytes32(uint256(uint160(signer))); StoreCore.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((nonce)), _fieldLayout); } @@ -127,9 +127,9 @@ library UserDelegationNonces { /** * @notice Set nonce. */ - function set(address delegator, uint256 nonce) internal { + function set(address signer, uint256 nonce) internal { bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = bytes32(uint256(uint160(delegator))); + _keyTuple[0] = bytes32(uint256(uint160(signer))); StoreSwitch.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((nonce)), _fieldLayout); } @@ -137,9 +137,9 @@ library UserDelegationNonces { /** * @notice Set nonce. */ - function _set(address delegator, uint256 nonce) internal { + function _set(address signer, uint256 nonce) internal { bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = bytes32(uint256(uint160(delegator))); + _keyTuple[0] = bytes32(uint256(uint160(signer))); StoreCore.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((nonce)), _fieldLayout); } @@ -147,9 +147,9 @@ library UserDelegationNonces { /** * @notice Delete all data for given keys. */ - function deleteRecord(address delegator) internal { + function deleteRecord(address signer) internal { bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = bytes32(uint256(uint160(delegator))); + _keyTuple[0] = bytes32(uint256(uint160(signer))); StoreSwitch.deleteRecord(_tableId, _keyTuple); } @@ -157,9 +157,9 @@ library UserDelegationNonces { /** * @notice Delete all data for given keys. */ - function _deleteRecord(address delegator) internal { + function _deleteRecord(address signer) internal { bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = bytes32(uint256(uint160(delegator))); + _keyTuple[0] = bytes32(uint256(uint160(signer))); StoreCore.deleteRecord(_tableId, _keyTuple, _fieldLayout); } @@ -190,9 +190,9 @@ library UserDelegationNonces { /** * @notice Encode keys as a bytes32 array using this table's field layout. */ - function encodeKeyTuple(address delegator) internal pure returns (bytes32[] memory) { + function encodeKeyTuple(address signer) internal pure returns (bytes32[] memory) { bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = bytes32(uint256(uint160(delegator))); + _keyTuple[0] = bytes32(uint256(uint160(signer))); return _keyTuple; } diff --git a/packages/world-modules/test/DelegationWithSignatureModule.t.sol b/packages/world-modules/test/CallWithSignatureModule.t.sol similarity index 73% rename from packages/world-modules/test/DelegationWithSignatureModule.t.sol rename to packages/world-modules/test/CallWithSignatureModule.t.sol index 50e9ba06c1..ccabbe2ad3 100644 --- a/packages/world-modules/test/DelegationWithSignatureModule.t.sol +++ b/packages/world-modules/test/CallWithSignatureModule.t.sol @@ -14,20 +14,21 @@ import { System } from "@latticexyz/world/src/System.sol"; import { RESOURCE_SYSTEM } from "@latticexyz/world/src/worldResourceTypes.sol"; import { UNLIMITED_DELEGATION } from "@latticexyz/world/src/constants.sol"; import { ResourceId, WorldResourceIdLib, WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol"; +import { REGISTRATION_SYSTEM_ID } from "@latticexyz/world/src/modules/init/constants.sol"; import { createWorld } from "@latticexyz/world/test/createWorld.sol"; import { WorldTestSystem } from "@latticexyz/world/test/World.t.sol"; -import { Unstable_DelegationWithSignatureModule } from "../src/modules/delegation/Unstable_DelegationWithSignatureModule.sol"; -import { Unstable_DelegationWithSignatureSystem } from "../src/modules/delegation/Unstable_DelegationWithSignatureSystem.sol"; +import { Unstable_CallWithSignatureModule } from "../src/modules/delegation/Unstable_CallWithSignatureModule.sol"; +import { Unstable_CallWithSignatureSystem } from "../src/modules/delegation/Unstable_CallWithSignatureSystem.sol"; import { getSignedMessageHash } from "../src/modules/delegation/getSignedMessageHash.sol"; import { ECDSA } from "../src/modules/delegation/ECDSA.sol"; -contract Unstable_DelegationWithSignatureModuleTest is Test, GasReporter { +contract Unstable_CallWithSignatureModuleTest is Test, GasReporter { using WorldResourceIdInstance for ResourceId; IBaseWorld world; - Unstable_DelegationWithSignatureModule delegationWithSignatureModule = new Unstable_DelegationWithSignatureModule(); + Unstable_CallWithSignatureModule callWithSignatureModule = new Unstable_CallWithSignatureModule(); function setUp() public { world = createWorld(); @@ -36,7 +37,7 @@ contract Unstable_DelegationWithSignatureModuleTest is Test, GasReporter { function testInstallRoot() public { startGasReport("install delegation module"); - world.installRootModule(delegationWithSignatureModule, new bytes(0)); + world.installRootModule(callWithSignatureModule, new bytes(0)); endGasReport(); } @@ -51,32 +52,32 @@ contract Unstable_DelegationWithSignatureModuleTest is Test, GasReporter { world.registerNamespace(systemId.getNamespaceId()); world.registerSystem(systemId, system, true); - world.installRootModule(delegationWithSignatureModule, new bytes(0)); + world.installRootModule(callWithSignatureModule, new bytes(0)); // Register a limited delegation using signature (address delegator, uint256 delegatorPk) = makeAddrAndKey("delegator"); address delegatee = address(2); - bytes32 hash = getSignedMessageHash(delegatee, UNLIMITED_DELEGATION, new bytes(0), delegator, 0, address(world)); + bytes memory callData = abi.encodeCall(world.registerDelegation, (delegatee, UNLIMITED_DELEGATION, new bytes(0))); + + bytes32 hash = getSignedMessageHash(delegator, REGISTRATION_SYSTEM_ID, callData, 0, address(world)); (uint8 v, bytes32 r, bytes32 s) = vm.sign(delegatorPk, hash); bytes memory signature = abi.encodePacked(r, s, v); // Attempt to register a limited delegation using an empty signature vm.expectRevert(abi.encodeWithSelector(ECDSA.ECDSAInvalidSignatureLength.selector, 0)); - Unstable_DelegationWithSignatureSystem(address(world)).registerDelegationWithSignature( - delegatee, - UNLIMITED_DELEGATION, - new bytes(0), + Unstable_CallWithSignatureSystem(address(world)).callWithSignature( delegator, + REGISTRATION_SYSTEM_ID, + callData, new bytes(0) ); startGasReport("register an unlimited delegation with signature"); - Unstable_DelegationWithSignatureSystem(address(world)).registerDelegationWithSignature( - delegatee, - UNLIMITED_DELEGATION, - new bytes(0), + Unstable_CallWithSignatureSystem(address(world)).callWithSignature( delegator, + REGISTRATION_SYSTEM_ID, + callData, signature ); endGasReport(); @@ -101,15 +102,14 @@ contract Unstable_DelegationWithSignatureModuleTest is Test, GasReporter { // Attempt to register a limited delegation using an old signature vm.expectRevert( abi.encodeWithSelector( - Unstable_DelegationWithSignatureSystem.InvalidSignature.selector, - 0x1Ee32CcbA4C692C5b89e0858F2C0779C8a3D98AB + Unstable_CallWithSignatureSystem.InvalidSignature.selector, + 0x824E5E0aF3eA693b906527Dc41E4a29F037d515b ) ); - Unstable_DelegationWithSignatureSystem(address(world)).registerDelegationWithSignature( - delegatee, - UNLIMITED_DELEGATION, - new bytes(0), + Unstable_CallWithSignatureSystem(address(world)).callWithSignature( delegator, + REGISTRATION_SYSTEM_ID, + callData, signature ); @@ -119,15 +119,14 @@ contract Unstable_DelegationWithSignatureModuleTest is Test, GasReporter { world.callFrom(delegator, systemId, abi.encodeCall(WorldTestSystem.msgSender, ())); // Register a limited delegation using a new signature - hash = getSignedMessageHash(delegatee, UNLIMITED_DELEGATION, new bytes(0), delegator, 1, address(world)); + hash = getSignedMessageHash(delegator, REGISTRATION_SYSTEM_ID, callData, 1, address(world)); (v, r, s) = vm.sign(delegatorPk, hash); signature = abi.encodePacked(r, s, v); - Unstable_DelegationWithSignatureSystem(address(world)).registerDelegationWithSignature( - delegatee, - UNLIMITED_DELEGATION, - new bytes(0), + Unstable_CallWithSignatureSystem(address(world)).callWithSignature( delegator, + REGISTRATION_SYSTEM_ID, + callData, signature ); diff --git a/packages/world/ts/actions/callFrom.ts b/packages/world/ts/actions/callFrom.ts index 95646b572c..7ee99401df 100644 --- a/packages/world/ts/actions/callFrom.ts +++ b/packages/world/ts/actions/callFrom.ts @@ -61,7 +61,7 @@ export function callFrom( writeArgs.address !== params.worldAddress || writeArgs.functionName === "call" || writeArgs.functionName === "callFrom" || - writeArgs.functionName === "registerDelegationWithSignature" + writeArgs.functionName === "callWithSignature" ) { return getAction(client, writeContract, "writeContract")(writeArgs); } diff --git a/packages/world/ts/callWithSignatureTypes.ts b/packages/world/ts/callWithSignatureTypes.ts new file mode 100644 index 0000000000..6c16828081 --- /dev/null +++ b/packages/world/ts/callWithSignatureTypes.ts @@ -0,0 +1,10 @@ +// Follows https://viem.sh/docs/actions/wallet/signTypedData#usage + +export const callWithSignatureTypes = { + Call: [ + { name: "signer", type: "address" }, + { name: "systemId", type: "bytes32" }, + { name: "callData", type: "bytes" }, + { name: "nonce", type: "uint256" }, + ], +} as const; diff --git a/packages/world/ts/delegationWithSignatureTypes.ts b/packages/world/ts/delegationWithSignatureTypes.ts deleted file mode 100644 index f61d7c0d52..0000000000 --- a/packages/world/ts/delegationWithSignatureTypes.ts +++ /dev/null @@ -1,11 +0,0 @@ -// Follows https://viem.sh/docs/actions/wallet/signTypedData#usage - -export const delegationWithSignatureTypes = { - Delegation: [ - { name: "delegatee", type: "address" }, - { name: "delegationControlId", type: "bytes32" }, - { name: "initCallData", type: "bytes" }, - { name: "delegator", type: "address" }, - { name: "nonce", type: "uint256" }, - ], -} as const; diff --git a/packages/world/ts/exports/internal.ts b/packages/world/ts/exports/internal.ts index 7657e8d688..ba1d0824cf 100644 --- a/packages/world/ts/exports/internal.ts +++ b/packages/world/ts/exports/internal.ts @@ -10,4 +10,4 @@ export * from "../encodeSystemCallsFrom"; export * from "../actions/callFrom"; -export * from "../delegationWithSignatureTypes"; +export * from "../callWithSignatureTypes";