From 081c396790a0b68d85ef7735f0e7e643b99721c3 Mon Sep 17 00:00:00 2001 From: alvarius Date: Fri, 5 Apr 2024 09:47:48 +0200 Subject: [PATCH] feat(world-modules): add `validateCallWithSignature` to `Unstable_CallWithSignatureModule` (#2614) --- .changeset/fresh-horses-type.md | 5 +++ packages/world-modules/gas-report.json | 2 +- .../IUnstable_CallWithSignatureSystem.sol | 2 -- .../IUnstable_CallWithSignatureErrors.sol | 9 ++++++ .../Unstable_CallWithSignatureSystem.sol | 20 +++--------- .../delegation/validateCallWithSignature.sol | 32 +++++++++++++++++++ .../test/CallWithSignatureModule.t.sol | 3 +- 7 files changed, 54 insertions(+), 19 deletions(-) create mode 100644 .changeset/fresh-horses-type.md create mode 100644 packages/world-modules/src/modules/delegation/IUnstable_CallWithSignatureErrors.sol create mode 100644 packages/world-modules/src/modules/delegation/validateCallWithSignature.sol diff --git a/.changeset/fresh-horses-type.md b/.changeset/fresh-horses-type.md new file mode 100644 index 0000000000..58302d6188 --- /dev/null +++ b/.changeset/fresh-horses-type.md @@ -0,0 +1,5 @@ +--- +"@latticexyz/world-modules": patch +--- + +Added `validateCallWithSignature` function to `Unstable_CallWithSignatureModule` to validate a signature without executing the call. diff --git a/packages/world-modules/gas-report.json b/packages/world-modules/gas-report.json index 79d3974954..54fa5bbf75 100644 --- a/packages/world-modules/gas-report.json +++ b/packages/world-modules/gas-report.json @@ -9,7 +9,7 @@ "file": "test/CallWithSignatureModule.t.sol", "test": "testRegisterDelegationWithSignature", "name": "register an unlimited delegation with signature", - "gasUsed": 133222 + "gasUsed": 133659 }, { "file": "test/ERC20.t.sol", diff --git a/packages/world-modules/src/interfaces/IUnstable_CallWithSignatureSystem.sol b/packages/world-modules/src/interfaces/IUnstable_CallWithSignatureSystem.sol index 11f0ab8371..035eb5306a 100644 --- a/packages/world-modules/src/interfaces/IUnstable_CallWithSignatureSystem.sol +++ b/packages/world-modules/src/interfaces/IUnstable_CallWithSignatureSystem.sol @@ -11,8 +11,6 @@ import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; * @dev This interface is automatically generated from the corresponding system contract. Do not edit manually. */ interface IUnstable_CallWithSignatureSystem { - error InvalidSignature(address signer); - function callWithSignature( address signer, ResourceId systemId, diff --git a/packages/world-modules/src/modules/delegation/IUnstable_CallWithSignatureErrors.sol b/packages/world-modules/src/modules/delegation/IUnstable_CallWithSignatureErrors.sol new file mode 100644 index 0000000000..5923338ca8 --- /dev/null +++ b/packages/world-modules/src/modules/delegation/IUnstable_CallWithSignatureErrors.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +interface IUnstable_CallWithSignatureErrors { + /** + * @dev Mismatched signature. + */ + error InvalidSignature(address signer); +} diff --git a/packages/world-modules/src/modules/delegation/Unstable_CallWithSignatureSystem.sol b/packages/world-modules/src/modules/delegation/Unstable_CallWithSignatureSystem.sol index 43fa0d12e1..f860f23049 100644 --- a/packages/world-modules/src/modules/delegation/Unstable_CallWithSignatureSystem.sol +++ b/packages/world-modules/src/modules/delegation/Unstable_CallWithSignatureSystem.sol @@ -9,13 +9,10 @@ import { createDelegation } from "@latticexyz/world/src/modules/init/implementat import { CallWithSignatureNonces } from "./tables/CallWithSignatureNonces.sol"; import { getSignedMessageHash } from "./getSignedMessageHash.sol"; import { ECDSA } from "./ECDSA.sol"; +import { validateCallWithSignature } from "./validateCallWithSignature.sol"; +import { IUnstable_CallWithSignatureErrors } from "./IUnstable_CallWithSignatureErrors.sol"; -contract Unstable_CallWithSignatureSystem is System { - /** - * @dev Mismatched signature. - */ - error InvalidSignature(address signer); - +contract Unstable_CallWithSignatureSystem is System, IUnstable_CallWithSignatureErrors { /** * @notice Calls a system with a given system ID using the given signature. * @param signer The address on whose behalf the system is called. @@ -30,16 +27,9 @@ contract Unstable_CallWithSignatureSystem is System { 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); - } + validateCallWithSignature(signer, systemId, callData, signature); - CallWithSignatureNonces.set(signer, nonce + 1); + CallWithSignatureNonces._set(signer, CallWithSignatureNonces._get(signer) + 1); return SystemCall.callWithHooksOrRevert(signer, systemId, callData, _msgValue()); } diff --git a/packages/world-modules/src/modules/delegation/validateCallWithSignature.sol b/packages/world-modules/src/modules/delegation/validateCallWithSignature.sol new file mode 100644 index 0000000000..6156ee7914 --- /dev/null +++ b/packages/world-modules/src/modules/delegation/validateCallWithSignature.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; +import { ResourceId } from "@latticexyz/world/src/WorldResourceId.sol"; +import { WorldContextConsumerLib } from "@latticexyz/world/src/WorldContext.sol"; +import { CallWithSignatureNonces } from "./tables/CallWithSignatureNonces.sol"; +import { getSignedMessageHash } from "./getSignedMessageHash.sol"; +import { ECDSA } from "./ECDSA.sol"; +import { IUnstable_CallWithSignatureErrors } from "./IUnstable_CallWithSignatureErrors.sol"; + +/** + * @notice Verifies the given system call corresponds to 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. + * @dev Reverts with InvalidSignature(recoveredSigner) if the signature is invalid. + */ +function validateCallWithSignature( + address signer, + ResourceId systemId, + bytes memory callData, + bytes memory signature +) view { + uint256 nonce = CallWithSignatureNonces._get(signer); + bytes32 hash = getSignedMessageHash(signer, systemId, callData, nonce, WorldContextConsumerLib._world()); + + // If the message was not signed by the delegator or is invalid, revert + address recoveredSigner = ECDSA.recover(hash, signature); + if (recoveredSigner != signer) { + revert IUnstable_CallWithSignatureErrors.InvalidSignature(recoveredSigner); + } +} diff --git a/packages/world-modules/test/CallWithSignatureModule.t.sol b/packages/world-modules/test/CallWithSignatureModule.t.sol index ccabbe2ad3..70c7bfb6a6 100644 --- a/packages/world-modules/test/CallWithSignatureModule.t.sol +++ b/packages/world-modules/test/CallWithSignatureModule.t.sol @@ -21,6 +21,7 @@ import { WorldTestSystem } from "@latticexyz/world/test/World.t.sol"; import { Unstable_CallWithSignatureModule } from "../src/modules/delegation/Unstable_CallWithSignatureModule.sol"; import { Unstable_CallWithSignatureSystem } from "../src/modules/delegation/Unstable_CallWithSignatureSystem.sol"; +import { IUnstable_CallWithSignatureErrors } from "../src/modules/delegation/IUnstable_CallWithSignatureErrors.sol"; import { getSignedMessageHash } from "../src/modules/delegation/getSignedMessageHash.sol"; import { ECDSA } from "../src/modules/delegation/ECDSA.sol"; @@ -102,7 +103,7 @@ contract Unstable_CallWithSignatureModuleTest is Test, GasReporter { // Attempt to register a limited delegation using an old signature vm.expectRevert( abi.encodeWithSelector( - Unstable_CallWithSignatureSystem.InvalidSignature.selector, + IUnstable_CallWithSignatureErrors.InvalidSignature.selector, 0x824E5E0aF3eA693b906527Dc41E4a29F037d515b ) );