From bf9e53acb20d58efdd483e8ebf41ded8c96be43b Mon Sep 17 00:00:00 2001 From: Adam Egyed <5456061+adamegyed@users.noreply.github.com> Date: Sun, 24 Nov 2024 15:24:37 -0800 Subject: [PATCH] fix: [CL ALCHEMY-003] validation function uniqueness in nonce (#293) --- gas-snapshots/ModularAccount.json | 28 +- gas-snapshots/SemiModularAccount.json | 26 +- gas/modular-account/ModularAccount.gas.t.sol | 43 +-- .../SemiModularAccount.gas.t.sol | 43 +-- src/account/AccountStorage.sol | 7 +- src/account/ModularAccountBase.sol | 188 +++++----- src/account/ModularAccountView.sol | 4 +- src/account/ModuleManagerInternals.sol | 76 ++-- src/account/SemiModularAccountBase.sol | 12 +- src/helpers/Constants.sol | 5 + src/libraries/ValidationLocatorLib.sol | 335 ++++++++++++++++++ test/account/DeferredAction.t.sol | 10 +- test/account/DeferredValidation.t.sol | 44 +-- test/account/DirectCallsFromModule.t.sol | 6 +- test/account/GlobalValidationTest.t.sol | 11 +- test/account/HookOrdering.t.sol | 24 +- test/account/ModularAccount.t.sol | 43 +-- test/account/MultiValidation.t.sol | 37 +- test/account/PHCallBuffers.t.sol | 19 +- test/account/PerHookData.t.sol | 36 +- test/account/PostHookData.t.sol | 11 +- test/account/RTCallBuffer.t.sol | 24 +- test/account/SMASpecific.t.sol | 7 +- test/account/SelfCallAuthorization.t.sol | 5 +- test/account/SigCallBuffer.t.sol | 17 +- test/account/UOCallBuffer.t.sol | 45 +-- test/account/UpgradeToSma.t.sol | 37 +- test/account/ValidationIntersection.t.sol | 46 +-- test/libraries/ValidationLocatorLib.t.sol | 158 +++++++++ test/mocks/modules/ValidationModuleMocks.sol | 7 +- test/modules/AllowlistERC20TokenLimit.t.sol | 4 +- test/modules/AllowlistModule.t.sol | 14 +- test/modules/NativeTokenLimitModule.t.sol | 72 ++-- test/modules/PaymasterGuardModule.t.sol | 14 +- test/modules/TimeRangeModule.t.sol | 21 +- test/modules/WebAuthnValidationModule.t.sol | 4 +- test/utils/AccountTestBase.sol | 34 +- test/utils/ModuleSignatureUtils.sol | 113 ++++-- 38 files changed, 1075 insertions(+), 555 deletions(-) create mode 100644 src/libraries/ValidationLocatorLib.sol create mode 100644 test/libraries/ValidationLocatorLib.t.sol diff --git a/gas-snapshots/ModularAccount.json b/gas-snapshots/ModularAccount.json index af46da78..1dadd657 100644 --- a/gas-snapshots/ModularAccount.json +++ b/gas-snapshots/ModularAccount.json @@ -1,16 +1,16 @@ { - "Runtime_AccountCreation": "176129", - "Runtime_BatchTransfers": "93000", - "Runtime_Erc20Transfer": "78454", - "Runtime_InstallSessionKey_Case1": "423213", - "Runtime_NativeTransfer": "54603", - "Runtime_UseSessionKey_Case1_Counter": "78653", - "Runtime_UseSessionKey_Case1_Token": "111966", - "UserOp_BatchTransfers": "198311", - "UserOp_Erc20Transfer": "185319", - "UserOp_InstallSessionKey_Case1": "531396", - "UserOp_NativeTransfer": "161576", - "UserOp_UseSessionKey_Case1_Counter": "195221", - "UserOp_UseSessionKey_Case1_Token": "226205", - "UserOp_deferredValidation": "258068" + "Runtime_AccountCreation": "176376", + "Runtime_BatchTransfers": "92817", + "Runtime_Erc20Transfer": "78271", + "Runtime_InstallSessionKey_Case1": "423293", + "Runtime_NativeTransfer": "54420", + "Runtime_UseSessionKey_Case1_Counter": "78542", + "Runtime_UseSessionKey_Case1_Token": "111855", + "UserOp_BatchTransfers": "197788", + "UserOp_Erc20Transfer": "184784", + "UserOp_InstallSessionKey_Case1": "531124", + "UserOp_NativeTransfer": "161029", + "UserOp_UseSessionKey_Case1_Counter": "194521", + "UserOp_UseSessionKey_Case1_Token": "225505", + "UserOp_deferredValidation": "257223" } \ No newline at end of file diff --git a/gas-snapshots/SemiModularAccount.json b/gas-snapshots/SemiModularAccount.json index c301d68d..3a2243f0 100644 --- a/gas-snapshots/SemiModularAccount.json +++ b/gas-snapshots/SemiModularAccount.json @@ -1,16 +1,16 @@ { "Runtime_AccountCreation": "97767", - "Runtime_BatchTransfers": "88986", - "Runtime_Erc20Transfer": "74488", - "Runtime_InstallSessionKey_Case1": "421765", - "Runtime_NativeTransfer": "50647", - "Runtime_UseSessionKey_Case1_Counter": "79003", - "Runtime_UseSessionKey_Case1_Token": "112316", - "UserOp_BatchTransfers": "193491", - "UserOp_Erc20Transfer": "180546", - "UserOp_InstallSessionKey_Case1": "528931", - "UserOp_NativeTransfer": "156833", - "UserOp_UseSessionKey_Case1_Counter": "195473", - "UserOp_UseSessionKey_Case1_Token": "226457", - "UserOp_deferredValidation": "254119" + "Runtime_BatchTransfers": "89019", + "Runtime_Erc20Transfer": "74521", + "Runtime_InstallSessionKey_Case1": "422024", + "Runtime_NativeTransfer": "50680", + "Runtime_UseSessionKey_Case1_Counter": "78809", + "Runtime_UseSessionKey_Case1_Token": "112122", + "UserOp_BatchTransfers": "193097", + "UserOp_Erc20Transfer": "180176", + "UserOp_InstallSessionKey_Case1": "528787", + "UserOp_NativeTransfer": "156451", + "UserOp_UseSessionKey_Case1_Counter": "194821", + "UserOp_UseSessionKey_Case1_Token": "225805", + "UserOp_deferredValidation": "253169" } \ No newline at end of file diff --git a/gas/modular-account/ModularAccount.gas.t.sol b/gas/modular-account/ModularAccount.gas.t.sol index 2fa6b7d1..490e991d 100644 --- a/gas/modular-account/ModularAccount.gas.t.sol +++ b/gas/modular-account/ModularAccount.gas.t.sol @@ -1,9 +1,8 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.26; -import {ModuleEntity} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol"; - import {Call} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol"; +import {ModuleEntity, ModuleEntityLib} from "@erc6900/reference-implementation/libraries/ModuleEntityLib.sol"; import { ValidationConfig, ValidationConfigLib @@ -81,7 +80,7 @@ contract ModularAccountGasTest is ModularAccountBenchmarkBase("ModularAccount") PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(signerValidation, GLOBAL_V, 0), initCode: "", callData: abi.encodeCall(ModularAccountBase.execute, (recipient, 0.1 ether, "")), // don't over-estimate by a lot here, otherwise a fee is assessed. @@ -94,8 +93,7 @@ contract ModularAccountGasTest is ModularAccountBenchmarkBase("ModularAccount") bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, MessageHashUtils.toEthSignedMessageHash(userOpHash)); - userOp.signature = - _encodeSignature(signerValidation, GLOBAL_VALIDATION, abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); + userOp.signature = _encodeSignature(abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); uint256 gasUsed = _userOpBenchmark(userOp); @@ -138,7 +136,7 @@ contract ModularAccountGasTest is ModularAccountBenchmarkBase("ModularAccount") PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(signerValidation, GLOBAL_V, 0), initCode: "", callData: abi.encodeCall( ModularAccountBase.execute, @@ -154,8 +152,7 @@ contract ModularAccountGasTest is ModularAccountBenchmarkBase("ModularAccount") bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, MessageHashUtils.toEthSignedMessageHash(userOpHash)); - userOp.signature = - _encodeSignature(signerValidation, GLOBAL_VALIDATION, abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); + userOp.signature = _encodeSignature(abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); uint256 gasUsed = _userOpBenchmark(userOp); @@ -214,7 +211,7 @@ contract ModularAccountGasTest is ModularAccountBenchmarkBase("ModularAccount") PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(signerValidation, GLOBAL_V, 0), initCode: "", callData: abi.encodeCall(ModularAccountBase.executeBatch, (calls)), // don't over-estimate by a lot here, otherwise a fee is assessed. @@ -227,8 +224,7 @@ contract ModularAccountGasTest is ModularAccountBenchmarkBase("ModularAccount") bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, MessageHashUtils.toEthSignedMessageHash(userOpHash)); - userOp.signature = - _encodeSignature(signerValidation, GLOBAL_VALIDATION, abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); + userOp.signature = _encodeSignature(abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); uint256 gasUsed = _userOpBenchmark(userOp); @@ -244,12 +240,14 @@ contract ModularAccountGasTest is ModularAccountBenchmarkBase("ModularAccount") vm.deal(address(account1), 1 ether); SingleSignerValidationModule newValidationModule = _deploySingleSignerValidationModule(); - uint32 newEntityId = 0; + uint32 newEntityId = 1; (address owner2, uint256 owner2Key) = makeAddrAndKey("owner2"); + ModuleEntity newUOValidationEntity = ModuleEntityLib.pack(address(newValidationModule), newEntityId); + PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonceDefAction(newUOValidationEntity, GLOBAL_V, 0), initCode: "", callData: abi.encodeCall(ModularAccountBase.execute, (recipient, 0.1 ether, "")), // don't over-estimate by a lot here, otherwise a fee is assessed. @@ -286,8 +284,6 @@ contract ModularAccountGasTest is ModularAccountBenchmarkBase("ModularAccount") ); userOp.signature = _encodeDeferredInstallUOSignature( - ValidationConfigLib.moduleEntity(newUOValidation), - GLOBAL_VALIDATION, _packDeferredInstallData( deferredInstallNonce, deferredInstallDeadline, @@ -329,7 +325,7 @@ contract ModularAccountGasTest is ModularAccountBenchmarkBase("ModularAccount") PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(signerValidation, GLOBAL_V, 0), initCode: "", callData: _getInstallDataSessionKeyCase1(), // don't over-estimate by a lot here, otherwise a fee is assessed. @@ -342,8 +338,7 @@ contract ModularAccountGasTest is ModularAccountBenchmarkBase("ModularAccount") bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, MessageHashUtils.toEthSignedMessageHash(userOpHash)); - userOp.signature = - _encodeSignature(signerValidation, GLOBAL_VALIDATION, abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); + userOp.signature = _encodeSignature(abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); uint256 gasUsed = _userOpBenchmark(userOp); @@ -392,7 +387,7 @@ contract ModularAccountGasTest is ModularAccountBenchmarkBase("ModularAccount") PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(sessionKeyValidation, SELECTOR_ASSOCIATED_V, 0), initCode: "", callData: abi.encodePacked( ModularAccountBase.executeUserOp.selector, @@ -411,9 +406,7 @@ contract ModularAccountGasTest is ModularAccountBenchmarkBase("ModularAccount") bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(sessionSigner1Key, MessageHashUtils.toEthSignedMessageHash(userOpHash)); - userOp.signature = _encodeSignature( - sessionKeyValidation, SELECTOR_ASSOCIATED_VALIDATION, abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v) - ); + userOp.signature = _encodeSignature(abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); uint256 gasUsed = _userOpBenchmark(userOp); @@ -466,7 +459,7 @@ contract ModularAccountGasTest is ModularAccountBenchmarkBase("ModularAccount") PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(sessionKeyValidation, SELECTOR_ASSOCIATED_V, 0), initCode: "", callData: abi.encodePacked( ModularAccountBase.executeUserOp.selector, @@ -486,9 +479,7 @@ contract ModularAccountGasTest is ModularAccountBenchmarkBase("ModularAccount") bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(sessionSigner1Key, MessageHashUtils.toEthSignedMessageHash(userOpHash)); - userOp.signature = _encodeSignature( - sessionKeyValidation, SELECTOR_ASSOCIATED_VALIDATION, abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v) - ); + userOp.signature = _encodeSignature(abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); uint256 gasUsed = _userOpBenchmark(userOp); diff --git a/gas/modular-account/SemiModularAccount.gas.t.sol b/gas/modular-account/SemiModularAccount.gas.t.sol index 56293227..1972acfb 100644 --- a/gas/modular-account/SemiModularAccount.gas.t.sol +++ b/gas/modular-account/SemiModularAccount.gas.t.sol @@ -1,9 +1,8 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.26; -import {ModuleEntity} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol"; - import {Call} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol"; +import {ModuleEntity, ModuleEntityLib} from "@erc6900/reference-implementation/libraries/ModuleEntityLib.sol"; import { ValidationConfig, ValidationConfigLib @@ -75,7 +74,7 @@ contract ModularAccountGasTest is ModularAccountBenchmarkBase("SemiModularAccoun PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(signerValidation, GLOBAL_V, 0), initCode: "", callData: abi.encodeCall(ModularAccountBase.execute, (recipient, 0.1 ether, "")), // don't over-estimate by a lot here, otherwise a fee is assessed. @@ -88,8 +87,7 @@ contract ModularAccountGasTest is ModularAccountBenchmarkBase("SemiModularAccoun bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, MessageHashUtils.toEthSignedMessageHash(userOpHash)); - userOp.signature = - _encodeSignature(signerValidation, GLOBAL_VALIDATION, abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); + userOp.signature = _encodeSignature(abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); uint256 gasUsed = _userOpBenchmark(userOp); @@ -132,7 +130,7 @@ contract ModularAccountGasTest is ModularAccountBenchmarkBase("SemiModularAccoun PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(signerValidation, GLOBAL_V, 0), initCode: "", callData: abi.encodeCall( ModularAccountBase.execute, @@ -148,8 +146,7 @@ contract ModularAccountGasTest is ModularAccountBenchmarkBase("SemiModularAccoun bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, MessageHashUtils.toEthSignedMessageHash(userOpHash)); - userOp.signature = - _encodeSignature(signerValidation, GLOBAL_VALIDATION, abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); + userOp.signature = _encodeSignature(abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); uint256 gasUsed = _userOpBenchmark(userOp); @@ -208,7 +205,7 @@ contract ModularAccountGasTest is ModularAccountBenchmarkBase("SemiModularAccoun PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(signerValidation, GLOBAL_V, 0), initCode: "", callData: abi.encodeCall(ModularAccountBase.executeBatch, (calls)), // don't over-estimate by a lot here, otherwise a fee is assessed. @@ -221,8 +218,7 @@ contract ModularAccountGasTest is ModularAccountBenchmarkBase("SemiModularAccoun bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, MessageHashUtils.toEthSignedMessageHash(userOpHash)); - userOp.signature = - _encodeSignature(signerValidation, GLOBAL_VALIDATION, abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); + userOp.signature = _encodeSignature(abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); uint256 gasUsed = _userOpBenchmark(userOp); @@ -238,12 +234,14 @@ contract ModularAccountGasTest is ModularAccountBenchmarkBase("SemiModularAccoun vm.deal(address(account1), 1 ether); SingleSignerValidationModule newValidationModule = _deploySingleSignerValidationModule(); - uint32 newEntityId = 0; + uint32 newEntityId = 1; (address owner2, uint256 owner2Key) = makeAddrAndKey("owner2"); + ModuleEntity newUOValidationEntity = ModuleEntityLib.pack(address(newValidationModule), newEntityId); + PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonceDefAction(newUOValidationEntity, GLOBAL_V, 0), initCode: "", callData: abi.encodeCall(ModularAccountBase.execute, (recipient, 0.1 ether, "")), // don't over-estimate by a lot here, otherwise a fee is assessed. @@ -276,8 +274,6 @@ contract ModularAccountGasTest is ModularAccountBenchmarkBase("SemiModularAccoun bytes memory deferredValidationSig = _signRawHash(vm, owner1Key, digest); userOp.signature = _encodeDeferredInstallUOSignature( - ValidationConfigLib.moduleEntity(newUOValidation), - GLOBAL_VALIDATION, _packDeferredInstallData( deferredInstallNonce, deferredInstallDeadline, @@ -319,7 +315,7 @@ contract ModularAccountGasTest is ModularAccountBenchmarkBase("SemiModularAccoun PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(signerValidation, GLOBAL_V, 0), initCode: "", callData: _getInstallDataSessionKeyCase1(), // don't over-estimate by a lot here, otherwise a fee is assessed. @@ -332,8 +328,7 @@ contract ModularAccountGasTest is ModularAccountBenchmarkBase("SemiModularAccoun bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, MessageHashUtils.toEthSignedMessageHash(userOpHash)); - userOp.signature = - _encodeSignature(signerValidation, GLOBAL_VALIDATION, abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); + userOp.signature = _encodeSignature(abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); uint256 gasUsed = _userOpBenchmark(userOp); @@ -382,7 +377,7 @@ contract ModularAccountGasTest is ModularAccountBenchmarkBase("SemiModularAccoun PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(sessionKeyValidation, SELECTOR_ASSOCIATED_V, 0), initCode: "", callData: abi.encodePacked( ModularAccountBase.executeUserOp.selector, @@ -401,9 +396,7 @@ contract ModularAccountGasTest is ModularAccountBenchmarkBase("SemiModularAccoun bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(sessionSigner1Key, MessageHashUtils.toEthSignedMessageHash(userOpHash)); - userOp.signature = _encodeSignature( - sessionKeyValidation, SELECTOR_ASSOCIATED_VALIDATION, abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v) - ); + userOp.signature = _encodeSignature(abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); uint256 gasUsed = _userOpBenchmark(userOp); @@ -456,7 +449,7 @@ contract ModularAccountGasTest is ModularAccountBenchmarkBase("SemiModularAccoun PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(sessionKeyValidation, SELECTOR_ASSOCIATED_V, 0), initCode: "", callData: abi.encodePacked( ModularAccountBase.executeUserOp.selector, @@ -476,9 +469,7 @@ contract ModularAccountGasTest is ModularAccountBenchmarkBase("SemiModularAccoun bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(sessionSigner1Key, MessageHashUtils.toEthSignedMessageHash(userOpHash)); - userOp.signature = _encodeSignature( - sessionKeyValidation, SELECTOR_ASSOCIATED_VALIDATION, abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v) - ); + userOp.signature = _encodeSignature(abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); uint256 gasUsed = _userOpBenchmark(userOp); diff --git a/src/account/AccountStorage.sol b/src/account/AccountStorage.sol index 86b886c2..51bce5fa 100644 --- a/src/account/AccountStorage.sol +++ b/src/account/AccountStorage.sol @@ -1,9 +1,10 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.26; -import {HookConfig, ModuleEntity} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol"; +import {HookConfig} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol"; import {LinkedListSet, SetValue} from "../libraries/LinkedListSetLib.sol"; +import {ValidationLookupKey} from "../libraries/ValidationLocatorLib.sol"; // ERC-7201 derived storage slot. // keccak256(abi.encode(uint256(keccak256("Alchemy.ModularAccount.Storage_V2")) - 1)) & ~bytes32(uint256(0xff)) @@ -27,6 +28,8 @@ struct ExecutionStorage { /// @notice Represents data associated with a specific validation function. struct ValidationStorage { + // The address of the validation module. + address module; // Whether or not this validation can be used as a global validation function. bool isGlobal; // Whether or not this validation is allowed to validate ERC-1271 signatures. @@ -55,7 +58,7 @@ struct AccountStorage { // Execution functions and their associated functions. mapping(bytes4 selector => ExecutionStorage) executionStorage; // Validation functions and their associated functions. - mapping(ModuleEntity validationFunction => ValidationStorage) validationStorage; + mapping(ValidationLookupKey lookupKey => ValidationStorage) validationStorage; // Module-defined ERC-165 interfaces installed on the account. mapping(bytes4 => uint256) supportedIfaces; // Nonce usage state for deferred actions. diff --git a/src/account/ModularAccountBase.sol b/src/account/ModularAccountBase.sol index 330f2e46..26314432 100644 --- a/src/account/ModularAccountBase.sol +++ b/src/account/ModularAccountBase.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.26; -import {DIRECT_CALL_VALIDATION_ENTITYID} from "@erc6900/reference-implementation/helpers/Constants.sol"; import {getEmptyCalldataSlice} from "@erc6900/reference-implementation/helpers/EmptyCalldataSlice.sol"; import {ExecutionManifest} from "@erc6900/reference-implementation/interfaces/IExecutionModule.sol"; import { @@ -39,8 +38,11 @@ import { } from "../libraries/ExecutionLib.sol"; import {LinkedListSet, LinkedListSetLib} from "../libraries/LinkedListSetLib.sol"; import {MemManagementLib, MemSnapshot} from "../libraries/MemManagementLib.sol"; +import { + ValidationLocator, ValidationLocatorLib, ValidationLookupKey +} from "../libraries/ValidationLocatorLib.sol"; import {AccountBase} from "./AccountBase.sol"; -import {AccountStorage, getAccountStorage, toSetValue} from "./AccountStorage.sol"; +import {AccountStorage, ValidationStorage, getAccountStorage, toSetValue} from "./AccountStorage.sol"; import {AccountStorageInitializable} from "./AccountStorageInitializable.sol"; import {ModularAccountView} from "./ModularAccountView.sol"; import {ModuleManagerInternals} from "./ModuleManagerInternals.sol"; @@ -68,6 +70,8 @@ abstract contract ModularAccountBase is using ValidationConfigLib for ValidationConfig; using HookConfigLib for HookConfig; using SparseCalldataSegmentLib for bytes; + // temp, remove after updating validation selection + using ValidationLocatorLib for ModuleEntity; enum ValidationCheckingType { GLOBAL, @@ -79,9 +83,9 @@ abstract contract ModularAccountBase is bytes32 internal constant _DOMAIN_SEPARATOR_TYPEHASH = 0x47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218; - // keccak256("DeferredAction(uint256 nonce,uint48 deadline,bytes25 validationFunction,bytes call)") + // keccak256("DeferredAction(uint256 nonce,uint48 deadline,uint168 validationLocator,bytes call)") bytes32 internal constant _DEFERRED_ACTION_TYPEHASH = - 0xa17377cb6cfc0b2dd5d19181605c29f5d15050cfef9781c26049921c79e525d2; + 0xa0a7682d80b18373e0ec8549850bc960f46562e16ae4ddee86ab9a3cc3d3a07c; // As per the EIP-165 spec, no interface should ever match 0xffffffff bytes4 internal constant _INTERFACE_ID_INVALID = 0xffffffff; @@ -90,9 +94,6 @@ abstract contract ModularAccountBase is bytes4 internal constant _1271_MAGIC_VALUE = 0x1626ba7e; bytes4 internal constant _1271_INVALID = 0xffffffff; - uint8 internal constant _IS_GLOBAL_VALIDATION_BIT = 1; - uint8 internal constant _HAS_DEFERRED_ACTION_BIT = 2; - address internal immutable _EXECUTION_INSTALL_DELEGATE; event DeferredActionNonceInvalidated(uint256 nonce); @@ -190,10 +191,10 @@ abstract contract ModularAccountBase is function executeUserOp(PackedUserOperation calldata userOp, bytes32) external override { _requireFromEntryPoint(); - ModuleEntity userOpValidationFunction = ModuleEntity.wrap(bytes24(userOp.signature[:24])); + ValidationLocator locator = ValidationLocatorLib.loadFromNonce(userOp.nonce); HookConfig[] memory validationAssocExecHooks = - MemManagementLib.loadExecHooks(getAccountStorage().validationStorage[userOpValidationFunction]); + MemManagementLib.loadExecHooks(getAccountStorage().validationStorage[locator.lookupKey()]); PHCallBuffer callBuffer; if (validationAssocExecHooks.length > 0) { @@ -259,22 +260,23 @@ abstract contract ModularAccountBase is payable returns (bytes memory) { - // Revert if the provided `authorization` is less than 24 bytes long, rather than right-padding. - ModuleEntity runtimeValidationFunction = ModuleEntity.wrap(bytes24(authorization[:24])); + (ValidationLocator locator, bytes calldata authorizationData) = + ValidationLocatorLib.loadFromSignature(authorization); + + ValidationStorage storage _validationStorage = getAccountStorage().validationStorage[locator.lookupKey()]; // Check if the runtime validation function is allowed to be called _checkIfValidationAppliesCallData( data, - runtimeValidationFunction, + locator.lookupKey(), // Unfortunately, have to avoid declaring a `bool isGlobalValidation` to avoid stack too deep issues. - uint8(authorization[24]) == 1 ? ValidationCheckingType.GLOBAL : ValidationCheckingType.SELECTOR + locator.isGlobal() ? ValidationCheckingType.GLOBAL : ValidationCheckingType.SELECTOR ); - RTCallBuffer rtCallBuffer = _doRuntimeValidation(runtimeValidationFunction, data, authorization[25:]); + RTCallBuffer rtCallBuffer = _doRuntimeValidation(locator, data, authorizationData); // If runtime validation passes, run exec hooks associated with the validator - HookConfig[] memory validationAssocExecHooks = - MemManagementLib.loadExecHooks(getAccountStorage().validationStorage[runtimeValidationFunction]); + HookConfig[] memory validationAssocExecHooks = MemManagementLib.loadExecHooks(_validationStorage); PHCallBuffer phCallBuffer; if (validationAssocExecHooks.length > 0) { @@ -350,9 +352,10 @@ abstract contract ModularAccountBase is /// @inheritdoc IERC1271 function isValidSignature(bytes32 hash, bytes calldata signature) external view override returns (bytes4) { - ModuleEntity sigValidation = ModuleEntity.wrap(bytes24(signature)); - signature = signature[24:]; - return _isValidSignature(sigValidation, hash, signature); + (ValidationLocator locator, bytes calldata signatureRemainder) = + ValidationLocatorLib.loadFromSignature(signature); + + return _isValidSignature(locator, hash, signatureRemainder); } /// @inheritdoc IERC165 @@ -398,85 +401,73 @@ abstract contract ModularAccountBase is override returns (uint256 validationData) { - // Revert if the provided `authorization` less than 24 bytes long, rather than right-padding. - ModuleEntity validationFunction = ModuleEntity.wrap(bytes24(userOp.signature[:24])); + ValidationLocator locator = ValidationLocatorLib.loadFromNonce(userOp.nonce); - // Decode the 25th byte into an 8-bit bitmap (6 bits of which remain unused). - uint8 validationFlags = uint8(userOp.signature[24]); - bool isGlobalValidation = validationFlags & _IS_GLOBAL_VALIDATION_BIT != 0; - bool hasDeferredAction = validationFlags & _HAS_DEFERRED_ACTION_BIT != 0; - - // Assigned depending on whether the UO includes a deferred action or not. - bytes calldata userOpSignature; + bytes calldata userOpSignature = userOp.signature; /// The calldata layout is unique for deferred validation installation. - /// Byte indices are [inclusive, exclusive] - /// [25:29] : uint32, encodedDatalength. - /// [29:(29 + encodedDatalength)] : bytes, abi-encoded deferred action data. - /// [(29 + encodedDataLength):(33 + encodedDataLength)] : uint32, deferredActionSigLength. - /// [(33 + encodedDataLength):(33 + deferredActionSigLength + encodedDataLength)] : bytes, + /// Byte indices are [inclusive, exclusive] and relative to the start of the signature after the locator is + /// decoded and removed. + /// [0:4] : uint32, encodedDatalength. + /// [4:(4 + encodedDatalength)] : bytes, abi-encoded deferred action data. + /// [(4 + encodedDataLength):(8 + encodedDataLength)] : uint32, deferredActionSigLength. + /// [(8 + encodedDataLength):(8 + deferredActionSigLength + encodedDataLength)] : bytes, /// deferred action sig. This is the signature passed to the outer validation decoded earlier. - /// [(33 + deferredActionSigLength + encodedDataLength):] : bytes, userOpSignature. This is the + /// [(8 + deferredActionSigLength + encodedDataLength):] : bytes, userOpSignature. This is the /// signature passed to the inner validation. - if (hasDeferredAction) { + if (locator.hasDeferredAction()) { // Use inner validation as a 1271 validation for the deferred action, then use the outer // validation to validate the UO. // Get the length of the deferred action data. - uint256 encodedDataLength = uint32(bytes4(userOp.signature[25:29])); + uint256 encodedDataLength = uint32(bytes4(userOpSignature[:4])); // Load the pointer to the encoded data. - bytes calldata encodedData = userOp.signature[29:29 + encodedDataLength]; + bytes calldata encodedData = userOpSignature[4:4 + encodedDataLength]; // Get the deferred action signature length. uint256 deferredActionSigLength = - uint32(bytes4(userOp.signature[29 + encodedDataLength:33 + encodedDataLength])); - - // Update the UserOp signature to the remaining bytes. - userOpSignature = userOp.signature[33 + encodedDataLength + deferredActionSigLength:]; + uint32(bytes4(userOpSignature[4 + encodedDataLength:8 + encodedDataLength])); // Get the deferred installation signature, which is passed to the outer validation to handle the // deferred action. bytes calldata deferredActionSig = - userOp.signature[33 + encodedDataLength:33 + encodedDataLength + deferredActionSigLength]; + userOpSignature[8 + encodedDataLength:8 + encodedDataLength + deferredActionSigLength]; //Validate the signature. - // The struct must sign over the user op validation function, nonce, deadline, and the deferred action. - // Note that while the declared type of the UO validation is `ValidationConfig`, the flags are - // interpretted as validation selection flags, not validation installation flags. - bytes25 uoValidation = bytes25(userOp.signature[:25]); // Freeze the free-memory pointer, since we won't need to use anything from the deferred action // validation memory. MemSnapshot memSnapshot = MemManagementLib.freezeFMP(); - uint48 deadline = _handleDeferredAction(uoValidation, encodedData, deferredActionSig); + uint48 deadline = _handleDeferredAction(locator, encodedData, deferredActionSig); // Restore the free memory pointer. MemManagementLib.restoreFMP(memSnapshot); // Update the validation data with the deadline. validationData = uint256(deadline) << 160; - } else { - userOpSignature = userOp.signature[25:]; + + // Update the UserOp signature to the remaining bytes. + userOpSignature = userOpSignature[8 + encodedDataLength + deferredActionSigLength:]; } _checkIfValidationAppliesCallData( userOp.callData, - validationFunction, - isGlobalValidation ? ValidationCheckingType.GLOBAL : ValidationCheckingType.SELECTOR + locator.lookupKey(), + locator.isGlobal() ? ValidationCheckingType.GLOBAL : ValidationCheckingType.SELECTOR ); // Check if there are execution hooks associated with the validator, and revert if the call isn't to // `executeUserOp`. This check must be here because if context isn't passed, we can't tell in execution // which hooks should have ran. if ( - getAccountStorage().validationStorage[validationFunction].executionHookCount > 0 + getAccountStorage().validationStorage[locator.lookupKey()].executionHookCount > 0 && bytes4(userOp.callData[:4]) != this.executeUserOp.selector ) { revert RequireUserOperationContext(); } - uint256 userOpValidationRes = _doUserOpValidation(userOp, userOpHash, validationFunction, userOpSignature); + uint256 userOpValidationRes = _doUserOpValidation(userOp, userOpHash, locator, userOpSignature); // We only coalesce validations if the validation data from deferred installation is nonzero. if (validationData != 0) { @@ -487,14 +478,14 @@ abstract contract ModularAccountBase is } /// @return The deadline of the deferred action - function _handleDeferredAction( - bytes25 userOpValidationFunction, - bytes calldata encodedData, - bytes calldata sig - ) internal returns (uint48) { + function _handleDeferredAction(ValidationLocator locator, bytes calldata encodedData, bytes calldata sig) + internal + returns (uint48) + { // The deadline, nonce, inner validation, and deferred call selector are all at fixed positions in the // encodedData. + // todo: this will need to get encoded differently ValidationConfig defActionSigValidation = ValidationConfig.wrap(bytes25(encodedData[38:63])); bool isGlobalSigValidation = defActionSigValidation.isGlobal(); @@ -502,25 +493,28 @@ abstract contract ModularAccountBase is // Because this bypasses UO validation hooks, we require that the validation used does not include any // validation hooks. - if (getAccountStorage().validationStorage[defActionValidationModuleEntity].validationHookCount != 0) { + if ( + getAccountStorage().validationStorage[defActionValidationModuleEntity.moduleEntityToLookupKey()] + .validationHookCount != 0 + ) { revert DeferredValidationHasValidationHooks(); } // Check if the outer validation applies to the function call _checkIfValidationAppliesCallData( encodedData[63:], - defActionValidationModuleEntity, + defActionValidationModuleEntity.moduleEntityToLookupKey(), isGlobalSigValidation ? ValidationCheckingType.GLOBAL : ValidationCheckingType.SELECTOR ); // Handle the signature validation - uint48 deadline = _validateDeferredActionSignature( - encodedData, sig, userOpValidationFunction, defActionValidationModuleEntity - ); + uint48 deadline = + _validateDeferredActionSignature(encodedData, sig, locator, defActionValidationModuleEntity); // Run the validation associated execution hooks, allocating a call buffer as needed. - HookConfig[] memory validationAssocExecHooks = - MemManagementLib.loadExecHooks(getAccountStorage().validationStorage[defActionValidationModuleEntity]); + HookConfig[] memory validationAssocExecHooks = MemManagementLib.loadExecHooks( + getAccountStorage().validationStorage[defActionValidationModuleEntity.moduleEntityToLookupKey()] + ); PHCallBuffer callBuffer; if (validationAssocExecHooks.length > 0) { @@ -542,15 +536,18 @@ abstract contract ModularAccountBase is function _doUserOpValidation( PackedUserOperation calldata userOp, bytes32 userOpHash, - ModuleEntity userOpValidationFunction, + ValidationLocator validationLocator, bytes calldata signature ) internal returns (uint256) { - uint256 validationRes; + ValidationStorage storage _validationStorage = + getAccountStorage().validationStorage[validationLocator.lookupKey()]; // Do preUserOpValidation hooks - HookConfig[] memory preUserOpValidationHooks = - MemManagementLib.loadValidationHooks(getAccountStorage().validationStorage[userOpValidationFunction]); + HookConfig[] memory preUserOpValidationHooks = MemManagementLib.loadValidationHooks(_validationStorage); + + ModuleEntity userOpValidationFunction = validationLocator.moduleEntity(_validationStorage); + uint256 validationRes; UOCallBuffer userOpCallBuffer; if (!_validationIsNative(userOpValidationFunction) || preUserOpValidationHooks.length > 0) { userOpCallBuffer = ExecutionLib.allocateUserOpValidationCallBuffer(userOp, userOpHash); @@ -597,13 +594,17 @@ abstract contract ModularAccountBase is } function _doRuntimeValidation( - ModuleEntity runtimeValidationFunction, + ValidationLocator validationLocator, bytes calldata callData, bytes calldata authorizationData ) internal returns (RTCallBuffer) { + ValidationStorage storage _validationData = + getAccountStorage().validationStorage[validationLocator.lookupKey()]; + // run all preRuntimeValidation hooks - HookConfig[] memory preRuntimeValidationHooks = - MemManagementLib.loadValidationHooks(getAccountStorage().validationStorage[runtimeValidationFunction]); + HookConfig[] memory preRuntimeValidationHooks = MemManagementLib.loadValidationHooks(_validationData); + + ModuleEntity runtimeValidationFunction = validationLocator.moduleEntity(_validationData); RTCallBuffer callBuffer; if (!_validationIsNative(runtimeValidationFunction) || preRuntimeValidationHooks.length > 0) { @@ -661,8 +662,7 @@ abstract contract ModularAccountBase is msg.sender != address(_ENTRY_POINT) && msg.sender != address(this) && !_storage.executionStorage[msg.sig].skipRuntimeValidation ) { - ModuleEntity directCallValidationKey = - ModuleEntityLib.pack(msg.sender, DIRECT_CALL_VALIDATION_ENTITYID); + ValidationLookupKey directCallValidationKey = ValidationLocatorLib.directCallLookupKey(msg.sender); _checkIfValidationAppliesCallData(msg.data, directCallValidationKey, ValidationCheckingType.EITHER); @@ -717,7 +717,7 @@ abstract contract ModularAccountBase is ) internal virtual returns (uint256) { AccountStorage storage _storage = getAccountStorage(); - if (!_storage.validationStorage[userOpValidationFunction].isUserOpValidation) { + if (!_storage.validationStorage[userOpValidationFunction.moduleEntityToLookupKey()].isUserOpValidation) { revert UserOpValidationInvalid(userOpValidationFunction); } @@ -737,7 +737,7 @@ abstract contract ModularAccountBase is function _validateDeferredActionSignature( bytes calldata encodedData, bytes calldata signature, - bytes25 userOpValidationFunction, + ValidationLocator locator, ModuleEntity deferredSigValidationModuleEntity ) internal returns (uint48) { uint256 nonce = uint256(bytes32(encodedData[:32])); @@ -789,14 +789,8 @@ abstract contract ModularAccountBase is // Clear the upper bits of the deadline, in case the caller didn't. mstore(ptr, and(deadline, 0xffffffffffff)) ptr := add(ptr, 0x20) - // Clear the lower bits of the validation function, in case the caller didn't. - mstore( - ptr, - and( - userOpValidationFunction, - 0xffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000 - ) - ) + // Clear the upper bits of the validation locator, in case the caller didn't. + mstore(ptr, and(locator, 0xffffffffffffffffffffffffffffffffffffffffff)) ptr := add(ptr, 0x20) mstore(ptr, selfCallHash) @@ -823,13 +817,17 @@ abstract contract ModularAccountBase is return deadline; } - function _isValidSignature(ModuleEntity sigValidation, bytes32 hash, bytes calldata signature) + function _isValidSignature(ValidationLocator validationLocator, bytes32 hash, bytes calldata signature) internal view returns (bytes4) { - HookConfig[] memory preSignatureValidationHooks = - MemManagementLib.loadValidationHooks(getAccountStorage().validationStorage[sigValidation]); + ValidationStorage storage _validationStorage = + getAccountStorage().validationStorage[validationLocator.lookupKey()]; + + HookConfig[] memory preSignatureValidationHooks = MemManagementLib.loadValidationHooks(_validationStorage); + + ModuleEntity sigValidation = validationLocator.moduleEntity(_validationStorage); SigCallBuffer sigCallBuffer; if (!_validationIsNative(sigValidation) || preSignatureValidationHooks.length > 0) { @@ -864,7 +862,7 @@ abstract contract ModularAccountBase is (hash); // unused in ModularAccountBase, but used in SemiModularAccountBase AccountStorage storage _storage = getAccountStorage(); - if (!_storage.validationStorage[sigValidation].isSignatureValidation) { + if (!_storage.validationStorage[sigValidation.moduleEntityToLookupKey()].isSignatureValidation) { revert SignatureValidationInvalid(sigValidation); } @@ -874,15 +872,17 @@ abstract contract ModularAccountBase is return _1271_INVALID; } - function _isValidationGlobal(ModuleEntity validationFunction) internal view virtual returns (bool) { + function _isValidationGlobal(ValidationLookupKey validationFunction) internal view virtual returns (bool) { return getAccountStorage().validationStorage[validationFunction].isGlobal; } function _checkIfValidationAppliesCallData( bytes calldata callData, - ModuleEntity validationFunction, + ValidationLookupKey validationFunction, ValidationCheckingType checkingType ) internal view { + // address(4).staticcall(abi.encodePacked(validationFunction)); + if (callData.length < 4) { revert UnrecognizedFunction(bytes4(callData)); } @@ -919,7 +919,7 @@ abstract contract ModularAccountBase is /// @param checkingType The type of validation checking to perform. function _checkExecuteBatchValidationApplicability( bytes calldata callData, - ModuleEntity validationFunction, + ValidationLookupKey validationFunction, ValidationCheckingType checkingType ) internal view { // Equivalent to the following code, but without using memory. @@ -1073,7 +1073,7 @@ abstract contract ModularAccountBase is function _checkIfValidationAppliesSelector( bytes4 selector, - ModuleEntity validationFunction, + ValidationLookupKey validationFunction, ValidationCheckingType checkingType ) internal view { // Check that the provided validation function is applicable to the selector @@ -1096,7 +1096,7 @@ abstract contract ModularAccountBase is } } - function _globalValidationApplies(bytes4 selector, ModuleEntity validationFunction) + function _globalValidationApplies(bytes4 selector, ValidationLookupKey validationFunction) internal view returns (bool) @@ -1109,7 +1109,7 @@ abstract contract ModularAccountBase is || getAccountStorage().executionStorage[selector].allowGlobalValidation; } - function _selectorValidationApplies(bytes4 selector, ModuleEntity validationFunction) + function _selectorValidationApplies(bytes4 selector, ValidationLookupKey validationFunction) internal view returns (bool) diff --git a/src/account/ModularAccountView.sol b/src/account/ModularAccountView.sol index c7940e14..e0e31ab1 100644 --- a/src/account/ModularAccountView.sol +++ b/src/account/ModularAccountView.sol @@ -16,6 +16,7 @@ import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeab import {NativeFunctionDelegate} from "../helpers/NativeFunctionDelegate.sol"; import {IModularAccountBase} from "../interfaces/IModularAccountBase.sol"; import {MemManagementLib} from "../libraries/MemManagementLib.sol"; +import {ValidationLocatorLib} from "../libraries/ValidationLocatorLib.sol"; import {ExecutionStorage, ValidationStorage, getAccountStorage} from "./AccountStorage.sol"; /// @title Modular Account View @@ -59,7 +60,8 @@ abstract contract ModularAccountView is IModularAccountView { override returns (ValidationDataView memory data) { - ValidationStorage storage validationStorage = getAccountStorage().validationStorage[validationFunction]; + ValidationStorage storage validationStorage = + getAccountStorage().validationStorage[ValidationLocatorLib.moduleEntityToLookupKey(validationFunction)]; data.isGlobal = validationStorage.isGlobal; data.isSignatureValidation = validationStorage.isSignatureValidation; data.isUserOpValidation = validationStorage.isUserOpValidation; diff --git a/src/account/ModuleManagerInternals.sol b/src/account/ModuleManagerInternals.sol index a5f41aec..5c44a5e5 100644 --- a/src/account/ModuleManagerInternals.sol +++ b/src/account/ModuleManagerInternals.sol @@ -18,6 +18,7 @@ import {ValidationConfigLib} from "@erc6900/reference-implementation/libraries/V import {LinkedListSet, LinkedListSetLib} from "../libraries/LinkedListSetLib.sol"; import {MemManagementLib} from "../libraries/MemManagementLib.sol"; import {ModuleInstallCommonsLib} from "../libraries/ModuleInstallCommonsLib.sol"; +import {ValidationLocatorLib} from "../libraries/ValidationLocatorLib.sol"; import {ValidationStorage, getAccountStorage, toSetValue} from "./AccountStorage.sol"; /// @title Module Manager Internals @@ -32,15 +33,49 @@ abstract contract ModuleManagerInternals is IModularAccount { error ArrayLengthMismatch(); error PreValidationHookDuplicate(); + error ValidationEntityIdInUse(); error ValidationAlreadySet(bytes4 selector, ModuleEntity validationFunction); error ValidationAssocHookLimitExceeded(); - function _removeValidationFunction(ModuleEntity validationFunction) internal { - ValidationStorage storage _validationStorage = getAccountStorage().validationStorage[validationFunction]; + function _setValidationFunction( + ValidationStorage storage validationStorage, + ValidationConfig validationConfig, + bytes4[] calldata selectors + ) internal { + // To allow for flag updates and appending hooks and selectors, two cases should be considered: + // - stored module address is zero - store the new validation module address + // - stored module address already holds the address of the validation module being installed - update + // flags and selectors. + // If the stored module address does not match, revert, as the validation entity ID must be unique over the + // account. + + address storedAddress = validationStorage.module; + if (storedAddress == address(0)) { + validationStorage.module = validationConfig.module(); + } else if (storedAddress != validationConfig.module()) { + revert ValidationEntityIdInUse(); + } + + validationStorage.isGlobal = validationConfig.isGlobal(); + validationStorage.isSignatureValidation = validationConfig.isSignatureValidation(); + validationStorage.isUserOpValidation = validationConfig.isUserOpValidation(); + + uint256 length = selectors.length; + for (uint256 i = 0; i < length; ++i) { + bytes4 selector = selectors[i]; + if (!validationStorage.selectors.tryAdd(toSetValue(selector))) { + revert ValidationAlreadySet(selector, validationConfig.moduleEntity()); + } + } + } - _validationStorage.isGlobal = false; - _validationStorage.isSignatureValidation = false; - _validationStorage.isUserOpValidation = false; + function _removeValidationFunction(ValidationStorage storage validationStorage) internal { + validationStorage.module = address(0); + validationStorage.isGlobal = false; + validationStorage.isSignatureValidation = false; + validationStorage.isUserOpValidation = false; + validationStorage.validationHookCount = 0; + validationStorage.executionHookCount = 0; } function _installValidation( @@ -49,9 +84,10 @@ abstract contract ModuleManagerInternals is IModularAccount { bytes calldata installData, bytes[] calldata hooks ) internal { - ModuleEntity moduleEntity = validationConfig.moduleEntity(); + ValidationStorage storage _validationStorage = + getAccountStorage().validationStorage[ValidationLocatorLib.configToLookupKey(validationConfig)]; - ValidationStorage storage _validationStorage = getAccountStorage().validationStorage[moduleEntity]; + _setValidationFunction(_validationStorage, validationConfig, selectors); uint256 length = hooks.length; for (uint256 i = 0; i < length; ++i) { @@ -98,18 +134,6 @@ abstract contract ModuleManagerInternals is IModularAccount { } } - length = selectors.length; - for (uint256 i = 0; i < length; ++i) { - bytes4 selector = selectors[i]; - if (!_validationStorage.selectors.tryAdd(toSetValue(selector))) { - revert ValidationAlreadySet(selector, moduleEntity); - } - } - - _validationStorage.isGlobal = validationConfig.isGlobal(); - _validationStorage.isSignatureValidation = validationConfig.isSignatureValidation(); - _validationStorage.isUserOpValidation = validationConfig.isUserOpValidation(); - ModuleInstallCommonsLib.onInstall( validationConfig.module(), installData, type(IValidationModule).interfaceId ); @@ -121,11 +145,10 @@ abstract contract ModuleManagerInternals is IModularAccount { bytes calldata uninstallData, bytes[] calldata hookUninstallDatas ) internal { - ValidationStorage storage _validationStorage = getAccountStorage().validationStorage[validationFunction]; + ValidationStorage storage _validationStorage = + getAccountStorage().validationStorage[ValidationLocatorLib.moduleEntityToLookupKey(validationFunction)]; bool onUninstallSuccess = true; - _removeValidationFunction(validationFunction); - // Send `onUninstall` to hooks if (hookUninstallDatas.length > 0) { HookConfig[] memory execHooks = MemManagementLib.loadExecHooks(_validationStorage); @@ -157,16 +180,17 @@ abstract contract ModuleManagerInternals is IModularAccount { } } - // Clear all stored hooks - _validationStorage.validationHookCount = 0; + // Clear all stored hooks. The lengths of the hooks are cleared in `_removeValidationFunction`. _validationStorage.validationHooks.clear(); - - _validationStorage.executionHookCount = 0; _validationStorage.executionHooks.clear(); // Clear selectors _validationStorage.selectors.clear(); + // Clear validation function data. + // Must be done at the end, because the hook lengths are accessed in the loop above. + _removeValidationFunction(_validationStorage); + (address module, uint32 entityId) = ModuleEntityLib.unpack(validationFunction); onUninstallSuccess = onUninstallSuccess && ModuleInstallCommonsLib.onUninstall(module, uninstallData); diff --git a/src/account/SemiModularAccountBase.sol b/src/account/SemiModularAccountBase.sol index a01ca2e1..8a28661a 100644 --- a/src/account/SemiModularAccountBase.sol +++ b/src/account/SemiModularAccountBase.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.26; -import {DIRECT_CALL_VALIDATION_ENTITYID} from "@erc6900/reference-implementation/helpers/Constants.sol"; import {ModuleEntity} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol"; import {ModuleEntityLib} from "@erc6900/reference-implementation/libraries/ModuleEntityLib.sol"; import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; @@ -9,10 +8,11 @@ import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; -import {FALLBACK_VALIDATION} from "../helpers/Constants.sol"; +import {FALLBACK_VALIDATION, FALLBACK_VALIDATION_LOOKUP_KEY} from "../helpers/Constants.sol"; import {ExecutionInstallDelegate} from "../helpers/ExecutionInstallDelegate.sol"; import {SignatureType} from "../helpers/SignatureType.sol"; import {RTCallBuffer, SigCallBuffer, UOCallBuffer} from "../libraries/ExecutionLib.sol"; +import {ValidationLocatorLib, ValidationLookupKey} from "../libraries/ValidationLocatorLib.sol"; import {ModularAccountBase} from "./ModularAccountBase.sol"; /// @title Semi-Modular Account Base @@ -150,8 +150,9 @@ abstract contract SemiModularAccountBase is ModularAccountBase { return selector == this.updateFallbackSignerData.selector || super._globalValidationAllowed(selector); } - function _isValidationGlobal(ModuleEntity validationFunction) internal view override returns (bool) { - if (validationFunction.eq(FALLBACK_VALIDATION) || super._isValidationGlobal(validationFunction)) { + function _isValidationGlobal(ValidationLookupKey validationFunction) internal view override returns (bool) { + if (validationFunction.eq(FALLBACK_VALIDATION_LOOKUP_KEY) || super._isValidationGlobal(validationFunction)) + { return true; } @@ -167,8 +168,7 @@ abstract contract SemiModularAccountBase is ModularAccountBase { address fallbackSigner = _retrieveFallbackSignerUnchecked(smaStorage); // Compute the direct call validation key. - ModuleEntity fallbackDirectCallValidation = - ModuleEntityLib.pack(fallbackSigner, DIRECT_CALL_VALIDATION_ENTITYID); + ValidationLookupKey fallbackDirectCallValidation = ValidationLocatorLib.directCallLookupKey(fallbackSigner); // Return true if the validation function passed is the fallback direct call validation key, and the sender // is the fallback signer. This enforces that context is a diff --git a/src/helpers/Constants.sol b/src/helpers/Constants.sol index b324d0cf..79dd95be 100644 --- a/src/helpers/Constants.sol +++ b/src/helpers/Constants.sol @@ -3,5 +3,10 @@ pragma solidity ^0.8.26; import {ModuleEntity} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol"; +import {ValidationLookupKey} from "../libraries/ValidationLocatorLib.sol"; + // Magic value for the ModuleEntity of the fallback validation for SemiModularAccount. ModuleEntity constant FALLBACK_VALIDATION = ModuleEntity.wrap(bytes24(0)); + +// Magic value for the ValidationLookupKey of the fallback validation for SemiModularAccount. +ValidationLookupKey constant FALLBACK_VALIDATION_LOOKUP_KEY = ValidationLookupKey.wrap(uint168(0)); diff --git a/src/libraries/ValidationLocatorLib.sol b/src/libraries/ValidationLocatorLib.sol new file mode 100644 index 00000000..7ec041b7 --- /dev/null +++ b/src/libraries/ValidationLocatorLib.sol @@ -0,0 +1,335 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.26; + +import {DIRECT_CALL_VALIDATION_ENTITYID} from "@erc6900/reference-implementation/helpers/Constants.sol"; +import {ModuleEntity, ModuleEntityLib} from "@erc6900/reference-implementation/libraries/ModuleEntityLib.sol"; +import { + ValidationConfig, + ValidationConfigLib +} from "@erc6900/reference-implementation/libraries/ValidationConfigLib.sol"; + +import {ValidationStorage} from "../account/AccountStorage.sol"; + +// A type representing a validation lookup key and flags for validation options. +// The validation lookup key is a tagged union between a direct call validation address and a validation entity ID. +type ValidationLocator is uint168; +// Layout: +// Unused +// 0x0000000000000000000000__________________________________________ +// Either the direct call validation's address, or the entity ID for non-direct-call validation. +// 0x______________________AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA__ +// Validation options +// 0x______________________________________________________________BB + +// ValidationOptions layout: +// 0b00000___ // Unused +// 0b_____A__ // is direct call validation (union tag) +// 0b______B_ // has deferred action +// 0b_______C // is global validation + +// A type representing only the validation lookup key, with validation options masked out except for the +// direct call validation flag. +type ValidationLookupKey is uint168; + +library ValidationLocatorLib { + using ValidationConfigLib for ValidationConfig; + + uint8 internal constant _VALIDATION_TYPE_GLOBAL = 1; + uint8 internal constant _HAS_DEFERRED_ACTION = 2; + uint8 internal constant _IS_DIRECT_CALL_VALIDATION = 4; + + function moduleEntity(ValidationLocator locator, ValidationStorage storage validationStorage) + internal + view + returns (ModuleEntity result) + { + if (locator.isDirectCallValidation()) { + result = ModuleEntityLib.pack(locator.directCallAddress(), DIRECT_CALL_VALIDATION_ENTITYID); + } else { + result = ModuleEntityLib.pack(validationStorage.module, locator.entityId()); + } + } + + // User op nonce, 4337 mandated layout: + // 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA________________ // Parallel Nonce Key + // 0x________________________________________________BBBBBBBBBBBBBBBB // Sequential Nonce Key + + // User op nonce, Alchemy MA usage: + // With non-direct call validation + // 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA__________________________ // Parallel Nonce Key + // 0x______________________________________BBBBBBBB__________________ // Validation Entity ID + // 0x______________________________________________CC________________ // Options byte + // 0x________________________________________________BBBBBBBBBBBBBBBB // Sequential Nonce Key + + // With direct call validation + // 0xAAAAAA__________________________________________________________ // Parallel Nonce Key + // 0x______BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB__________________ // Caller address of direct-call + // validation + // 0x______________________________________________CC________________ // Options byte + // 0x________________________________________________BBBBBBBBBBBBBBBB // Sequential Nonce Key + function loadFromNonce(uint256 nonce) internal pure returns (ValidationLocator result) { + assembly ("memory-safe") { + nonce := shr(64, nonce) + let validationType := and(nonce, _IS_DIRECT_CALL_VALIDATION) + + switch validationType + case 0 { + // If not using direct call validation, the validation locator contains a 4-byte entity ID + // Mask it to the lower 5 bytes + result := and(nonce, 0xFFFFFFFFFF) + } + default { + // If using direct call validation, the validation locator contains a 20-byte address + // Mask it to the lower 21 bytes + result := and(nonce, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + } + } + } + + // executeRuntimeValidation authorization layout, and isValidSignature signature layout + // [1-byte options][4-byte validation id OR 20-byte address of direct call validation][remainder] + + // With non-direct call validation + // 0xAA______________ // Validation Type + // 0x__BBBBBBBB______ // Validation Entity ID + // 0x__________CCC... // Remainder + + // With direct call validation + // 0xAA______________________________________________ // Validation Type + // 0x__BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB______ // Caller address of direct-call validation + // 0x__________________________________________CCC... // Remainder + function loadFromSignature(bytes calldata signature) + internal + pure + returns (ValidationLocator result, bytes calldata remainder) + { + assembly ("memory-safe") { + // Regular validation requires at least 5 bytes. Direct call validation requires at least 21 bytes, + // checked later. + if lt(signature.length, 5) { revert(0, 0) } + + result := calldataload(signature.offset) + + let validationOptions := shr(248, result) + + switch and(validationOptions, _IS_DIRECT_CALL_VALIDATION) + case 0 { + // If not using direct call validation, the validation locator contains a 32-byte entity ID + + // Result contains: + // 0xAA______________________________________________________________ // Validation Type + // 0x__BBBBBBBB______________________________________________________ // Validation Entity ID + // 0x__________CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC // Remainder bits and/or + // zeros + + // We need to clear the upper byte by shifting left 1 bytes (8 bits), then shift right 28 bytes + // (224 bits), leaving only the entity ID. + result := shr(224, shl(8, result)) + // Next, we need to set the validation type, which is 0 in this branch + result := or(shl(8, result), validationOptions) + + // Advance the remainder by 5 bytes + remainder.offset := add(signature.offset, 5) + remainder.length := sub(signature.length, 5) + } + default { + // Direct call validation requires at least 21 bytes + if lt(signature.length, 21) { revert(0, 0) } + + // If using direct call validation, the validation locator contains a 20-byte address + // Result contains: + // 0xAA______________________________________________________________ // Validation Type + // 0x__BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB______________________ // Caller address of + // direct-call validation + // 0x__________________________________________CCCCCCCCCCCCCCCCCCCCCC // Remainder bits and/or + // zeros + + // So we need to clear the upper byte by shifting left 1 bytes (8 bits), then shift right 12 + // bytes (96 bits) to get the address. + result := shr(96, shl(8, result)) + // Next, we need to set the validation type + result := or(shl(8, result), validationOptions) + + // Advance the remainder by 21 bytes + remainder.offset := add(signature.offset, 21) + remainder.length := sub(signature.length, 21) + } + } + } + + // Only safe to call if the locator has been asserted to be a direct call validation. + function directCallAddress(ValidationLocator locator) internal pure returns (address result) { + assembly ("memory-safe") { + result := and(shr(8, locator), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + } + } + + // Only safe to call if the locator has been asserted to be a non-direct call validation. + function entityId(ValidationLocator locator) internal pure returns (uint32 result) { + assembly ("memory-safe") { + result := and(shr(8, locator), 0xFFFFFFFFFFFFFFFF) + } + } + + function isGlobal(ValidationLocator locator) internal pure returns (bool) { + return (ValidationLocator.unwrap(locator) & _VALIDATION_TYPE_GLOBAL) != 0; + } + + function hasDeferredAction(ValidationLocator locator) internal pure returns (bool) { + return (ValidationLocator.unwrap(locator) & _HAS_DEFERRED_ACTION) != 0; + } + + function isDirectCallValidation(ValidationLocator locator) internal pure returns (bool) { + return (ValidationLocator.unwrap(locator) & _IS_DIRECT_CALL_VALIDATION) != 0; + } + + function configToLookupKey(ValidationConfig validationConfig) + internal + pure + returns (ValidationLookupKey result) + { + if (validationConfig.entityId() == DIRECT_CALL_VALIDATION_ENTITYID) { + result = ValidationLookupKey.wrap( + uint168(uint160(validationConfig.module())) << 8 | _IS_DIRECT_CALL_VALIDATION + ); + } else { + result = ValidationLookupKey.wrap(uint168(uint160(validationConfig.entityId())) << 8); + } + } + + function moduleEntityToLookupKey(ModuleEntity _moduleEntity) + internal + pure + returns (ValidationLookupKey result) + { + (address module, uint32 _entityId) = ModuleEntityLib.unpack(_moduleEntity); + if (_entityId == DIRECT_CALL_VALIDATION_ENTITYID) { + result = ValidationLookupKey.wrap(uint168(uint160(module)) << 8 | _IS_DIRECT_CALL_VALIDATION); + } else { + result = ValidationLookupKey.wrap(uint168(uint160(_entityId)) << 8); + } + } + + function directCallLookupKey(address directCallValidation) + internal + pure + returns (ValidationLookupKey result) + { + result = ValidationLookupKey.wrap(uint168(uint160(directCallValidation)) << 8 | _IS_DIRECT_CALL_VALIDATION); + } + + function lookupKey(ValidationLocator locator) internal pure returns (ValidationLookupKey result) { + assembly ("memory-safe") { + result := and(locator, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF04) + } + } + + // Packing functions. These should not be used in the account, but in scripts and tests. + + function pack(uint32 _entityId, bool _isGlobal, bool _hasDeferredAction) + internal + pure + returns (ValidationLocator) + { + uint168 result = uint168(_entityId) << 8; + if (_isGlobal) { + result |= _VALIDATION_TYPE_GLOBAL; + } + if (_hasDeferredAction) { + result |= _HAS_DEFERRED_ACTION; + } + + return ValidationLocator.wrap(result); + } + + function packDirectCall(address directCallValidation, bool _isGlobal, bool _hasDeferredAction) + internal + pure + returns (ValidationLocator) + { + uint168 result = uint168(uint160(directCallValidation)) << 8 | _IS_DIRECT_CALL_VALIDATION; + if (_isGlobal) { + result |= _VALIDATION_TYPE_GLOBAL; + } + if (_hasDeferredAction) { + result |= _HAS_DEFERRED_ACTION; + } + + return ValidationLocator.wrap(result); + } + + function packNonce(uint32 validationEntityId, bool _isGlobal, bool _hasDeferredAction) + internal + pure + returns (uint256 result) + { + result = uint256(validationEntityId) << 8; + if (_isGlobal) { + result |= _VALIDATION_TYPE_GLOBAL; + } + if (_hasDeferredAction) { + result |= _HAS_DEFERRED_ACTION; + } + // Finally, shift left to make space for the sequential nonce key + result <<= 64; + } + + function packNonceDirectCall(address directCallValidation, bool _isGlobal, bool _hasDeferredAction) + internal + pure + returns (uint256 result) + { + result = uint256(uint160(directCallValidation)) << 8 | _IS_DIRECT_CALL_VALIDATION; + if (_isGlobal) { + result |= _VALIDATION_TYPE_GLOBAL; + } + if (_hasDeferredAction) { + result |= _HAS_DEFERRED_ACTION; + } + // Finally, shift left to make space for the sequential nonce key + result <<= 64; + } + + function packSignature( + uint32 validationEntityId, + bool _isGlobal, + bool _hasDeferredAction, + bytes memory signature + ) internal pure returns (bytes memory result) { + uint8 options = 0; + if (_isGlobal) { + options |= _VALIDATION_TYPE_GLOBAL; + } + if (_hasDeferredAction) { + options |= _HAS_DEFERRED_ACTION; + } + + return bytes.concat(abi.encodePacked(options, uint32(validationEntityId)), signature); + } + + function packSignatureDirectCall( + address directCallValidation, + bool _isGlobal, + bool _hasDeferredAction, + bytes memory signature + ) internal pure returns (bytes memory result) { + uint8 options = _IS_DIRECT_CALL_VALIDATION; + if (_isGlobal) { + options |= _VALIDATION_TYPE_GLOBAL; + } + if (_hasDeferredAction) { + options |= _HAS_DEFERRED_ACTION; + } + + return bytes.concat(abi.encodePacked(options, uint160(directCallValidation)), signature); + } + + // Operators + + function eq(ValidationLookupKey a, ValidationLookupKey b) internal pure returns (bool) { + return ValidationLookupKey.unwrap(a) == ValidationLookupKey.unwrap(b); + } +} + +using ValidationLocatorLib for ValidationLocator global; +using ValidationLocatorLib for ValidationLookupKey global; diff --git a/test/account/DeferredAction.t.sol b/test/account/DeferredAction.t.sol index bc452db6..42688509 100644 --- a/test/account/DeferredAction.t.sol +++ b/test/account/DeferredAction.t.sol @@ -83,7 +83,7 @@ contract DeferredActionTest is AccountTestBase { PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account2), - nonce: 0, + nonce: _encodeNonceDefAction(_signerValidation, GLOBAL_V, 0), initCode: initCode, callData: abi.encodeCall(IModularAccount.execute, (address(0), 0 wei, "")), accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), @@ -146,7 +146,7 @@ contract DeferredActionTest is AccountTestBase { // Attempt to call it via a deferred action in a user op PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonceDefAction(_signerValidation, GLOBAL_V, 0), initCode: "", callData: abi.encodeCall(IModularAccount.execute, (address(0), 0 wei, "")), accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), @@ -196,7 +196,7 @@ contract DeferredActionTest is AccountTestBase { // Attempt to call it via a deferred action in a user op PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonceDefAction(_signerValidation, GLOBAL_V, 0), initCode: "", callData: abi.encodeCall(IModularAccount.execute, (address(0), 0 wei, "")), accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), @@ -248,7 +248,7 @@ contract DeferredActionTest is AccountTestBase { // Attempt to call it via a deferred action in a user op PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonceDefAction(_signerValidation, GLOBAL_V, 0), initCode: "", callData: abi.encodeCall(IModularAccount.execute, (address(0), 0 wei, "")), accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), @@ -336,7 +336,7 @@ contract DeferredActionTest is AccountTestBase { PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonceDefAction(validation2, GLOBAL_V, 0), initCode: "", callData: abi.encodePacked( IAccountExecute.executeUserOp.selector, diff --git a/test/account/DeferredValidation.t.sol b/test/account/DeferredValidation.t.sol index b65cb813..1614ccd4 100644 --- a/test/account/DeferredValidation.t.sol +++ b/test/account/DeferredValidation.t.sol @@ -38,6 +38,7 @@ contract DeferredValidationTest is AccountTestBase { using ValidationConfigLib for ValidationConfig; bytes internal _encodedCall; + uint32 internal constant _NEW_ENTITY_ID = 1; ModuleEntity internal _deferredValidation; // The ABI-encoded call to `installValidation(...)` to be used with deferred validation install bytes internal _deferredValidationInstallCall; @@ -49,13 +50,12 @@ contract DeferredValidationTest is AccountTestBase { function setUp() public override { _revertSnapshot = vm.snapshotState(); - _encodedCall = abi.encodeCall(ModularAccountBase.execute, (makeAddr("dead"), 0, "")); - _deferredValidation = ModuleEntityLib.pack(address(_deploySingleSignerValidationModule()), 0); - uint32 entityId = 0; + _encodedCall = abi.encodeCall(ModularAccountBase.execute, (makeAddr("dead"), 0 wei, "")); + _deferredValidation = ModuleEntityLib.pack(address(_deploySingleSignerValidationModule()), _NEW_ENTITY_ID); (address newSigner, uint256 newSignerKey) = makeAddrAndKey("newSigner"); _newSignerKey = newSignerKey; - bytes memory deferredValidationInstallData = abi.encode(entityId, newSigner); + bytes memory deferredValidationInstallData = abi.encode(_NEW_ENTITY_ID, newSigner); _newUOValidation = ValidationConfigLib.pack({ _validationFunction: _deferredValidation, @@ -73,11 +73,9 @@ contract DeferredValidationTest is AccountTestBase { // Negatives function test_fail_deferredValidation_nonceUsed() external withSMATest { - uint256 nonce = entryPoint.getNonce(address(account1), 0); - PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: nonce, + nonce: _encodeNextNonce(address(account1), _deferredValidation, true, true), initCode: hex"", callData: _encodedCall, accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), @@ -121,11 +119,9 @@ contract DeferredValidationTest is AccountTestBase { // Note that a deadline of 0 implies no expiry vm.warp(2); - uint256 nonce = entryPoint.getNonce(address(account1), 0); - PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: nonce, + nonce: _encodeNextNonce(address(account1), _deferredValidation, true, true), initCode: hex"", callData: _encodedCall, accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), @@ -160,11 +156,9 @@ contract DeferredValidationTest is AccountTestBase { } function test_fail_deferredValidation_invalidSig() external withSMATest { - uint256 nonce = entryPoint.getNonce(address(account1), 0); - PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: nonce, + nonce: _encodeNextNonce(address(account1), _deferredValidation, true, true), initCode: hex"", callData: _encodedCall, accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), @@ -208,11 +202,9 @@ contract DeferredValidationTest is AccountTestBase { vm.prank(address(entryPoint)); account1.invalidateDeferredValidationInstallNonce(0); - uint256 nonce = entryPoint.getNonce(address(account1), 0); - PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: nonce, + nonce: _encodeNextNonce(address(account1), _deferredValidation, true, true), initCode: hex"", callData: _encodedCall, accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), @@ -251,11 +243,9 @@ contract DeferredValidationTest is AccountTestBase { } function test_fail_deferredValidation_invalidDeferredValidationSig() external withSMATest { - uint256 nonce = entryPoint.getNonce(address(account1), 0); - PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: nonce, + nonce: _encodeNextNonce(address(account1), _deferredValidation, true, true), initCode: hex"", callData: _encodedCall, accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), @@ -301,11 +291,9 @@ contract DeferredValidationTest is AccountTestBase { ValidationConfigLib.pack(_signerValidation, true, true, true), new bytes4[](0), "", hooks ); - uint256 nonce = entryPoint.getNonce(address(account1), 0); - PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: nonce, + nonce: _encodeNextNonce(address(account1), _deferredValidation, true, true), initCode: hex"", callData: _encodedCall, accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), @@ -345,11 +333,9 @@ contract DeferredValidationTest is AccountTestBase { // Positives function test_deferredValidation_deployed() external withSMATest { - uint256 nonce = entryPoint.getNonce(address(account1), 0); - PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: nonce, + nonce: _encodeNextNonce(address(account1), _deferredValidation, true, true), initCode: hex"", callData: _encodedCall, accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), @@ -394,11 +380,9 @@ contract DeferredValidationTest is AccountTestBase { ValidationConfigLib.pack(_signerValidation, true, true, true), new bytes4[](0), "", hooks ); - uint256 nonce = entryPoint.getNonce(address(account1), 0); - PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: nonce, + nonce: _encodeNextNonce(address(account1), _deferredValidation, true, true), initCode: hex"", callData: _encodedCall, accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), @@ -452,11 +436,9 @@ contract DeferredValidationTest is AccountTestBase { // prefund vm.deal(address(account2), 100 ether); - uint256 nonce = entryPoint.getNonce(address(account1), 0); - PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account2), - nonce: nonce, + nonce: _encodeNextNonce(address(account1), _deferredValidation, true, true), initCode: initCode, callData: _encodedCall, accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), diff --git a/test/account/DirectCallsFromModule.t.sol b/test/account/DirectCallsFromModule.t.sol index e11c822f..272543a2 100644 --- a/test/account/DirectCallsFromModule.t.sol +++ b/test/account/DirectCallsFromModule.t.sol @@ -44,7 +44,7 @@ contract DirectCallsFromModuleTest is AccountTestBase { modifier randomizedValidationType(bool selectorValidation) { if (selectorValidation) { - _installValidationSelector(); + _installValidationToSelector(); } else { _installValidationGlobal(); } @@ -81,7 +81,7 @@ contract DirectCallsFromModuleTest is AccountTestBase { } function test_fail_directCallModuleCallOtherSelector() external withSMATest { - _installValidationSelector(); + _installValidationToSelector(); Call[] memory calls = new Call[](0); @@ -174,7 +174,7 @@ contract DirectCallsFromModuleTest is AccountTestBase { /* Internals */ /* -------------------------------------------------------------------------- */ - function _installValidationSelector() internal { + function _installValidationToSelector() internal { bytes4[] memory selectors = new bytes4[](1); selectors[0] = IModularAccount.execute.selector; diff --git a/test/account/GlobalValidationTest.t.sol b/test/account/GlobalValidationTest.t.sol index 12bd83c6..b1677e89 100644 --- a/test/account/GlobalValidationTest.t.sol +++ b/test/account/GlobalValidationTest.t.sol @@ -55,7 +55,7 @@ contract GlobalValidationTest is AccountTestBase { function test_globalValidation_userOp_simple() public withSMATest { PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account2), - nonce: 0, + nonce: _encodeNonce(_signerValidation, GLOBAL_V, 0), initCode: abi.encodePacked( address(factory), abi.encodeCall(factory.createAccount, (owner2, 0, TEST_DEFAULT_VALIDATION_ENTITY_ID)) ), @@ -70,8 +70,7 @@ contract GlobalValidationTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = - _encodeSignature(_signerValidation, GLOBAL_VALIDATION, abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); + userOp.signature = _encodeSignature(abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -97,7 +96,7 @@ contract GlobalValidationTest is AccountTestBase { function test_globalValidation_failsOnSelectorApplicability() public withSMATest { PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account2), - nonce: 0, + nonce: _encodeNonce(_signerValidation, SELECTOR_ASSOCIATED_V, 0), initCode: abi.encodePacked( address(factory), abi.encodeCall(factory.createAccount, (owner2, 0, TEST_DEFAULT_VALIDATION_ENTITY_ID)) ), @@ -112,9 +111,7 @@ contract GlobalValidationTest is AccountTestBase { bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); // Use the wrong checking mode - SELECTOR_ASSOCIATED_VALIDATION - userOp.signature = _encodeSignature( - _signerValidation, SELECTOR_ASSOCIATED_VALIDATION, abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v) - ); + userOp.signature = _encodeSignature(abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; diff --git a/test/account/HookOrdering.t.sol b/test/account/HookOrdering.t.sol index 4161a66a..29d845ce 100644 --- a/test/account/HookOrdering.t.sol +++ b/test/account/HookOrdering.t.sol @@ -109,7 +109,7 @@ contract HookOrderingTest is AccountTestBase { PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(orderCheckerValidationEntity, SELECTOR_ASSOCIATED_V, 0), initCode: hex"", callData: abi.encodePacked( account1.executeUserOp.selector, abi.encodeCall(HookOrderCheckerModule.foo, (17)) @@ -118,7 +118,7 @@ contract HookOrderingTest is AccountTestBase { preVerificationGas: 0, gasFees: _encodeGas(1, 1), paymasterAndData: hex"", - signature: _encodeSignature(orderCheckerValidationEntity, SELECTOR_ASSOCIATED_VALIDATION, hex"") + signature: _encodeSignature(hex"") }); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); @@ -134,14 +134,14 @@ contract HookOrderingTest is AccountTestBase { PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(orderCheckerValidationEntity, SELECTOR_ASSOCIATED_V, 0), initCode: hex"", callData: abi.encodeCall(HookOrderCheckerModule.foo, (17)), accountGasLimits: _encodeGas(1_000_000, 1_000_000), preVerificationGas: 0, gasFees: _encodeGas(1, 1), paymasterAndData: hex"", - signature: _encodeSignature(orderCheckerValidationEntity, SELECTOR_ASSOCIATED_VALIDATION, hex"") + signature: _encodeSignature(hex"") }); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); @@ -157,7 +157,7 @@ contract HookOrderingTest is AccountTestBase { PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(orderCheckerValidationEntity, SELECTOR_ASSOCIATED_V, 0), initCode: hex"", callData: abi.encodePacked( account1.executeUserOp.selector, abi.encodeCall(HookOrderCheckerModule.foo, (17)) @@ -166,7 +166,7 @@ contract HookOrderingTest is AccountTestBase { preVerificationGas: 0, gasFees: _encodeGas(1, 1), paymasterAndData: hex"", - signature: _encodeSignature(orderCheckerValidationEntity, SELECTOR_ASSOCIATED_VALIDATION, hex"") + signature: _encodeSignature(hex"") }); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); @@ -222,7 +222,7 @@ contract HookOrderingTest is AccountTestBase { PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(orderCheckerValidationEntity, SELECTOR_ASSOCIATED_V, 0), initCode: hex"", callData: abi.encodePacked( account1.executeUserOp.selector, @@ -235,7 +235,7 @@ contract HookOrderingTest is AccountTestBase { preVerificationGas: 0, gasFees: _encodeGas(1, 1), paymasterAndData: hex"", - signature: _encodeSignature(orderCheckerValidationEntity, SELECTOR_ASSOCIATED_VALIDATION, hex"") + signature: _encodeSignature(hex"") }); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); @@ -251,7 +251,7 @@ contract HookOrderingTest is AccountTestBase { PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(orderCheckerValidationEntity, SELECTOR_ASSOCIATED_V, 0), initCode: hex"", callData: abi.encodeCall( account1.execute, (address(hookOrderChecker), 0 wei, abi.encodeCall(HookOrderCheckerModule.foo, (17))) @@ -260,7 +260,7 @@ contract HookOrderingTest is AccountTestBase { preVerificationGas: 0, gasFees: _encodeGas(1, 1), paymasterAndData: hex"", - signature: _encodeSignature(orderCheckerValidationEntity, SELECTOR_ASSOCIATED_VALIDATION, hex"") + signature: _encodeSignature(hex"") }); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); @@ -276,7 +276,7 @@ contract HookOrderingTest is AccountTestBase { PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(orderCheckerValidationEntity, SELECTOR_ASSOCIATED_V, 0), initCode: hex"", callData: abi.encodePacked( account1.executeUserOp.selector, @@ -289,7 +289,7 @@ contract HookOrderingTest is AccountTestBase { preVerificationGas: 0, gasFees: _encodeGas(1, 1), paymasterAndData: hex"", - signature: _encodeSignature(orderCheckerValidationEntity, SELECTOR_ASSOCIATED_VALIDATION, hex"") + signature: _encodeSignature(hex"") }); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); diff --git a/test/account/ModularAccount.t.sol b/test/account/ModularAccount.t.sol index e86fef29..24621a0b 100644 --- a/test/account/ModularAccount.t.sol +++ b/test/account/ModularAccount.t.sol @@ -99,7 +99,7 @@ contract ModularAccountTest is AccountTestBase { function test_postDeploy_ethSend() public withSMATest { PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(_signerValidation, GLOBAL_V, 0), initCode: "", callData: abi.encodeCall(ModularAccountBase.execute, (ethRecipient, 1 wei, "")), accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), @@ -112,8 +112,7 @@ contract ModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = - _encodeSignature(_signerValidation, GLOBAL_VALIDATION, abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); + userOp.signature = _encodeSignature(abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -145,7 +144,7 @@ contract ModularAccountTest is AccountTestBase { PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account2), - nonce: 0, + nonce: _encodeNonce(_signerValidation, GLOBAL_V, 0), initCode: initCode, callData: callData, accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), @@ -158,8 +157,7 @@ contract ModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = - _encodeSignature(_signerValidation, GLOBAL_VALIDATION, abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); + userOp.signature = _encodeSignature(abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -178,7 +176,7 @@ contract ModularAccountTest is AccountTestBase { PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account2), - nonce: 0, + nonce: _encodeNonce(_signerValidation, GLOBAL_V, 0), initCode: initCode, callData: abi.encodeCall(ModularAccountBase.execute, (recipient, 1 wei, "")), accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), @@ -191,8 +189,7 @@ contract ModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = - _encodeSignature(_signerValidation, GLOBAL_VALIDATION, abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); + userOp.signature = _encodeSignature(abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -205,7 +202,7 @@ contract ModularAccountTest is AccountTestBase { function test_debug_ModularAccount_storageAccesses() public withSMATest { PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(_signerValidation, GLOBAL_V, 0), initCode: "", callData: abi.encodeCall(ModularAccountBase.execute, (ethRecipient, 1 wei, "")), accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), @@ -218,8 +215,7 @@ contract ModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = - _encodeSignature(_signerValidation, GLOBAL_VALIDATION, abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); + userOp.signature = _encodeSignature(abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -237,7 +233,7 @@ contract ModularAccountTest is AccountTestBase { function test_contractInteraction() public withSMATest { PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(_signerValidation, GLOBAL_V, 0), initCode: "", callData: abi.encodeCall( ModularAccountBase.execute, (address(counter), 0, abi.encodeCall(counter.increment, ())) @@ -252,8 +248,7 @@ contract ModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = - _encodeSignature(_signerValidation, GLOBAL_VALIDATION, abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); + userOp.signature = _encodeSignature(abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -271,7 +266,7 @@ contract ModularAccountTest is AccountTestBase { PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(_signerValidation, GLOBAL_V, 0), initCode: "", callData: abi.encodeCall(ModularAccountBase.executeBatch, (calls)), accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), @@ -284,8 +279,7 @@ contract ModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = - _encodeSignature(_signerValidation, GLOBAL_VALIDATION, abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); + userOp.signature = _encodeSignature(abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -516,7 +510,7 @@ contract ModularAccountTest is AccountTestBase { PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(ModuleEntityLib.pack(address(singleSignerValidationModule), newEntityId), GLOBAL_V, 0), initCode: "", callData: abi.encodeCall(ModularAccountBase.execute, (ethRecipient, 1 wei, "")), accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), @@ -529,11 +523,7 @@ contract ModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = _encodeSignature( - ModuleEntityLib.pack(address(singleSignerValidationModule), newEntityId), - GLOBAL_VALIDATION, - abi.encodePacked(r, s, v) - ); + userOp.signature = _encodeSignature(abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -650,7 +640,7 @@ contract ModularAccountTest is AccountTestBase { function test_validationRevertsOnShortCalldata() public withSMATest { PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(_signerValidation, GLOBAL_V, 0), initCode: "", callData: abi.encodePacked(bytes3(ModularAccountBase.execute.selector)), // Short calldata accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), @@ -663,8 +653,7 @@ contract ModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = - _encodeSignature(_signerValidation, GLOBAL_VALIDATION, abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); + userOp.signature = _encodeSignature(abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; diff --git a/test/account/MultiValidation.t.sol b/test/account/MultiValidation.t.sol index 1c47a32a..cd1bc70a 100644 --- a/test/account/MultiValidation.t.sol +++ b/test/account/MultiValidation.t.sol @@ -30,7 +30,7 @@ import {ExecutionLib} from "../../src/libraries/ExecutionLib.sol"; import {SingleSignerValidationModule} from "../../src/modules/validation/SingleSignerValidationModule.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; -import {CODELESS_ADDRESS, TEST_DEFAULT_VALIDATION_ENTITY_ID} from "../utils/TestConstants.sol"; +import {CODELESS_ADDRESS} from "../utils/TestConstants.sol"; contract MultiValidationTest is AccountTestBase { using ECDSA for bytes32; @@ -38,6 +38,8 @@ contract MultiValidationTest is AccountTestBase { SingleSignerValidationModule public validator2; + uint32 public constant ENTITY_ID_1 = 1; + address public owner2; uint256 public owner2Key; @@ -50,15 +52,15 @@ contract MultiValidationTest is AccountTestBase { function test_overlappingValidationInstall() public withSMATest { vm.prank(address(entryPoint)); account1.installValidation( - ValidationConfigLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID, true, true, true), + ValidationConfigLib.pack(address(validator2), ENTITY_ID_1, true, true, true), new bytes4[](0), - abi.encode(TEST_DEFAULT_VALIDATION_ENTITY_ID, owner2), + abi.encode(ENTITY_ID_1, owner2), new bytes[](0) ); ModuleEntity[] memory validations = new ModuleEntity[](2); validations[0] = _signerValidation; - validations[1] = ModuleEntityLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID); + validations[1] = ModuleEntityLib.pack(address(validator2), ENTITY_ID_1); bytes4[] memory selectors0 = account1.getValidationData(validations[0]).selectors; bytes4[] memory selectors1 = account1.getValidationData(validations[1]).selectors; @@ -77,23 +79,19 @@ contract MultiValidationTest is AccountTestBase { vm.expectRevert( abi.encodeWithSelector( ExecutionLib.RuntimeValidationFunctionReverted.selector, - ModuleEntityLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID), + ModuleEntityLib.pack(address(validator2), ENTITY_ID_1), abi.encodeWithSignature("NotAuthorized()") ) ); account1.executeWithRuntimeValidation( abi.encodeCall(IModularAccount.execute, (CODELESS_ADDRESS, 0, "")), - _encodeSignature( - ModuleEntityLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, "" - ) + _encodeSignature(ModuleEntityLib.pack(address(validator2), ENTITY_ID_1), GLOBAL_VALIDATION, "") ); vm.prank(owner2); account1.executeWithRuntimeValidation( abi.encodeCall(IModularAccount.execute, (CODELESS_ADDRESS, 0, "")), - _encodeSignature( - ModuleEntityLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, "" - ) + _encodeSignature(ModuleEntityLib.pack(address(validator2), ENTITY_ID_1), GLOBAL_VALIDATION, "") ); } @@ -104,7 +102,7 @@ contract MultiValidationTest is AccountTestBase { PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(ModuleEntityLib.pack(address(validator2), ENTITY_ID_1), GLOBAL_V, 0), initCode: "", callData: abi.encodeCall(ModularAccountBase.execute, (CODELESS_ADDRESS, 0, "")), accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), @@ -117,11 +115,7 @@ contract MultiValidationTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = _encodeSignature( - ModuleEntityLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID), - GLOBAL_VALIDATION, - abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v) - ); + userOp.signature = _encodeSignature(abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -130,13 +124,10 @@ contract MultiValidationTest is AccountTestBase { // Sign with owner 1, expect fail - userOp.nonce = 1; + userOp.nonce = _encodeNonce(ModuleEntityLib.pack(address(validator2), ENTITY_ID_1), GLOBAL_V, 1); + userOpHash = entryPoint.getUserOpHash(userOp); (v, r, s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = _encodeSignature( - ModuleEntityLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID), - GLOBAL_VALIDATION, - abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v) - ); + userOp.signature = _encodeSignature(abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); userOps[0] = userOp; vm.expectRevert(abi.encodeWithSelector(IEntryPoint.FailedOp.selector, 0, "AA24 signature error")); diff --git a/test/account/PHCallBuffers.t.sol b/test/account/PHCallBuffers.t.sol index 0850ada5..ba1a58ee 100644 --- a/test/account/PHCallBuffers.t.sol +++ b/test/account/PHCallBuffers.t.sol @@ -51,7 +51,8 @@ contract PHCallBufferTest is AccountTestBase { // installed entity id is their index MockModule[] public execHooks; - // installed with entity id 0 + uint32 public constant NEW_VALIDATION_ENTITY_ID = 1; + // installed with entity id 1 MockModule public validationModule; // installed with entity id 1 @@ -71,7 +72,7 @@ contract PHCallBufferTest is AccountTestBase { PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(_validationFunction, GLOBAL_V, 0), initCode: "", callData: abi.encodePacked( IAccountExecute.executeUserOp.selector, abi.encodeCall(account1.execute, (beneficiary, 0 wei, "")) @@ -242,7 +243,7 @@ contract PHCallBufferTest is AccountTestBase { emit ReceivedCall( abi.encodeCall( IValidationModule.validateRuntime, - (address(account1), uint32(0), address(owner1), 0 wei, callData, "") + (address(account1), NEW_VALIDATION_ENTITY_ID, address(owner1), 0 wei, callData, "") ), 0 ); @@ -276,7 +277,7 @@ contract PHCallBufferTest is AccountTestBase { emit ReceivedCall( abi.encodeCall( IValidationModule.validateRuntime, - (address(account1), uint32(0), address(owner1), 0 wei, callData, "") + (address(account1), NEW_VALIDATION_ENTITY_ID, address(owner1), 0 wei, callData, "") ), 0 ); @@ -319,7 +320,7 @@ contract PHCallBufferTest is AccountTestBase { emit ReceivedCall( abi.encodeCall( IValidationModule.validateRuntime, - (address(account1), uint32(0), address(owner1), 0 wei, callData, "") + (address(account1), NEW_VALIDATION_ENTITY_ID, address(owner1), 0 wei, callData, "") ), 0 ); @@ -363,7 +364,7 @@ contract PHCallBufferTest is AccountTestBase { emit ReceivedCall( abi.encodeCall( IValidationModule.validateRuntime, - (address(account1), uint32(0), address(owner1), 0 wei, callData, "") + (address(account1), NEW_VALIDATION_ENTITY_ID, address(owner1), 0 wei, callData, "") ), 0 ); @@ -471,14 +472,14 @@ contract PHCallBufferTest is AccountTestBase { vm.sign(owner1Key, MessageHashUtils.toEthSignedMessageHash(userOpHash)); bytes memory smaSig = abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v); - return _encodeSignature(_validationFunction, GLOBAL_VALIDATION, smaSig); + return _encodeSignature(smaSig); } else { - return _encodeSignature(_validationFunction, GLOBAL_VALIDATION, ""); + return _encodeSignature(""); } } function _install3ValAssocExecHooks() internal { - _install3ValAssocExecHooks(0, false); + _install3ValAssocExecHooks(NEW_VALIDATION_ENTITY_ID, false); } function _install3ValAssocExecHooks(uint32 validationEntityId, bool forceInstall) internal { diff --git a/test/account/PerHookData.t.sol b/test/account/PerHookData.t.sol index 7a9a678a..493954ff 100644 --- a/test/account/PerHookData.t.sol +++ b/test/account/PerHookData.t.sol @@ -63,12 +63,7 @@ contract PerHookDataTest is CustomValidationTestBase { PreValidationHookData[] memory preValidationHookData = new PreValidationHookData[](1); preValidationHookData[0] = PreValidationHookData({index: 0, validationData: abi.encodePacked(_counter)}); - userOp.signature = _encodeSignature( - _signerValidation, - GLOBAL_VALIDATION, - preValidationHookData, - abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v) - ); + userOp.signature = _encodeSignature(preValidationHookData, abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -115,7 +110,7 @@ contract PerHookDataTest is CustomValidationTestBase { (PackedUserOperation memory userOp, bytes32 userOpHash) = _getCounterUserOP(); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = _encodeSignature(_signerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature(abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -143,9 +138,7 @@ contract PerHookDataTest is CustomValidationTestBase { preValidationHookData[0] = PreValidationHookData({index: 0, validationData: abi.encodePacked(_counter)}); preValidationHookData[1] = PreValidationHookData({index: 1, validationData: abi.encodePacked(_counter)}); - userOp.signature = _encodeSignature( - _signerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v) - ); + userOp.signature = _encodeSignature(preValidationHookData, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -174,12 +167,7 @@ contract PerHookDataTest is CustomValidationTestBase { preValidationHookData[0] = PreValidationHookData({index: 0, validationData: abi.encodePacked(_counter)}); preValidationHookData[1] = PreValidationHookData({index: 1, validationData: abi.encodePacked(_counter)}); - userOp.signature = _encodeSignature( - _signerValidation, - GLOBAL_VALIDATION, - preValidationHookData, - abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v) - ); + userOp.signature = _encodeSignature(preValidationHookData, abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -199,9 +187,7 @@ contract PerHookDataTest is CustomValidationTestBase { preValidationHookData[0] = PreValidationHookData({index: 0, validationData: abi.encodePacked(_counter)}); preValidationHookData[1] = PreValidationHookData({index: 0, validationData: abi.encodePacked(_counter)}); - userOp.signature = _encodeSignature( - _signerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v) - ); + userOp.signature = _encodeSignature(preValidationHookData, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -220,7 +206,7 @@ contract PerHookDataTest is CustomValidationTestBase { function test_failAccessControl_badTarget_userOp() public withSMATest { PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(_signerValidation, GLOBAL_V, 0), initCode: "", callData: abi.encodeCall(ModularAccountBase.execute, (beneficiary, 1 wei, "")), accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), @@ -236,9 +222,7 @@ contract PerHookDataTest is CustomValidationTestBase { PreValidationHookData[] memory preValidationHookData = new PreValidationHookData[](1); preValidationHookData[0] = PreValidationHookData({index: 0, validationData: abi.encodePacked(beneficiary)}); - userOp.signature = _encodeSignature( - _signerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v) - ); + userOp.signature = _encodeSignature(preValidationHookData, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -265,9 +249,7 @@ contract PerHookDataTest is CustomValidationTestBase { PreValidationHookData[] memory preValidationHookData = new PreValidationHookData[](1); preValidationHookData[0] = PreValidationHookData({index: 0, validationData: ""}); - userOp.signature = _encodeSignature( - _signerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v) - ); + userOp.signature = _encodeSignature(preValidationHookData, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -510,7 +492,7 @@ contract PerHookDataTest is CustomValidationTestBase { function _getCounterUserOP() internal view returns (PackedUserOperation memory, bytes32) { PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(_signerValidation, GLOBAL_V, 0), initCode: "", callData: abi.encodeCall( ModularAccountBase.execute, (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) diff --git a/test/account/PostHookData.t.sol b/test/account/PostHookData.t.sol index de82a8a3..910565da 100644 --- a/test/account/PostHookData.t.sol +++ b/test/account/PostHookData.t.sol @@ -38,7 +38,8 @@ contract PostHookDataTest is AccountTestBase { // installed entity id is their index MockModule[] public execHooks; - // installed with entity id 0 + uint32 public constant NEW_VALIDATION_ENTITY_ID = 1; + // installed with entity id 1 MockModule public validationModule; ModuleEntity internal _validationFunction; @@ -56,7 +57,7 @@ contract PostHookDataTest is AccountTestBase { PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(_validationFunction, GLOBAL_V, 0), initCode: hex"", callData: abi.encodePacked( IAccountExecute.executeUserOp.selector, abi.encodeCall(account1.execute, (beneficiary, 0, hex"")) @@ -65,7 +66,7 @@ contract PostHookDataTest is AccountTestBase { preVerificationGas: 0, gasFees: _encodeGas(0, 1), paymasterAndData: hex"", - signature: _encodeSignature(_validationFunction, GLOBAL_VALIDATION, "") + signature: _encodeSignature("") }); bytes32 userOpHash = entryPoint.getUserOpHash(userOp); @@ -207,7 +208,7 @@ contract PostHookDataTest is AccountTestBase { account1.installValidation( ValidationConfigLib.pack({ _module: address(validationModule), - _entityId: 0, + _entityId: NEW_VALIDATION_ENTITY_ID, _isGlobal: true, _isSignatureValidation: true, _isUserOpValidation: true @@ -217,6 +218,6 @@ contract PostHookDataTest is AccountTestBase { hookInstalls ); - _validationFunction = ModuleEntityLib.pack(address(validationModule), 0); + _validationFunction = ModuleEntityLib.pack(address(validationModule), NEW_VALIDATION_ENTITY_ID); } } diff --git a/test/account/RTCallBuffer.t.sol b/test/account/RTCallBuffer.t.sol index e645ad47..0547fe00 100644 --- a/test/account/RTCallBuffer.t.sol +++ b/test/account/RTCallBuffer.t.sol @@ -32,7 +32,8 @@ contract RTCallBufferTest is AccountTestBase { // installed entity id is their index MockModule[] public validationHooks; - // installed with entity id 0 + uint32 public constant NEW_VALIDATION_ENTITY_ID = 1; + // installed with entity id 1 MockModule public validationModule; ModuleEntity internal _validationFunction; @@ -66,7 +67,14 @@ contract RTCallBufferTest is AccountTestBase { emit ReceivedCall( abi.encodeCall( IValidationModule.validateRuntime, - (address(account1), 0, address(entryPoint), 0 wei, callData, "abcdefghijklmnopqrstuvwxyz") + ( + address(account1), + NEW_VALIDATION_ENTITY_ID, + address(entryPoint), + 0 wei, + callData, + "abcdefghijklmnopqrstuvwxyz" + ) ), 0 ); @@ -108,7 +116,7 @@ contract RTCallBufferTest is AccountTestBase { emit ReceivedCall( abi.encodeCall( IValidationModule.validateRuntime, - (address(account1), uint32(0), address(entryPoint), 0 wei, callData, validationData) + (address(account1), NEW_VALIDATION_ENTITY_ID, address(entryPoint), 0 wei, callData, validationData) ), 0 ); @@ -147,7 +155,7 @@ contract RTCallBufferTest is AccountTestBase { account1.installValidation( ValidationConfigLib.pack({ _module: address(validationModule), - _entityId: 0, + _entityId: NEW_VALIDATION_ENTITY_ID, _isGlobal: true, _isSignatureValidation: true, _isUserOpValidation: true @@ -157,7 +165,7 @@ contract RTCallBufferTest is AccountTestBase { hooks ); - _validationFunction = ModuleEntityLib.pack(address(validationModule), 0); + _validationFunction = ModuleEntityLib.pack(address(validationModule), NEW_VALIDATION_ENTITY_ID); bytes memory callData = abi.encodeCall(account1.execute, (beneficiary, 0 wei, "")); @@ -187,7 +195,7 @@ contract RTCallBufferTest is AccountTestBase { emit ReceivedCall( abi.encodeCall( IValidationModule.validateRuntime, - (address(account1), uint32(0), address(entryPoint), 0 wei, callData, validationData) + (address(account1), NEW_VALIDATION_ENTITY_ID, address(entryPoint), 0 wei, callData, validationData) ), 0 ); @@ -220,7 +228,7 @@ contract RTCallBufferTest is AccountTestBase { account1.installValidation( ValidationConfigLib.pack({ _module: address(validationModule), - _entityId: 0, + _entityId: NEW_VALIDATION_ENTITY_ID, _isGlobal: true, _isSignatureValidation: true, _isUserOpValidation: true @@ -230,6 +238,6 @@ contract RTCallBufferTest is AccountTestBase { hooks ); - _validationFunction = ModuleEntityLib.pack(address(validationModule), 0); + _validationFunction = ModuleEntityLib.pack(address(validationModule), NEW_VALIDATION_ENTITY_ID); } } diff --git a/test/account/SMASpecific.t.sol b/test/account/SMASpecific.t.sol index 38497dfd..831b684b 100644 --- a/test/account/SMASpecific.t.sol +++ b/test/account/SMASpecific.t.sol @@ -104,11 +104,9 @@ contract SMASpecificTest is AccountTestBase { } function _runUserOpWithFallbackValidation(bytes memory encodedCall) internal { - uint256 nonce = entryPoint.getNonce(address(account1), 0); - PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: nonce, + nonce: _encodeNextNonce(address(account1), FALLBACK_VALIDATION, true), initCode: hex"", callData: encodedCall, accountGasLimits: _encodeGas(type(uint24).max, type(uint24).max), @@ -121,8 +119,7 @@ contract SMASpecificTest is AccountTestBase { bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = - _encodeSignature(FALLBACK_VALIDATION, GLOBAL_VALIDATION, abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); + userOp.signature = _encodeSignature(abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; diff --git a/test/account/SelfCallAuthorization.t.sol b/test/account/SelfCallAuthorization.t.sol index 2a59739a..c477cf2b 100644 --- a/test/account/SelfCallAuthorization.t.sol +++ b/test/account/SelfCallAuthorization.t.sol @@ -333,10 +333,9 @@ contract SelfCallAuthorizationTest is AccountTestBase { view returns (PackedUserOperation memory) { - uint256 nonce = entryPoint.getNonce(address(account1), 0); return PackedUserOperation({ sender: address(account1), - nonce: nonce, + nonce: _encodeNextNonce(address(account1), comprehensiveModuleValidation, false), initCode: hex"", callData: callData, accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), @@ -344,8 +343,6 @@ contract SelfCallAuthorizationTest is AccountTestBase { gasFees: _encodeGas(1, 1), paymasterAndData: hex"", signature: _encodeSignature( - comprehensiveModuleValidation, - SELECTOR_ASSOCIATED_VALIDATION, // Comprehensive module's validation function doesn't actually check anything, so we don't need to // sign anything. "" diff --git a/test/account/SigCallBuffer.t.sol b/test/account/SigCallBuffer.t.sol index b90279a4..065da40b 100644 --- a/test/account/SigCallBuffer.t.sol +++ b/test/account/SigCallBuffer.t.sol @@ -40,7 +40,8 @@ contract SigCallBufferTest is AccountTestBase { MockModule[] public validationHooks; - // installed with entity id 0 + uint32 public constant NEW_VALIDATION_ENTITY_ID = 1; + // installed with entity id 1 MockModule public validationModule; ModuleEntity internal _validationFunction; @@ -74,7 +75,13 @@ contract SigCallBufferTest is AccountTestBase { address(validationModule), abi.encodeCall( IValidationModule.validateSignature, - (address(account1), uint32(0), beneficiary, hash, abi.encodePacked(EOA_TYPE_SIGNATURE)) + ( + address(account1), + NEW_VALIDATION_ENTITY_ID, + beneficiary, + hash, + abi.encodePacked(EOA_TYPE_SIGNATURE) + ) ) ); } @@ -155,7 +162,7 @@ contract SigCallBufferTest is AccountTestBase { vm.expectRevert( abi.encodeWithSelector( ExecutionLib.SignatureValidationFunctionReverted.selector, - ModuleEntityLib.pack(address(validationModule), uint32(0)), + ModuleEntityLib.pack(address(validationModule), NEW_VALIDATION_ENTITY_ID), hex"abcdabcd" ) ); @@ -210,7 +217,7 @@ contract SigCallBufferTest is AccountTestBase { } else { validationConfig = ValidationConfigLib.pack({ _module: address(validationModule), - _entityId: 0, + _entityId: NEW_VALIDATION_ENTITY_ID, _isGlobal: true, _isSignatureValidation: true, _isUserOpValidation: false @@ -240,7 +247,7 @@ contract SigCallBufferTest is AccountTestBase { address(validationModule), abi.encodeCall( IValidationModule.validateSignature, - (address(account1), uint32(0), beneficiary, hash, fuzzConfig.signature) + (address(account1), NEW_VALIDATION_ENTITY_ID, beneficiary, hash, fuzzConfig.signature) ) ); } diff --git a/test/account/UOCallBuffer.t.sol b/test/account/UOCallBuffer.t.sol index d40394d0..a496499b 100644 --- a/test/account/UOCallBuffer.t.sol +++ b/test/account/UOCallBuffer.t.sol @@ -33,7 +33,8 @@ contract UOCallBufferTest is AccountTestBase { // installed entity id is their index MockModule[] public validationHooks; - // installed with entity id 0 + uint32 public constant NEW_VALIDATION_ENTITY_ID = 1; + // installed with entity id 1 MockModule public validationModule; ModuleEntity internal _validationFunction; @@ -49,7 +50,7 @@ contract UOCallBufferTest is AccountTestBase { PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(_validationFunction, GLOBAL_V, 0), initCode: "", callData: abi.encodeCall(account1.execute, (beneficiary, 0 wei, "")), accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), @@ -73,10 +74,12 @@ contract UOCallBufferTest is AccountTestBase { userOp.signature = "abcdefghijklmnopqrstuvwxyz"; vm.expectEmit(address(validationModule)); - emit ReceivedCall(abi.encodeCall(IValidationModule.validateUserOp, (uint32(0), userOp, userOpHash)), 0); + emit ReceivedCall( + abi.encodeCall(IValidationModule.validateUserOp, (NEW_VALIDATION_ENTITY_ID, userOp, userOpHash)), 0 + ); // Now, fill in the signature - userOp.signature = _encodeSignature(_validationFunction, GLOBAL_VALIDATION, "abcdefghijklmnopqrstuvwxyz"); + userOp.signature = _encodeSignature("abcdefghijklmnopqrstuvwxyz"); vm.prank(address(entryPoint)); account1.validateUserOp(userOp, userOpHash, 1 wei); @@ -90,7 +93,7 @@ contract UOCallBufferTest is AccountTestBase { PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(_validationFunction, GLOBAL_V, 0), initCode: "", callData: abi.encodeCall(account1.execute, (beneficiary, 0 wei, "")), accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), @@ -122,11 +125,12 @@ contract UOCallBufferTest is AccountTestBase { userOp.signature = validationData; vm.expectEmit(address(validationModule)); - emit ReceivedCall(abi.encodeCall(IValidationModule.validateUserOp, (uint32(0), userOp, userOpHash)), 0); + emit ReceivedCall( + abi.encodeCall(IValidationModule.validateUserOp, (NEW_VALIDATION_ENTITY_ID, userOp, userOpHash)), 0 + ); // Now, fill in the signature - userOp.signature = - _encodeSignature(_validationFunction, GLOBAL_VALIDATION, preValidationHookDatasToSend, validationData); + userOp.signature = _encodeSignature(preValidationHookDatasToSend, validationData); vm.prank(address(entryPoint)); account1.validateUserOp(userOp, userOpHash, 1 wei); @@ -159,7 +163,7 @@ contract UOCallBufferTest is AccountTestBase { account1.installValidation( ValidationConfigLib.pack({ _module: address(validationModule), - _entityId: 0, + _entityId: NEW_VALIDATION_ENTITY_ID, _isGlobal: true, _isSignatureValidation: true, _isUserOpValidation: true @@ -169,12 +173,12 @@ contract UOCallBufferTest is AccountTestBase { hooks ); - _validationFunction = ModuleEntityLib.pack(address(validationModule), 0); + _validationFunction = ModuleEntityLib.pack(address(validationModule), NEW_VALIDATION_ENTITY_ID); // Set up the user op PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(_validationFunction, GLOBAL_V, 0), initCode: "", callData: abi.encodeCall(account1.execute, (beneficiary, 0 wei, "")), accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), @@ -208,11 +212,12 @@ contract UOCallBufferTest is AccountTestBase { userOp.signature = validationData; vm.expectEmit(address(validationModule)); - emit ReceivedCall(abi.encodeCall(IValidationModule.validateUserOp, (uint32(0), userOp, userOpHash)), 0); + emit ReceivedCall( + abi.encodeCall(IValidationModule.validateUserOp, (NEW_VALIDATION_ENTITY_ID, userOp, userOpHash)), 0 + ); // Now, fill in the signature - userOp.signature = - _encodeSignature(_validationFunction, GLOBAL_VALIDATION, preValidationHookDatasToSend, validationData); + userOp.signature = _encodeSignature(preValidationHookDatasToSend, validationData); vm.prank(address(entryPoint)); account1.validateUserOp(userOp, userOpHash, 1 wei); @@ -223,14 +228,14 @@ contract UOCallBufferTest is AccountTestBase { PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(_validationFunction, GLOBAL_V, 0), initCode: "", callData: abi.encodeCall(account1.execute, (beneficiary, 0 wei, "")), accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), preVerificationGas: 0, gasFees: _encodeGas(1, 2), paymasterAndData: "", - signature: _encodeSignature(_validationFunction, GLOBAL_VALIDATION, "abcdefghijklmnopqrstuvwxyz") + signature: _encodeSignature("abcdefghijklmnopqrstuvwxyz") }); bytes32 userOpHash = entryPoint.getUserOpHash(userOp); @@ -258,14 +263,14 @@ contract UOCallBufferTest is AccountTestBase { function test_uoCallBuffer_shortReturnData_validationFunction() public { PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(_signerValidation, GLOBAL_V, 0), initCode: "", callData: abi.encodeCall(account1.execute, (beneficiary, 0 wei, "")), accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), preVerificationGas: 0, gasFees: _encodeGas(1, 2), paymasterAndData: "", - signature: _encodeSignature(_signerValidation, GLOBAL_VALIDATION, "abcdefghijklmnopqrstuvwxyz") + signature: _encodeSignature("abcdefghijklmnopqrstuvwxyz") }); bytes32 userOpHash = entryPoint.getUserOpHash(userOp); @@ -309,7 +314,7 @@ contract UOCallBufferTest is AccountTestBase { account1.installValidation( ValidationConfigLib.pack({ _module: address(validationModule), - _entityId: 0, + _entityId: NEW_VALIDATION_ENTITY_ID, _isGlobal: true, _isSignatureValidation: true, _isUserOpValidation: true @@ -319,6 +324,6 @@ contract UOCallBufferTest is AccountTestBase { hooks ); - _validationFunction = ModuleEntityLib.pack(address(validationModule), 0); + _validationFunction = ModuleEntityLib.pack(address(validationModule), NEW_VALIDATION_ENTITY_ID); } } diff --git a/test/account/UpgradeToSma.t.sol b/test/account/UpgradeToSma.t.sol index 1b99933e..e87d3604 100644 --- a/test/account/UpgradeToSma.t.sol +++ b/test/account/UpgradeToSma.t.sol @@ -77,20 +77,24 @@ contract UpgradeToSmaTest is AccountTestBase { vm.prank(owner2); account1.uninstallValidation(_signerValidation, "", new bytes[](0)); - // Build expected revert data for a UO with the original signer. - bytes memory expectedRevertdata = abi.encodeWithSelector( - IEntryPoint.FailedOpWithRevert.selector, - 0, - "AA23 reverted", - abi.encodeWithSelector( - ModularAccountBase.ValidationFunctionMissing.selector, ModularAccountBase.execute.selector - ) - ); - - // Execute a UO with the original signer and the now uninstalled validation, anticipating a revert. - _userOpTransfer(address(account1), owner1Key, expectedRevertdata, transferAmount, false); + // TODO: this case is now unreachable because the fallback signer will have the same entity id as the + // original signer. Investigate whether or not this case is still reachable in the future. + + // // Build expected revert data for a UO with the original signer. + // bytes memory expectedRevertdata = abi.encodeWithSelector( + // IEntryPoint.FailedOpWithRevert.selector, + // 0, + // "AA23 reverted", + // abi.encodeWithSelector( + // ModularAccountBase.ValidationFunctionMissing.selector, ModularAccountBase.execute.selector + // ) + // ); + + // // Execute a UO with the original signer and the now uninstalled validation, anticipating a revert. + // _userOpTransfer(address(account1), owner1Key, expectedRevertdata, transferAmount, false); - expectedRevertdata = abi.encodeWithSelector(IEntryPoint.FailedOp.selector, 0, "AA24 signature error"); + bytes memory expectedRevertdata = + abi.encodeWithSelector(IEntryPoint.FailedOp.selector, 0, "AA24 signature error"); // Execute a UO with the original signer and the fallback validation, anticipating a revert. _userOpTransfer(address(account1), owner1Key, expectedRevertdata, transferAmount, true); @@ -163,11 +167,9 @@ contract UpgradeToSmaTest is AccountTestBase { bytes memory encodedCall, bytes memory expectedRevertData ) internal { - uint256 nonce = entryPoint.getNonce(account, 0); - PackedUserOperation memory userOp = PackedUserOperation({ sender: account, - nonce: nonce, + nonce: _encodeNextNonce(account, FALLBACK_VALIDATION, true), initCode: hex"", callData: encodedCall, accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), @@ -180,8 +182,7 @@ contract UpgradeToSmaTest is AccountTestBase { bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerKey, userOpHash.toEthSignedMessageHash()); - userOp.signature = - _encodeSignature(FALLBACK_VALIDATION, GLOBAL_VALIDATION, abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); + userOp.signature = _encodeSignature(abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; diff --git a/test/account/ValidationIntersection.t.sol b/test/account/ValidationIntersection.t.sol index 6bbb0509..f42ea6d6 100644 --- a/test/account/ValidationIntersection.t.sol +++ b/test/account/ValidationIntersection.t.sol @@ -50,20 +50,11 @@ contract ValidationIntersectionTest is AccountTestBase { oneHookModule = new MockUserOpValidation1HookModule(); twoHookModule = new MockUserOpValidation2HookModule(); - noHookValidation = ModuleEntityLib.pack({ - addr: address(noHookModule), - entityId: uint32(MockBaseUserOpValidationModule.EntityId.USER_OP_VALIDATION) - }); + noHookValidation = ModuleEntityLib.pack({addr: address(noHookModule), entityId: uint32(1)}); - oneHookValidation = ModuleEntityLib.pack({ - addr: address(oneHookModule), - entityId: uint32(MockBaseUserOpValidationModule.EntityId.USER_OP_VALIDATION) - }); + oneHookValidation = ModuleEntityLib.pack({addr: address(oneHookModule), entityId: uint32(2)}); - twoHookValidation = ModuleEntityLib.pack({ - addr: address(twoHookModule), - entityId: uint32(MockBaseUserOpValidationModule.EntityId.USER_OP_VALIDATION) - }); + twoHookValidation = ModuleEntityLib.pack({addr: address(twoHookModule), entityId: uint32(3)}); bytes4[] memory validationSelectors = new bytes4[](1); validationSelectors[0] = MockUserOpValidationModule.foo.selector; @@ -113,7 +104,8 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(noHookModule.foo.selector); - userOp.signature = _encodeSignature(noHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); + userOp.nonce = _encodeNonce(noHookValidation, SELECTOR_ASSOCIATED_V, 0); + userOp.signature = _encodeSignature(""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); @@ -130,7 +122,8 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(oneHookModule.bar.selector); - userOp.signature = _encodeSignature(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); + userOp.nonce = _encodeNonce(oneHookValidation, SELECTOR_ASSOCIATED_V, 0); + userOp.signature = _encodeSignature(""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); @@ -148,7 +141,8 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(oneHookModule.bar.selector); - userOp.signature = _encodeSignature(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); + userOp.nonce = _encodeNonce(oneHookValidation, SELECTOR_ASSOCIATED_V, 0); + userOp.signature = _encodeSignature(""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); @@ -171,7 +165,8 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(oneHookModule.bar.selector); - userOp.signature = _encodeSignature(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); + userOp.nonce = _encodeNonce(oneHookValidation, SELECTOR_ASSOCIATED_V, 0); + userOp.signature = _encodeSignature(""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); @@ -193,7 +188,8 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(oneHookModule.bar.selector); - userOp.signature = _encodeSignature(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); + userOp.nonce = _encodeNonce(oneHookValidation, SELECTOR_ASSOCIATED_V, 0); + userOp.signature = _encodeSignature(""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); @@ -213,7 +209,8 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(oneHookModule.bar.selector); - userOp.signature = _encodeSignature(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); + userOp.nonce = _encodeNonce(oneHookValidation, SELECTOR_ASSOCIATED_V, 0); + userOp.signature = _encodeSignature(""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); @@ -239,7 +236,8 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(oneHookModule.bar.selector); - userOp.signature = _encodeSignature(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); + userOp.nonce = _encodeNonce(oneHookValidation, SELECTOR_ASSOCIATED_V, 0); + userOp.signature = _encodeSignature(""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); @@ -263,7 +261,8 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(oneHookModule.bar.selector); - userOp.signature = _encodeSignature(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); + userOp.nonce = _encodeNonce(oneHookValidation, SELECTOR_ASSOCIATED_V, 0); + userOp.signature = _encodeSignature(""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); @@ -286,7 +285,8 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(twoHookModule.baz.selector); - userOp.signature = _encodeSignature(twoHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); + userOp.nonce = _encodeNonce(twoHookValidation, SELECTOR_ASSOCIATED_V, 0); + userOp.signature = _encodeSignature(""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); @@ -304,8 +304,8 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(twoHookModule.baz.selector); - - userOp.signature = _encodeSignature(twoHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); + userOp.nonce = _encodeNonce(twoHookValidation, SELECTOR_ASSOCIATED_V, 0); + userOp.signature = _encodeSignature(""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); diff --git a/test/libraries/ValidationLocatorLib.t.sol b/test/libraries/ValidationLocatorLib.t.sol new file mode 100644 index 00000000..b0a07cc6 --- /dev/null +++ b/test/libraries/ValidationLocatorLib.t.sol @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.26; + +import {Test} from "forge-std/Test.sol"; + +import {DIRECT_CALL_VALIDATION_ENTITYID} from "@erc6900/reference-implementation/helpers/Constants.sol"; +import {ModuleEntity, ModuleEntityLib} from "@erc6900/reference-implementation/libraries/ModuleEntityLib.sol"; +import { + ValidationConfig, + ValidationConfigLib +} from "@erc6900/reference-implementation/libraries/ValidationConfigLib.sol"; + +import { + ValidationLocator, + ValidationLocatorLib, + ValidationLookupKey +} from "../../src/libraries/ValidationLocatorLib.sol"; + +contract ValidationLocatorLibTest is Test { + function testFuzz_loadFromNonce_regular(uint32 entityId, bool isGlobal, bool isDeferredAction) public pure { + uint256 nonce = ValidationLocatorLib.packNonce(entityId, isGlobal, isDeferredAction); + + ValidationLocator result = ValidationLocatorLib.loadFromNonce(nonce); + + ValidationLocator expected = ValidationLocatorLib.pack(entityId, isGlobal, isDeferredAction); + + assertEq(ValidationLocator.unwrap(result), ValidationLocator.unwrap(expected)); + } + + function testFuzz_loadFromNonce_directCall(address directCallValidation, bool isGlobal, bool isDeferredAction) + public + pure + { + uint256 nonce = ValidationLocatorLib.packNonceDirectCall(directCallValidation, isGlobal, isDeferredAction); + + ValidationLocator result = ValidationLocatorLib.loadFromNonce(nonce); + + ValidationLocator expected = + ValidationLocatorLib.packDirectCall(directCallValidation, isGlobal, isDeferredAction); + + assertEq(ValidationLocator.unwrap(result), ValidationLocator.unwrap(expected)); + } + + function testFuzz_loadFromSignature_regular( + uint32 entityId, + bool isGlobal, + bool isDeferredAction, + bytes memory signature + ) public view { + bytes memory finalSignature = + ValidationLocatorLib.packSignature(entityId, isGlobal, isDeferredAction, signature); + + (ValidationLocator result, bytes memory remainder) = this.loadFromSignature(finalSignature); + + ValidationLocator expected = ValidationLocatorLib.pack(entityId, isGlobal, isDeferredAction); + + assertEq(ValidationLocator.unwrap(result), ValidationLocator.unwrap(expected)); + assertEq(remainder, signature); + } + + function testFuzz_loadFromSignature_directCall( + address directCallValidation, + bool isGlobal, + bool isDeferredAction, + bytes memory signature + ) public view { + bytes memory finalSignature = ValidationLocatorLib.packSignatureDirectCall( + directCallValidation, isGlobal, isDeferredAction, signature + ); + + (ValidationLocator result, bytes memory remainder) = this.loadFromSignature(finalSignature); + + ValidationLocator expected = + ValidationLocatorLib.packDirectCall(directCallValidation, isGlobal, isDeferredAction); + + assertEq(ValidationLocator.unwrap(result), ValidationLocator.unwrap(expected)); + assertEq(remainder, signature); + } + + function testFuzz_validationLookupKey_regular(uint32 entityId, bool isGlobal, bool isDeferredAction) + public + pure + { + ValidationLocator locator = ValidationLocatorLib.pack(entityId, isGlobal, isDeferredAction); + + ValidationLookupKey result = locator.lookupKey(); + + ValidationLookupKey expected = ValidationLocatorLib.pack(entityId, false, false).lookupKey(); + + assertEq(ValidationLookupKey.unwrap(result), ValidationLookupKey.unwrap(expected)); + } + + function testFuzz_validationLookupKey_directCall( + address directCallValidation, + bool isGlobal, + bool isDeferredAction + ) public pure { + ValidationLocator locator = + ValidationLocatorLib.packDirectCall(directCallValidation, isGlobal, isDeferredAction); + + ValidationLookupKey result = locator.lookupKey(); + + ValidationLookupKey expected = + ValidationLocatorLib.packDirectCall(directCallValidation, false, false).lookupKey(); + + assertEq(ValidationLookupKey.unwrap(result), ValidationLookupKey.unwrap(expected)); + } + + function testFuzz_configToLookupKey( + ModuleEntity validationEntity, + bool isGlobal, + bool isSignatureValidation, + bool isUserOpValidation + ) public pure { + (address module, uint32 entityId) = ModuleEntityLib.unpack(validationEntity); + + ValidationConfig input = + ValidationConfigLib.pack(validationEntity, isGlobal, isSignatureValidation, isUserOpValidation); + + ValidationLookupKey result = ValidationLocatorLib.configToLookupKey(input); + + ValidationLookupKey expected; + + if (entityId == DIRECT_CALL_VALIDATION_ENTITYID) { + expected = ValidationLocatorLib.packDirectCall(module, false, false).lookupKey(); + } else { + expected = ValidationLocatorLib.pack(entityId, false, false).lookupKey(); + } + + assertEq(ValidationLookupKey.unwrap(result), ValidationLookupKey.unwrap(expected)); + } + + function testFuzz_moduleEntityToLookupKey(ModuleEntity validationEntity) public pure { + (address module, uint32 entityId) = ModuleEntityLib.unpack(validationEntity); + + ValidationLookupKey result = ValidationLocatorLib.moduleEntityToLookupKey(validationEntity); + + ValidationLookupKey expected; + + if (entityId == DIRECT_CALL_VALIDATION_ENTITYID) { + expected = ValidationLocatorLib.packDirectCall(module, false, false).lookupKey(); + } else { + expected = ValidationLocatorLib.pack(entityId, false, false).lookupKey(); + } + + assertEq(ValidationLookupKey.unwrap(result), ValidationLookupKey.unwrap(expected)); + } + + // External function to convert to calldata + function loadFromSignature(bytes calldata finalSignature) + external + pure + returns (ValidationLocator, bytes memory) + { + (ValidationLocator res, bytes calldata remainder) = ValidationLocatorLib.loadFromSignature(finalSignature); + return (res, remainder); + } +} diff --git a/test/mocks/modules/ValidationModuleMocks.sol b/test/mocks/modules/ValidationModuleMocks.sol index e287d2a2..9a666882 100644 --- a/test/mocks/modules/ValidationModuleMocks.sol +++ b/test/mocks/modules/ValidationModuleMocks.sol @@ -67,16 +67,13 @@ abstract contract MockBaseUserOpValidationModule is revert NotImplemented(); } - function validateUserOp(uint32 entityId, PackedUserOperation calldata, bytes32) + function validateUserOp(uint32, PackedUserOperation calldata, bytes32) external view override returns (uint256) { - if (entityId == uint32(EntityId.USER_OP_VALIDATION)) { - return _userOpValidationFunctionData; - } - revert NotImplemented(); + return _userOpValidationFunctionData; } function preSignatureValidationHook(uint32, address, bytes32, bytes calldata) external pure override {} diff --git a/test/modules/AllowlistERC20TokenLimit.t.sol b/test/modules/AllowlistERC20TokenLimit.t.sol index d1289852..df99e664 100644 --- a/test/modules/AllowlistERC20TokenLimit.t.sol +++ b/test/modules/AllowlistERC20TokenLimit.t.sol @@ -94,14 +94,14 @@ contract AllowlistERC20TokenLimitTest is AccountTestBase { function _getPackedUO(bytes memory callData) internal view returns (PackedUserOperation memory uo) { uo = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(validationFunction, GLOBAL_V, 0), initCode: "", callData: abi.encodePacked(ModularAccountBase.executeUserOp.selector, callData), accountGasLimits: bytes32(bytes16(uint128(200_000))) | bytes32(uint256(200_000)), preVerificationGas: 200_000, gasFees: bytes32(uint256(uint128(0))), paymasterAndData: "", - signature: _encodeSignature(validationFunction, GLOBAL_VALIDATION, "") + signature: _encodeSignature("") }); } diff --git a/test/modules/AllowlistModule.t.sol b/test/modules/AllowlistModule.t.sol index 0284779e..8cb7a922 100644 --- a/test/modules/AllowlistModule.t.sol +++ b/test/modules/AllowlistModule.t.sol @@ -274,11 +274,9 @@ contract AllowlistModuleTest is CustomValidationTestBase { _customValidationSetup(); - uint256 nonce = entryPoint.getNonce(address(account1), 0); - PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: nonce, + nonce: _encodeNextNonce(address(account1), _signerValidation, true), initCode: hex"", callData: abi.encodeCall( account1.execute, (address(counters[0]), 0, abi.encodeCall(Counter.increment, ())) @@ -293,8 +291,7 @@ contract AllowlistModuleTest is CustomValidationTestBase { bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, MessageHashUtils.toEthSignedMessageHash(userOpHash)); - userOp.signature = - _encodeSignature(_signerValidation, GLOBAL_VALIDATION, abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); + userOp.signature = _encodeSignature(abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -316,12 +313,7 @@ contract AllowlistModuleTest is CustomValidationTestBase { PreValidationHookData[] memory preValidationHookData = new PreValidationHookData[](1); preValidationHookData[0] = PreValidationHookData({index: uint8(0), validationData: "abcd"}); - userOp.signature = _encodeSignature( - _signerValidation, - GLOBAL_VALIDATION, - preValidationHookData, - abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v) - ); + userOp.signature = _encodeSignature(preValidationHookData, abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); userOps[0] = userOp; diff --git a/test/modules/NativeTokenLimitModule.t.sol b/test/modules/NativeTokenLimitModule.t.sol index 0a4e33e4..be07bde1 100644 --- a/test/modules/NativeTokenLimitModule.t.sol +++ b/test/modules/NativeTokenLimitModule.t.sol @@ -45,7 +45,7 @@ contract NativeTokenLimitModuleTest is AccountTestBase { NativeTokenLimitModule public module = new NativeTokenLimitModule(); uint256 public spendLimit = 10 ether; - uint32 public entityId = 0; + uint32 public entityId = 1; function setUp() public override { _revertSnapshot = vm.snapshotState(); @@ -103,14 +103,14 @@ contract NativeTokenLimitModuleTest is AccountTestBase { { uo = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(ModuleEntityLib.pack(address(validationModule), entityId), GLOBAL_V, 0), initCode: "", callData: abi.encodePacked(ModularAccountBase.executeUserOp.selector, callData), accountGasLimits: bytes32(bytes16(uint128(gas1))) | bytes32(uint256(gas2)), preVerificationGas: gas3, gasFees: bytes32(uint256(uint128(gasPrice))), paymasterAndData: "", - signature: _encodeSignature(ModuleEntityLib.pack(address(validationModule), 0), GLOBAL_VALIDATION, "") + signature: _encodeSignature("") }); } @@ -118,11 +118,11 @@ contract NativeTokenLimitModuleTest is AccountTestBase { vm.startPrank(address(entryPoint)); // uses 10e - 200000 of gas - assertEq(module.limits(0, address(account1)), 10 ether); + assertEq(module.limits(entityId, address(account1)), 10 ether); uint256 result = account1.validateUserOp( _getPackedUO(100_000, 100_000, 10 ether - 400_000, 1, _getExecuteWithValue(0)), bytes32(0), 0 ); - assertEq(module.limits(0, address(account1)), 200_000); + assertEq(module.limits(entityId, address(account1)), 200_000); uint256 expected = uint256(type(uint48).max) << 160; assertEq(result, expected); @@ -145,15 +145,15 @@ contract NativeTokenLimitModuleTest is AccountTestBase { vm.startPrank(address(entryPoint)); // uses 5e of native tokens - assertEq(module.limits(0, address(account1)), 10 ether); + assertEq(module.limits(entityId, address(account1)), 10 ether); account1.executeUserOp(_getPackedUO(0, 0, 0, 0, _getExecuteWithValue(5 ether)), bytes32(0)); - assertEq(module.limits(0, address(account1)), 5 ether); + assertEq(module.limits(entityId, address(account1)), 5 ether); // uses 5e + 1wei of native tokens vm.expectRevert( abi.encodeWithSelector( ExecutionLib.PreExecHookReverted.selector, - ModuleEntityLib.pack(address(module), uint32(0)), + ModuleEntityLib.pack(address(module), entityId), abi.encodePacked(NativeTokenLimitModule.ExceededNativeTokenLimit.selector) ) ); @@ -163,7 +163,7 @@ contract NativeTokenLimitModuleTest is AccountTestBase { } function test_userOp_invalidPaymaster() public withSMATest { - assertEq(module.limits(0, address(account1)), 10 ether); + assertEq(module.limits(entityId, address(account1)), 10 ether); PackedUserOperation[] memory uos = new PackedUserOperation[](1); uos[0] = _getPackedUO(200_000, 200_000, 200_000, 1, _getExecuteWithValue(5 ether)); uos[0].paymasterAndData = new bytes(52); @@ -189,11 +189,11 @@ contract NativeTokenLimitModuleTest is AccountTestBase { calls[2] = Call({target: recipient, value: 5 ether + 100_000, data: ""}); vm.startPrank(address(entryPoint)); - assertEq(module.limits(0, address(account1)), 10 ether); + assertEq(module.limits(entityId, address(account1)), 10 ether); account1.executeUserOp( _getPackedUO(0, 0, 0, 0, abi.encodeCall(IModularAccount.executeBatch, (calls))), bytes32(0) ); - assertEq(module.limits(0, address(account1)), 10 ether - 6 ether - 100_001); + assertEq(module.limits(entityId, address(account1)), 10 ether - 6 ether - 100_001); assertEq(recipient.balance, 6 ether + 100_001); vm.stopPrank(); @@ -203,9 +203,9 @@ contract NativeTokenLimitModuleTest is AccountTestBase { vm.startPrank(address(entryPoint)); // uses 5e of native tokens - assertEq(module.limits(0, address(account1)), 10 ether); + assertEq(module.limits(entityId, address(account1)), 10 ether); account1.executeUserOp(_getPackedUO(0, 0, 0, 0, _getPerformCreateCalldata(5 ether)), bytes32(0)); - assertEq(module.limits(0, address(account1)), 5 ether); + assertEq(module.limits(entityId, address(account1)), 5 ether); vm.stopPrank(); } @@ -214,20 +214,20 @@ contract NativeTokenLimitModuleTest is AccountTestBase { vm.startPrank(address(entryPoint)); // uses 5e of native tokens - assertEq(module.limits(0, address(account1)), 10 ether); + assertEq(module.limits(entityId, address(account1)), 10 ether); account1.executeUserOp(_getPackedUO(0, 0, 0, 0, _getPerformCreate2Calldata(5 ether, 0)), bytes32(0)); - assertEq(module.limits(0, address(account1)), 5 ether); + assertEq(module.limits(entityId, address(account1)), 5 ether); vm.stopPrank(); } function test_userOp_combinedExecLimit_success() public withSMATest { - assertEq(module.limits(0, address(account1)), 10 ether); + assertEq(module.limits(entityId, address(account1)), 10 ether); PackedUserOperation[] memory uos = new PackedUserOperation[](1); uos[0] = _getPackedUO(200_000, 200_000, 200_000, 1, _getExecuteWithValue(5 ether)); entryPoint.handleOps(uos, beneficiary); - assertEq(module.limits(0, address(account1)), 5 ether - 600_000); + assertEq(module.limits(entityId, address(account1)), 5 ether - 600_000); assertEq(recipient.balance, 5 ether); } @@ -238,38 +238,38 @@ contract NativeTokenLimitModuleTest is AccountTestBase { calls[2] = Call({target: recipient, value: 5 ether + 100_000, data: ""}); vm.startPrank(address(entryPoint)); - assertEq(module.limits(0, address(account1)), 10 ether); + assertEq(module.limits(entityId, address(account1)), 10 ether); PackedUserOperation[] memory uos = new PackedUserOperation[](1); uos[0] = _getPackedUO(200_000, 200_000, 200_000, 1, abi.encodeCall(IModularAccount.executeBatch, (calls))); entryPoint.handleOps(uos, beneficiary); - assertEq(module.limits(0, address(account1)), 10 ether - 6 ether - 700_001); + assertEq(module.limits(entityId, address(account1)), 10 ether - 6 ether - 700_001); assertEq(recipient.balance, 6 ether + 100_001); vm.stopPrank(); } function test_userOp_combinedExecLimit_failExec() public withSMATest { - assertEq(module.limits(0, address(account1)), 10 ether); + assertEq(module.limits(entityId, address(account1)), 10 ether); PackedUserOperation[] memory uos = new PackedUserOperation[](1); uos[0] = _getPackedUO(200_000, 200_000, 200_000, 1, _getExecuteWithValue(10 ether)); entryPoint.handleOps(uos, beneficiary); - assertEq(module.limits(0, address(account1)), 10 ether - 600_000); + assertEq(module.limits(entityId, address(account1)), 10 ether - 600_000); assertEq(recipient.balance, 0); } function test_userOp_paymaster() public withSMATest { vm.startPrank(address(entryPoint)); - assertEq(module.limits(0, address(account1)), 10 ether); + assertEq(module.limits(entityId, address(account1)), 10 ether); PackedUserOperation memory uo = _getPackedUO(200_000, 200_000, 200_000, 1, _getExecuteWithValue(10 ether)); uo.paymasterAndData = abi.encodePacked(address(account1), uint128(uint256(1_000_000)), uint128(uint256(1_000_000))); uint256 validationData = account1.validateUserOp(uo, bytes32(0), 0); assertEq(validationData & 0x1, 0); // check for success - assertEq(module.limits(0, address(account1)), 10 ether); // limit should not decrease + assertEq(module.limits(entityId, address(account1)), 10 ether); // limit should not decrease assertEq(recipient.balance, 0); vm.stopPrank(); } @@ -280,24 +280,24 @@ contract NativeTokenLimitModuleTest is AccountTestBase { vm.startPrank(address(entryPoint)); - assertEq(module.limits(0, address(account1)), 10 ether); + assertEq(module.limits(entityId, address(account1)), 10 ether); PackedUserOperation memory uo = _getPackedUO(200_000, 200_000, 200_000, 1, _getExecuteWithValue(5 ether)); uo.paymasterAndData = abi.encodePacked(address(account1), uint128(uint256(200_000)), uint128(uint256(200_000))); uint256 validationData = account1.validateUserOp(uo, bytes32(0), 0); assertEq(validationData & 0x1, 0); // check for success - assertEq(module.limits(0, address(account1)), 10 ether - 200_000 * 5); // limit should not decrease + assertEq(module.limits(entityId, address(account1)), 10 ether - 200_000 * 5); // limit should not decrease assertEq(recipient.balance, 0); vm.stopPrank(); } function test_runtime_executeLimit() public withSMATest { - assertEq(module.limits(0, address(account1)), 10 ether); + assertEq(module.limits(entityId, address(account1)), 10 ether); account1.executeWithRuntimeValidation( _getExecuteWithValue(5 ether), _encodeSignature(validationFunction, 1, "") ); - assertEq(module.limits(0, address(account1)), 5 ether); + assertEq(module.limits(entityId, address(account1)), 5 ether); } function test_runtime_executeBatchLimit() public withSMATest { @@ -306,31 +306,31 @@ contract NativeTokenLimitModuleTest is AccountTestBase { calls[1] = Call({target: recipient, value: 1 ether, data: ""}); calls[2] = Call({target: recipient, value: 5 ether + 100_000, data: ""}); - assertEq(module.limits(0, address(account1)), 10 ether); + assertEq(module.limits(entityId, address(account1)), 10 ether); account1.executeWithRuntimeValidation( abi.encodeCall(IModularAccount.executeBatch, (calls)), _encodeSignature(validationFunction, 1, "") ); - assertEq(module.limits(0, address(account1)), 4 ether - 100_001); + assertEq(module.limits(entityId, address(account1)), 4 ether - 100_001); } function test_runtime_performCreateLimit() public withSMATest { - assertEq(module.limits(0, address(account1)), 10 ether); + assertEq(module.limits(entityId, address(account1)), 10 ether); bytes memory b = account1.executeWithRuntimeValidation( _getPerformCreateCalldata(5 ether), _encodeSignature(validationFunction, 1, "") ); - assertEq(module.limits(0, address(account1)), 5 ether); + assertEq(module.limits(entityId, address(account1)), 5 ether); address deployed = abi.decode(b, (address)); assertEq(deployed.balance, 5 ether); } function test_runtime_performCreate2Limit() public withSMATest { - assertEq(module.limits(0, address(account1)), 10 ether); + assertEq(module.limits(entityId, address(account1)), 10 ether); bytes memory b = account1.executeWithRuntimeValidation( _getPerformCreate2Calldata({value: 5 ether, salt: bytes32(0)}), _encodeSignature(validationFunction, 1, "") ); - assertEq(module.limits(0, address(account1)), 5 ether); + assertEq(module.limits(entityId, address(account1)), 5 ether); address deployed = abi.decode(b, (address)); assertEq(deployed.balance, 5 ether); @@ -353,9 +353,7 @@ contract NativeTokenLimitModuleTest is AccountTestBase { PreValidationHookData[] memory preValidationHookData = new PreValidationHookData[](1); preValidationHookData[0] = PreValidationHookData({index: uint8(0), validationData: "abcd"}); - uos[0].signature = _encodeSignature( - ModuleEntityLib.pack(address(validationModule), 0), GLOBAL_VALIDATION, preValidationHookData, "" - ); + uos[0].signature = _encodeSignature(preValidationHookData, ""); vm.prank(beneficiary); vm.expectRevert( @@ -374,7 +372,7 @@ contract NativeTokenLimitModuleTest is AccountTestBase { } function test_deleteSingleSessionKey() public withSMATest { - uint32 newEntityId = 1; + uint32 newEntityId = 2; // Add new entity, delete latest entity, old limit should still work ModuleEntity[] memory preValidationHooks = new ModuleEntity[](1); diff --git a/test/modules/PaymasterGuardModule.t.sol b/test/modules/PaymasterGuardModule.t.sol index 0994b5ab..a7810e14 100644 --- a/test/modules/PaymasterGuardModule.t.sol +++ b/test/modules/PaymasterGuardModule.t.sol @@ -152,7 +152,7 @@ contract PaymasterGuardModuleTest is AccountTestBase { { return PackedUserOperation({ sender: accountAddr, - nonce: 0, + nonce: _encodeNonce(ModuleEntityLib.pack(address(singleSignerValidationModule), ENTITY_ID), GLOBAL_V, 0), initCode: "", callData: abi.encodePacked( ModularAccountBase.executeUserOp.selector, abi.encodeCall(account1.execute, (owner1, 0, hex"")) @@ -177,11 +177,7 @@ contract PaymasterGuardModuleTest is AccountTestBase { PackedUserOperation memory userOp = _packUO(address(account1), abi.encodePacked(paymaster1, "")); bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = _encodeSignature( - ModuleEntityLib.pack(address(singleSignerValidationModule), ENTITY_ID), - GLOBAL_VALIDATION, - abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v) - ); + userOp.signature = _encodeSignature(abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); vm.prank(address(entryPoint)); account1.validateUserOp(userOp, userOpHash, 0); } @@ -190,11 +186,7 @@ contract PaymasterGuardModuleTest is AccountTestBase { PackedUserOperation memory userOp = _packUO(address(account1), abi.encodePacked(paymaster2, "")); bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = _encodeSignature( - ModuleEntityLib.pack(address(singleSignerValidationModule), ENTITY_ID), - GLOBAL_VALIDATION, - abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v) - ); + userOp.signature = _encodeSignature(abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); vm.expectRevert( abi.encodeWithSelector( diff --git a/test/modules/TimeRangeModule.t.sol b/test/modules/TimeRangeModule.t.sol index f8997a7c..ac0b95c6 100644 --- a/test/modules/TimeRangeModule.t.sol +++ b/test/modules/TimeRangeModule.t.sol @@ -152,7 +152,7 @@ contract TimeRangeModuleTest is CustomValidationTestBase { PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(_signerValidation, GLOBAL_V, 0), initCode: hex"", callData: abi.encodeCall(ModularAccountBase.execute, (makeAddr("recipient"), 0 wei, "")), accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), @@ -165,8 +165,7 @@ contract TimeRangeModuleTest is CustomValidationTestBase { bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, MessageHashUtils.toEthSignedMessageHash(userOpHash)); - userOp.signature = - _encodeSignature(_signerValidation, GLOBAL_VALIDATION, abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); + userOp.signature = _encodeSignature(abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); vm.prank(address(entryPoint)); uint256 validationData = account1.validateUserOp(userOp, userOpHash, 0); @@ -179,7 +178,7 @@ contract TimeRangeModuleTest is CustomValidationTestBase { ); } - function testFuzz_timeRangeModule_userOp(uint48 time1, uint48 time2) public { + function testFuzz_timeRangeModule_userOp_success(uint48 time1, uint48 time2) public { vm.assume(time1 != time2); validUntil = time1 > time2 ? time1 : time2; validAfter = time1 < time2 ? time1 : time2; @@ -188,7 +187,7 @@ contract TimeRangeModuleTest is CustomValidationTestBase { PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(_signerValidation, GLOBAL_V, 0), initCode: hex"", callData: abi.encodeCall(ModularAccountBase.execute, (makeAddr("recipient"), 0 wei, "")), accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), @@ -201,8 +200,7 @@ contract TimeRangeModuleTest is CustomValidationTestBase { bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, MessageHashUtils.toEthSignedMessageHash(userOpHash)); - userOp.signature = - _encodeSignature(_signerValidation, GLOBAL_VALIDATION, abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); + userOp.signature = _encodeSignature(abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); vm.prank(address(entryPoint)); uint256 validationData = account1.validateUserOp(userOp, userOpHash, 0); @@ -224,7 +222,7 @@ contract TimeRangeModuleTest is CustomValidationTestBase { PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(_signerValidation, GLOBAL_V, 0), initCode: hex"", callData: abi.encodeCall(ModularAccountBase.execute, (makeAddr("recipient"), 0 wei, "")), accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), @@ -236,8 +234,7 @@ contract TimeRangeModuleTest is CustomValidationTestBase { bytes32 userOpHash = entryPoint.getUserOpHash(userOp); // Generate a bad signature - userOp.signature = - _encodeSignature(_signerValidation, GLOBAL_VALIDATION, abi.encodePacked(EOA_TYPE_SIGNATURE, "abcd")); + userOp.signature = _encodeSignature(abi.encodePacked(EOA_TYPE_SIGNATURE, "abcd")); vm.prank(address(entryPoint)); uint256 validationData = account1.validateUserOp(userOp, userOpHash, 0); @@ -338,7 +335,7 @@ contract TimeRangeModuleTest is CustomValidationTestBase { PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), - nonce: 0, + nonce: _encodeNonce(_signerValidation, GLOBAL_V, 0), initCode: hex"", callData: abi.encodeCall(ModularAccountBase.execute, (makeAddr("recipient"), 0 wei, "")), accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), @@ -354,7 +351,7 @@ contract TimeRangeModuleTest is CustomValidationTestBase { PreValidationHookData[] memory preValidationHookData = new PreValidationHookData[](1); preValidationHookData[0] = PreValidationHookData({index: uint8(0), validationData: "abcd"}); - userOp.signature = _encodeSignature(_signerValidation, GLOBAL_VALIDATION, preValidationHookData, ""); + userOp.signature = _encodeSignature(preValidationHookData, ""); vm.prank(address(entryPoint)); vm.expectRevert( diff --git a/test/modules/WebAuthnValidationModule.t.sol b/test/modules/WebAuthnValidationModule.t.sol index 48c1e40e..b6ba6d25 100644 --- a/test/modules/WebAuthnValidationModule.t.sol +++ b/test/modules/WebAuthnValidationModule.t.sol @@ -123,6 +123,7 @@ contract WebAuthnValidationModuleTest is AccountTestBase { function test_uoValidation() external withSMATest { PackedUserOperation memory uo; uo.sender = account; + uo.nonce = _encodeNextNonce(account, ModuleEntityLib.pack(address(module), entityId), true); uo.callData = abi.encodeCall(ModularAccountBase.execute, (CODELESS_ADDRESS, 0, new bytes(0))); bytes32 uoHash = entryPoint.getUserOpHash(uo); uo.signature = _getUOSigForChallenge(uoHash.toEthSignedMessageHash(), 0, 0); @@ -134,6 +135,7 @@ contract WebAuthnValidationModuleTest is AccountTestBase { function testFuzz_uoValidation_shouldFail(uint256 sigR, uint256 sigS) external { PackedUserOperation memory uo; uo.sender = account; + uo.nonce = _encodeNextNonce(account, ModuleEntityLib.pack(address(module), entityId), true); uo.callData = abi.encodeCall(ModularAccountBase.execute, (CODELESS_ADDRESS, 0, new bytes(0))); bytes32 uoHash = entryPoint.getUserOpHash(uo); @@ -162,8 +164,6 @@ contract WebAuthnValidationModuleTest is AccountTestBase { s = bytes32(Utils.normalizeS(uint256(s))); return _encodeSignature( - ModuleEntityLib.pack(address(module), entityId), - uint8(1), abi.encode( WebAuthn.WebAuthnAuth({ authenticatorData: webAuthn.authenticatorData, diff --git a/test/utils/AccountTestBase.sol b/test/utils/AccountTestBase.sol index 8a0bf2d3..80ef52d9 100644 --- a/test/utils/AccountTestBase.sol +++ b/test/utils/AccountTestBase.sol @@ -166,11 +166,9 @@ abstract contract AccountTestBase is OptimizedTest, ModuleSignatureUtils { bytes memory callData, bytes memory expectedRevertData ) internal { - uint256 nonce = entryPoint.getNonce(address(account), 0); - PackedUserOperation memory userOp = PackedUserOperation({ sender: account, - nonce: nonce, + nonce: _encodeNextNonce(account, _signerValidation, true), initCode: hex"", callData: callData, accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), @@ -183,8 +181,7 @@ abstract contract AccountTestBase is OptimizedTest, ModuleSignatureUtils { bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerKey, userOpHash.toEthSignedMessageHash()); - userOp.signature = - _encodeSignature(_signerValidation, GLOBAL_VALIDATION, abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); + userOp.signature = _encodeSignature(abi.encodePacked(EOA_TYPE_SIGNATURE, r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -295,6 +292,25 @@ abstract contract AccountTestBase is OptimizedTest, ModuleSignatureUtils { _signerValidation = FALLBACK_VALIDATION; } + function _encodeNextNonce( + address account, + ModuleEntity validationFunction, + bool isGlobal, + bool hasDeferredAction + ) internal view returns (uint256) { + uint256 validationEncodedNonce = _encodeNonce(validationFunction, isGlobal, hasDeferredAction, uint64(0)); + + return entryPoint.getNonce(account, uint192(validationEncodedNonce >> 64)); + } + + function _encodeNextNonce(address account, ModuleEntity validationFunction, bool isGlobal) + internal + view + returns (uint256) + { + return _encodeNextNonce(account, validationFunction, isGlobal, false); + } + // Uses state vars: // - _signerValidation // - ecdsaValidation, when not SMA @@ -338,13 +354,7 @@ abstract contract AccountTestBase is OptimizedTest, ModuleSignatureUtils { ); } - return _encodeDeferredInstallUOSignature( - uoValidationFunction.moduleEntity(), - (uoValidationFunction.isGlobal()) ? GLOBAL_VALIDATION : SELECTOR_ASSOCIATED_VALIDATION, - deferredValidationDatas, - deferredValidationSig, - uoSig - ); + return _encodeDeferredInstallUOSignature(deferredValidationDatas, deferredValidationSig, uoSig); } // helper function to compress 2 gas values into a single bytes32 diff --git a/test/utils/ModuleSignatureUtils.sol b/test/utils/ModuleSignatureUtils.sol index 19422c08..4a10e67e 100644 --- a/test/utils/ModuleSignatureUtils.sol +++ b/test/utils/ModuleSignatureUtils.sol @@ -19,7 +19,10 @@ pragma solidity ^0.8.26; import {Vm} from "forge-std/Vm.sol"; -import {RESERVED_VALIDATION_DATA_INDEX} from "@erc6900/reference-implementation/helpers/Constants.sol"; +import { + DIRECT_CALL_VALIDATION_ENTITYID, + RESERVED_VALIDATION_DATA_INDEX +} from "@erc6900/reference-implementation/helpers/Constants.sol"; import {ModuleEntity} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol"; import {ModuleEntityLib} from "@erc6900/reference-implementation/libraries/ModuleEntityLib.sol"; import { @@ -29,25 +32,34 @@ import { import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {ModularAccount} from "../../src/account/ModularAccount.sol"; +import {ValidationLocator, ValidationLocatorLib} from "../../src/libraries/ValidationLocatorLib.sol"; /// @dev Utilities for encoding signatures for modular account validation. Used for encoding user op, runtime, and /// 1271 signatures. contract ModuleSignatureUtils { using ModuleEntityLib for ModuleEntity; + enum ValidationType { + SELECTOR_ASSOCIATED, + GLOBAL + } + struct PreValidationHookData { uint8 index; bytes validationData; } uint8 public constant SELECTOR_ASSOCIATED_VALIDATION = 0; + ValidationType public constant SELECTOR_ASSOCIATED_V = ValidationType.SELECTOR_ASSOCIATED; uint8 public constant GLOBAL_VALIDATION = 1; + ValidationType public constant GLOBAL_V = ValidationType.GLOBAL; + uint8 public constant HAS_DEFERRED_ACTION_BIT = 2; uint8 public constant EOA_TYPE_SIGNATURE = 0; string internal constant _DEFERRED_ACTION_CONTENTS_TYPE = - "DeferredAction(uint256 nonce,uint48 deadline,bytes25 validationFunction,bytes call)"; + "DeferredAction(uint256 nonce,uint48 deadline,uint168 validationLocator,bytes call)"; bytes32 private constant _DEFERRED_ACTION_TYPEHASH = keccak256(abi.encodePacked(_DEFERRED_ACTION_CONTENTS_TYPE)); @@ -58,6 +70,22 @@ contract ModuleSignatureUtils { bytes32 internal constant _MODULE_DOMAIN_SEPARATOR = keccak256("EIP712Domain(uint256 chainId,address verifyingContract,bytes32 salt)"); + function _encodeSignature(PreValidationHookData[] memory preValidationHookData, bytes memory validationData) + internal + pure + returns (bytes memory) + { + bytes memory sig = _packPreHookDatas(preValidationHookData); + + sig = abi.encodePacked(sig, _packFinalSignature(validationData)); + + return sig; + } + + function _encodeSignature(bytes memory validationData) internal pure returns (bytes memory) { + return _packFinalSignature(validationData); + } + // helper function to encode a signature, according to the per-hook and per-validation data format. function _encodeSignature( ModuleEntity validationFunction, @@ -65,11 +93,18 @@ contract ModuleSignatureUtils { PreValidationHookData[] memory preValidationHookData, bytes memory validationData ) internal pure returns (bytes memory) { - bytes memory sig = abi.encodePacked(validationFunction, globalOrNot); + // Construct the per-hook data and validation data first, then prefix with the validation locator. - sig = abi.encodePacked(sig, _packPreHookDatas(preValidationHookData)); + bytes memory sig = _encodeSignature(preValidationHookData, validationData); - sig = abi.encodePacked(sig, _packFinalSignature(validationData)); + (address module, uint32 entityId) = validationFunction.unpack(); + + if (entityId == DIRECT_CALL_VALIDATION_ENTITYID) { + sig = + ValidationLocatorLib.packSignatureDirectCall(module, globalOrNot == GLOBAL_VALIDATION, false, sig); + } else { + sig = ValidationLocatorLib.packSignature(entityId, globalOrNot == GLOBAL_VALIDATION, false, sig); + } return sig; } @@ -97,12 +132,18 @@ contract ModuleSignatureUtils { PreValidationHookData[] memory perHookDatas, bytes memory validationData ) internal pure returns (bytes memory) { - bytes memory sig = abi.encodePacked(validationFunction); - - sig = abi.encodePacked(sig, _packPreHookDatas(perHookDatas)); + bytes memory sig = _packPreHookDatas(perHookDatas); sig = abi.encodePacked(sig, _packFinalSignature(validationData)); + (address module, uint32 entityId) = validationFunction.unpack(); + + if (entityId == DIRECT_CALL_VALIDATION_ENTITYID) { + sig = ValidationLocatorLib.packSignatureDirectCall(module, false, false, sig); + } else { + sig = ValidationLocatorLib.packSignature(entityId, false, false, sig); + } + return sig; } @@ -189,12 +230,43 @@ contract ModuleSignatureUtils { }); } - function _packValidationLocator(ModuleEntity validationFunction, uint8 validationSettings) + function _encodeNonce( + ModuleEntity validationFunction, + bool isGlobal, + bool hasDeferredAction, + uint64 linearNonce + ) internal pure returns (uint256) { + (address module, uint32 entityId) = validationFunction.unpack(); + + if (entityId == DIRECT_CALL_VALIDATION_ENTITYID) { + return ValidationLocatorLib.packNonceDirectCall(module, isGlobal, hasDeferredAction) | linearNonce; + } else { + return ValidationLocatorLib.packNonce(entityId, isGlobal, hasDeferredAction) | linearNonce; + } + } + + function _encodeNonce(ModuleEntity validationFunction, bool isGlobal, uint64 linearNonce) internal pure - returns (bytes25) + returns (uint256) { - return bytes25(abi.encodePacked(validationFunction, validationSettings)); + return _encodeNonce(validationFunction, isGlobal, false, linearNonce); + } + + function _encodeNonce(ModuleEntity validationFunction, ValidationType validationType, uint64 linearNonce) + internal + pure + returns (uint256) + { + return _encodeNonce(validationFunction, validationType == ValidationType.GLOBAL, false, linearNonce); + } + + function _encodeNonceDefAction( + ModuleEntity validationFunction, + ValidationType validationType, + uint64 linearNonce + ) internal pure returns (uint256) { + return _encodeNonce(validationFunction, validationType == ValidationType.GLOBAL, true, linearNonce); } // Deferred validation helpers @@ -202,17 +274,11 @@ contract ModuleSignatureUtils { // Internal Helpers function _encodeDeferredInstallUOSignature( - ModuleEntity uoValidationFunction, - uint8 globalOrNot, bytes memory packedDeferredInstallData, bytes memory deferredValidationInstallSig, bytes memory uoValidationSig ) internal pure returns (bytes memory) { - uint8 outerValidationFlags = 2 | globalOrNot; - return abi.encodePacked( - uoValidationFunction, - outerValidationFlags, uint32(packedDeferredInstallData.length), packedDeferredInstallData, uint32(deferredValidationInstallSig.length), @@ -239,9 +305,12 @@ contract ModuleSignatureUtils { ValidationConfig validationFunction, bytes memory selfCall ) internal view returns (bytes32) { - bytes25 maskedValidationFunction = _packValidationLocator( - ValidationConfigLib.moduleEntity(validationFunction), GLOBAL_VALIDATION | HAS_DEFERRED_ACTION_BIT - ); + // Assumes this is not using the direct call path, and that isGlobal is true. + ValidationLocator locator = ValidationLocatorLib.pack({ + _entityId: ValidationConfigLib.entityId(validationFunction), + _isGlobal: true, + _hasDeferredAction: true + }); bytes32 domainSeparator = _computeDomainSeparator(address(account)); @@ -249,9 +318,7 @@ contract ModuleSignatureUtils { return MessageHashUtils.toTypedDataHash({ domainSeparator: domainSeparator, - structHash: keccak256( - abi.encode(_DEFERRED_ACTION_TYPEHASH, nonce, deadline, maskedValidationFunction, selfCallHash) - ) + structHash: keccak256(abi.encode(_DEFERRED_ACTION_TYPEHASH, nonce, deadline, locator, selfCallHash)) }); }