From 963e54da42b73696e590d10bb15354513f0e0358 Mon Sep 17 00:00:00 2001 From: HuaweiGu Date: Mon, 4 Nov 2024 13:29:56 -0500 Subject: [PATCH] feat: update contracts * v1 MSCA and plugins * v2 MSCA and modules (WIP) --- .github/workflows/ci.yml | 2 + .github/workflows/coverage.yml | 5 +- foundry.toml | 3 +- package.json | 7 +- remappings.txt | 2 + src/msca/6900/README.md | 2 + src/msca/6900/shared/common/Errors.sol | 2 + src/msca/6900/v0.7/account/UpgradableMSCA.sol | 18 +- .../6900/v0.7/libs/SelectorRegistryLib.sol | 8 +- src/msca/6900/v0.7/plugins/BasePlugin.sol | 6 +- .../v1_0_0/multisig/BaseMultisigPlugin.sol | 27 +- .../multisig/BaseWeightedMultisigPlugin.sol | 2 +- .../multisig/IWeightedMultisigPlugin.sol | 21 +- .../WeightedWebauthnMultisigPlugin.sol | 316 +-- .../utility/DefaultTokenCallbackPlugin.sol | 4 +- src/msca/6900/v0.8/README.md | 4 +- src/msca/6900/v0.8/account/BaseMSCA.sol | 562 +++--- src/msca/6900/v0.8/account/UpgradableMSCA.sol | 6 +- src/msca/6900/v0.8/common/Constants.sol | 10 - src/msca/6900/v0.8/common/ModuleManifest.sol | 68 - src/msca/6900/v0.8/common/Structs.sol | 43 +- src/msca/6900/v0.8/common/Types.sol | 53 - .../v0.8/factories/UpgradableMSCAFactory.sol | 24 +- .../v0.8/interfaces/IExecutionHookModule.sol | 46 - .../6900/v0.8/interfaces/IExecutionModule.sol | 33 - .../6900/v0.8/interfaces/IModularAccount.sol | 107 - .../v0.8/interfaces/IModularAccountView.sol | 40 - src/msca/6900/v0.8/interfaces/IModule.sol | 45 - .../v0.8/interfaces/IValidationHookModule.sol | 65 - .../v0.8/interfaces/IValidationModule.sol | 70 - src/msca/6900/v0.8/libs/HookConfigLib.sol | 252 --- src/msca/6900/v0.8/libs/HookLib.sol | 152 ++ .../6900/v0.8/libs/SelectorRegistryLib.sol | 31 +- src/msca/6900/v0.8/libs/WalletStorageLib.sol | 8 +- .../v0.8/libs/thirdparty/ModuleEntityLib.sol | 60 - .../thirdparty/SparseCalldataSegmentLib.sol | 123 -- .../libs/thirdparty/ValidationConfigLib.sol | 135 -- .../6900/v0.8/managers/StandardExecutor.sol | 2 +- src/msca/6900/v0.8/modules/BaseModule.sol | 2 +- .../ColdStorageAddressBookModule.sol | 19 +- .../addressbook/IAddressBookModule.sol | 4 +- .../modules/multisig/BaseMultisigModule.sol | 5 +- .../multisig/BaseWeightedMultisigModule.sol | 6 +- src/msca/6900/v0.8/modules/multisig/README.md | 2 +- .../WeightedWebauthnMultisigModule.sol | 375 ---- .../ISingleSignerValidationModule.sol | 2 +- .../SingleSignerValidationModule.sol | 5 +- test/SponsorPaymaster.t.sol | 3 +- test/libs/AddressBytesLib.t.sol | 2 +- test/libs/PublicKeyLib.t.sol | 4 +- test/libs/RecipientAddressLib.t.sol | 91 +- test/libs/SetValueLib.t.sol | 2 +- test/libs/webauthn/WebAuthnLib.t.sol | 4 +- test/libs/webauthn/WebAuthnLibFuzz.t.sol | 8 +- test/msca/6900/shared/libs/Bytes4DLLLib.t.sol | 2 +- .../6900/v0.7/HookFunctionReferenceDLL.t.sol | 1 + .../RepeatableFunctionReferenceDLLLib.t.sol | 1 + test/msca/6900/v0.7/SingleOwnerMSCA.t.sol | 19 +- test/msca/6900/v0.7/SingleOwnerPlugin.t.sol | 2 +- .../TestPermitAnyExternalAddressPlugin.sol | 8 +- ...yExternalAddressWithPostHookOnlyPlugin.sol | 8 +- ...nyExternalAddressWithPreHookOnlyPlugin.sol | 8 +- test/msca/6900/v0.7/TestTokenPlugin.sol | 4 + .../v0.7/TestTokenWithPostHookOnlyPlugin.sol | 4 +- .../v0.7/TestTokenWithPreHookOnlyPlugin.sol | 7 +- .../6900/v0.7/TestUserOpAllPassValidator.sol | 8 +- test/msca/6900/v0.7/UpgradableMSCA.t.sol | 73 +- test/msca/6900/v0.7/WalletStorageV1Lib.t.sol | 15 +- .../AddressBookPluginWithSemiMSCA.t.sol | 2 +- ...StorageAddressBookPluginWithSemiMSCA.t.sol | 4 +- .../WeightedWebauthnMultisigPlugin.t.sol | 1728 +++++++++-------- .../6900/v0.8/DirectCallsFromModule.t.sol | 24 +- .../6900/v0.8/DynamicValidationHookData.t.sol | 21 +- test/msca/6900/v0.8/FooBarModule.sol | 9 +- test/msca/6900/v0.8/MSCACallFlow.t.sol | 722 +++++++ test/msca/6900/v0.8/ModuleManagement.t.sol | 34 +- test/msca/6900/v0.8/PermittedCall.t.sol | 12 +- test/msca/6900/v0.8/SelfCallRule.t.sol | 25 +- .../6900/v0.8/TestPermittedCallModule.sol | 7 +- test/msca/6900/v0.8/TestTokenModule.sol | 18 +- test/msca/6900/v0.8/UpgradableMSCA.t.sol | 197 +- .../6900/v0.8/UpgradableMSCAFactory.t.sol | 14 +- test/msca/6900/v0.8/WalletStorageLib.t.sol | 2 +- .../6900/v0.8/helpers/DirectCallModule.sol | 4 +- .../6900/v0.8/helpers/MSCACallFlowModule.sol | 155 ++ test/msca/6900/v0.8/helpers/MockModule.sol | 9 +- .../v0.8/helpers/TestAddressBookModule.sol | 20 +- test/msca/6900/v0.8/libs/HookConfigLib.t.sol | 110 -- .../libs/thirdparty/ModuleEntityLib.t.sol | 56 - .../thirdparty/SparseCalldataSegmentLib.t.sol | 125 -- .../libs/thirdparty/ValidationConfigLib.t.sol | 122 -- .../SingleSignerValidationModule.t.sol | 17 +- .../msca/6900/v0.8/utils/AccountTestUtils.sol | 22 +- test/util/Mock1820Registry.sol | 6 +- test/util/TestERC777.sol | 13 +- yarn.lock | 4 + 96 files changed, 2981 insertions(+), 3559 deletions(-) create mode 100644 src/msca/6900/README.md delete mode 100644 src/msca/6900/v0.8/common/ModuleManifest.sol delete mode 100644 src/msca/6900/v0.8/common/Types.sol delete mode 100644 src/msca/6900/v0.8/interfaces/IExecutionHookModule.sol delete mode 100644 src/msca/6900/v0.8/interfaces/IExecutionModule.sol delete mode 100644 src/msca/6900/v0.8/interfaces/IModularAccount.sol delete mode 100644 src/msca/6900/v0.8/interfaces/IModularAccountView.sol delete mode 100644 src/msca/6900/v0.8/interfaces/IModule.sol delete mode 100644 src/msca/6900/v0.8/interfaces/IValidationHookModule.sol delete mode 100644 src/msca/6900/v0.8/interfaces/IValidationModule.sol delete mode 100644 src/msca/6900/v0.8/libs/HookConfigLib.sol create mode 100644 src/msca/6900/v0.8/libs/HookLib.sol delete mode 100644 src/msca/6900/v0.8/libs/thirdparty/ModuleEntityLib.sol delete mode 100644 src/msca/6900/v0.8/libs/thirdparty/SparseCalldataSegmentLib.sol delete mode 100644 src/msca/6900/v0.8/libs/thirdparty/ValidationConfigLib.sol delete mode 100644 src/msca/6900/v0.8/modules/multisig/WeightedWebauthnMultisigModule.sol create mode 100644 test/msca/6900/v0.8/MSCACallFlow.t.sol create mode 100644 test/msca/6900/v0.8/helpers/MSCACallFlowModule.sol delete mode 100644 test/msca/6900/v0.8/libs/HookConfigLib.t.sol delete mode 100644 test/msca/6900/v0.8/libs/thirdparty/ModuleEntityLib.t.sol delete mode 100644 test/msca/6900/v0.8/libs/thirdparty/SparseCalldataSegmentLib.t.sol delete mode 100644 test/msca/6900/v0.8/libs/thirdparty/ValidationConfigLib.t.sol diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 35bd98b..17dfdd8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,3 +29,5 @@ jobs: needs: lint_and_test if: github.event_name == 'pull_request' uses: circlefin/circle-public-github-workflows/.github/workflows/pr-scan.yaml@v1 + with: + allow-reciprocal-licenses: true diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index a0e7037..d80edb9 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -20,4 +20,7 @@ jobs: - name: Setup environment uses: ./.github/actions/setup - # TODO report and check coverage + # TODO: fail the build if coverage is below the threshold + # TODO: print the coverage report in the github output + - name: Run Test Coverage + run: yarn coverage diff --git a/foundry.toml b/foundry.toml index ee79208..d4e2c1a 100644 --- a/foundry.toml +++ b/foundry.toml @@ -3,12 +3,13 @@ fs_permissions = [{ access = "read-write", path = "./gas"}, { access = "read", path = "./test/fixtures"}] src = 'src' out = 'out' -libs = ['lib'] +libs = ['lib', 'node_modules'] solc_version = "0.8.24" test = 'test' via_ir = true auto_detect_solc = false auto_detect_remappings = false +deny_warnings = true [fuzz] runs = 1024 diff --git a/package.json b/package.json index f2952ae..2c48eb8 100644 --- a/package.json +++ b/package.json @@ -5,10 +5,10 @@ "scripts": { "clean": "rm -rf cache artifacts typechain-types", "build": "forge build", - "lint": "solhint -w 112 -c .solhint-src.json './src/**/*.sol' && solhint -w 228 -c .solhint-test.json './test/**/*.sol' && solhint -w 0 -c .solhint-script.json './script/**/*.sol'", + "lint": "solhint -w 105 -c .solhint-src.json './src/**/*.sol' && solhint -w 228 -c .solhint-test.json './test/**/*.sol' && solhint -w 0 -c .solhint-script.json './script/**/*.sol'", "test": "forge test -vv", "gasreport": "forge test --gas-report > gas/reports/gas-report.txt", - "coverage": "forge coverage --ir-minimum", + "coverage": "forge coverage", "format:check": "forge fmt --check", "format:write": "forge fmt" }, @@ -20,7 +20,8 @@ "@openzeppelin/contracts-upgradeable": "5.0.2", "fcl": "github:rdubois-crypto/FreshCryptoLib#8179e08cac72072bd260796633fec41fdfd5b441", "forge-std": "github:foundry-rs/forge-std#v1.9.2", - "solady": "0.0.243" + "solady": "0.0.243", + "@erc6900/reference-implementation": "github:erc6900/reference-implementation#v0.8.0-rc.6" }, "devDependencies": { "solhint": "^5.0.3" diff --git a/remappings.txt b/remappings.txt index 2f57a80..8d4de77 100644 --- a/remappings.txt +++ b/remappings.txt @@ -5,3 +5,5 @@ @solady/=node_modules/solady/src/ @fcl/=node_modules/fcl/solidity/src/ forge-std/=node_modules/forge-std/ +@erc6900/reference-implementation/=node_modules/@erc6900/reference-implementation/src/ +@eth-infinitism/account-abstraction/=lib/account-abstraction/contracts/ diff --git a/src/msca/6900/README.md b/src/msca/6900/README.md new file mode 100644 index 0000000..e69d22f --- /dev/null +++ b/src/msca/6900/README.md @@ -0,0 +1,2 @@ +# Acknowledgements +The contracts in this folder are based on the ERC-6900 specification and are significantly influenced by the design of the ERC-6900 reference implementation. diff --git a/src/msca/6900/shared/common/Errors.sol b/src/msca/6900/shared/common/Errors.sol index de8f196..1f15fea 100644 --- a/src/msca/6900/shared/common/Errors.sol +++ b/src/msca/6900/shared/common/Errors.sol @@ -59,3 +59,5 @@ error InvalidItem(); // v2 NotImplemented error NotImplementedFunction(bytes4 selector, uint32 entityId); + +error SignatureInflation(); diff --git a/src/msca/6900/v0.7/account/UpgradableMSCA.sol b/src/msca/6900/v0.7/account/UpgradableMSCA.sol index 0723781..f47b83d 100644 --- a/src/msca/6900/v0.7/account/UpgradableMSCA.sol +++ b/src/msca/6900/v0.7/account/UpgradableMSCA.sol @@ -18,6 +18,7 @@ */ pragma solidity 0.8.24; +import {DefaultCallbackHandler} from "../../../../callback/DefaultCallbackHandler.sol"; import {ExecutionUtils} from "../../../../utils/ExecutionUtils.sol"; import {InvalidInitializationInput} from "../../shared/common/Errors.sol"; import {FunctionReference} from "../common/Structs.sol"; @@ -26,13 +27,17 @@ import {BaseMSCA} from "./BaseMSCA.sol"; import {IEntryPoint} from "@account-abstraction/contracts/interfaces/IEntryPoint.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; +import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; +import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; + /** * @dev Leverage {ERC1967Proxy} brought by UUPS proxies, when this contract is set as the implementation behind such a * proxy. * The {_authorizeUpgrade} function is overridden here so more granular ACLs to the upgrade mechanism should be enforced * by plugins. */ -contract UpgradableMSCA is BaseMSCA, UUPSUpgradeable { +contract UpgradableMSCA is BaseMSCA, DefaultCallbackHandler, UUPSUpgradeable { using ExecutionUtils for address; event UpgradableMSCAInitialized(address indexed account, address indexed entryPointAddress); @@ -72,6 +77,17 @@ contract UpgradableMSCA is BaseMSCA, UUPSUpgradeable { emit UpgradableMSCAInitialized(address(this), address(entryPoint)); } + function supportsInterface(bytes4 interfaceId) + public + view + override(BaseMSCA, DefaultCallbackHandler) + returns (bool) + { + // BaseMSCA has already implemented ERC165 + return BaseMSCA.supportsInterface(interfaceId) || interfaceId == type(IERC721Receiver).interfaceId + || interfaceId == type(IERC1155Receiver).interfaceId || interfaceId == type(IERC1271).interfaceId; + } + /// @inheritdoc UUPSUpgradeable function upgradeToAndCall(address newImplementation, bytes memory data) public diff --git a/src/msca/6900/v0.7/libs/SelectorRegistryLib.sol b/src/msca/6900/v0.7/libs/SelectorRegistryLib.sol index 70b1d93..0fbf4ef 100644 --- a/src/msca/6900/v0.7/libs/SelectorRegistryLib.sol +++ b/src/msca/6900/v0.7/libs/SelectorRegistryLib.sol @@ -28,6 +28,9 @@ import {IAggregator} from "@account-abstraction/contracts/interfaces/IAggregator import {IPaymaster} from "@account-abstraction/contracts/interfaces/IPaymaster.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {IERC777Recipient} from "@openzeppelin/contracts/interfaces/IERC777Recipient.sol"; +import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; +import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; library SelectorRegistryLib { @@ -61,7 +64,10 @@ library SelectorRegistryLib { || selector == IAccountLoupe.getInstalledPlugins.selector || selector == VALIDATE_USER_OP || selector == GET_ENTRYPOINT || selector == GET_NONCE || selector == INITIALIZE_UPGRADABLE_MSCA || selector == INITIALIZE_SINGLE_OWNER_MSCA || selector == TRANSFER_NATIVE_OWNERSHIP - || selector == RENOUNCE_NATIVE_OWNERSHIP || selector == GET_NATIVE_OWNER; + || selector == RENOUNCE_NATIVE_OWNERSHIP || selector == GET_NATIVE_OWNER + || selector == IERC1155Receiver.onERC1155Received.selector + || selector == IERC1155Receiver.onERC1155BatchReceived.selector + || selector == IERC721Receiver.onERC721Received.selector || selector == IERC777Recipient.tokensReceived.selector; } function _isErc4337FunctionSelector(bytes4 selector) internal pure returns (bool) { diff --git a/src/msca/6900/v0.7/plugins/BasePlugin.sol b/src/msca/6900/v0.7/plugins/BasePlugin.sol index c839d63..7f2025b 100644 --- a/src/msca/6900/v0.7/plugins/BasePlugin.sol +++ b/src/msca/6900/v0.7/plugins/BasePlugin.sol @@ -82,7 +82,7 @@ abstract contract BasePlugin is IPlugin, ERC165 { virtual returns (uint256 validationData) { - (functionId, userOp, userOpHash); + (functionId, userOp, userOpHash, validationData); revert NotImplemented(msg.sig, functionId); } @@ -98,7 +98,7 @@ abstract contract BasePlugin is IPlugin, ERC165 { virtual returns (uint256 validationData) { - (functionId, userOp, userOpHash); + (functionId, userOp, userOpHash, validationData); revert NotImplemented(msg.sig, functionId); } @@ -145,7 +145,7 @@ abstract contract BasePlugin is IPlugin, ERC165 { virtual returns (bytes memory context) { - (functionId, sender, value, data); + (functionId, sender, value, data, context); revert NotImplemented(msg.sig, functionId); } diff --git a/src/msca/6900/v0.7/plugins/v1_0_0/multisig/BaseMultisigPlugin.sol b/src/msca/6900/v0.7/plugins/v1_0_0/multisig/BaseMultisigPlugin.sol index 71013e6..f64c918 100644 --- a/src/msca/6900/v0.7/plugins/v1_0_0/multisig/BaseMultisigPlugin.sol +++ b/src/msca/6900/v0.7/plugins/v1_0_0/multisig/BaseMultisigPlugin.sol @@ -29,6 +29,8 @@ import { import {NotImplemented} from "../../../../shared/common/Errors.sol"; import {BasePlugin} from "../../BasePlugin.sol"; +import {IWeightedMultisigPlugin} from "./IWeightedMultisigPlugin.sol"; + import {PackedUserOperation} from "@account-abstraction/contracts/interfaces/PackedUserOperation.sol"; import { AssociatedLinkedListSet, @@ -71,26 +73,18 @@ abstract contract BaseMultisigPlugin is BasePlugin { AssociatedLinkedListSet internal _owners; address public immutable ENTRYPOINT; - string constant ADD_OWNERS_PERMISSION = "Add Owners"; - string constant UPDATE_MULTISIG_WEIGHTS_PERMISSION = "Update Multisig Weights"; - string constant REMOVE_OWNERS_PERMISSION = "Remove Owners"; + string internal constant ADD_OWNERS_PERMISSION = "Add Owners"; + string internal constant UPDATE_MULTISIG_WEIGHTS_PERMISSION = "Update Multisig Weights"; + string internal constant REMOVE_OWNERS_PERMISSION = "Remove Owners"; constructor(address entryPoint) { ENTRYPOINT = entryPoint; } /// @notice Check if the signatures are valid for the account. - /// @param actualDigest The actual gas digest. - /// @param minimalDigest Digest of user op with minimal required fields set: - /// (address sender, uint256 nonce, bytes initCode, bytes callData), and remaining - /// fields set to default values. - /// @param account The account to check the signatures for. - /// @param signatures The signatures to check. - /// @return success True if the signatures are valid. - /// @return firstFailure first failure, if failed is true. /// (Note: if all signatures are individually valid but do not satisfy the /// multisig, firstFailure will be set to the last signature's index.) - function checkNSignatures(bytes32 actualDigest, bytes32 minimalDigest, address account, bytes memory signatures) + function checkNSignatures(IWeightedMultisigPlugin.CheckNSignatureInput memory input) public view virtual @@ -114,8 +108,13 @@ abstract contract BaseMultisigPlugin is BasePlugin { if (actualUserOpDigest == minimalUserOpDigest) { revert InvalidUserOpDigest(); } - (bool success,) = checkNSignatures(actualUserOpDigest, minimalUserOpDigest, msg.sender, userOp.signature); - + IWeightedMultisigPlugin.CheckNSignatureInput memory input = IWeightedMultisigPlugin.CheckNSignatureInput({ + actualDigest: actualUserOpDigest, + minimalDigest: minimalUserOpDigest, + account: msg.sender, + signatures: userOp.signature + }); + (bool success,) = checkNSignatures(input); return success ? SIG_VALIDATION_SUCCEEDED : SIG_VALIDATION_FAILED; } diff --git a/src/msca/6900/v0.7/plugins/v1_0_0/multisig/BaseWeightedMultisigPlugin.sol b/src/msca/6900/v0.7/plugins/v1_0_0/multisig/BaseWeightedMultisigPlugin.sol index fa7c0b2..4a3a118 100644 --- a/src/msca/6900/v0.7/plugins/v1_0_0/multisig/BaseWeightedMultisigPlugin.sol +++ b/src/msca/6900/v0.7/plugins/v1_0_0/multisig/BaseWeightedMultisigPlugin.sol @@ -175,7 +175,7 @@ abstract contract BaseWeightedMultisigPlugin is BaseMultisigPlugin, IWeightedMul } /// @inheritdoc IWeightedMultisigPlugin - function checkNSignatures(bytes32 actualDigest, bytes32 minimalDigest, address account, bytes memory signatures) + function checkNSignatures(CheckNSignatureInput memory inputs) public view virtual diff --git a/src/msca/6900/v0.7/plugins/v1_0_0/multisig/IWeightedMultisigPlugin.sol b/src/msca/6900/v0.7/plugins/v1_0_0/multisig/IWeightedMultisigPlugin.sol index 6fa2d65..0648346 100644 --- a/src/msca/6900/v0.7/plugins/v1_0_0/multisig/IWeightedMultisigPlugin.sol +++ b/src/msca/6900/v0.7/plugins/v1_0_0/multisig/IWeightedMultisigPlugin.sol @@ -55,6 +55,13 @@ interface IWeightedMultisigPlugin { error OwnersWeightsMismatch(); error ThresholdWeightExceedsTotalWeight(uint256 thresholdWeight, uint256 totalWeight); + struct CheckNSignatureInput { + bytes32 actualDigest; + bytes32 minimalDigest; + address account; + bytes signatures; + } + /// @notice Add owners and their associated weights for the account, and optionally update threshold weight required /// to perform an action. /// @dev Constraints: @@ -141,19 +148,19 @@ interface IWeightedMultisigPlugin { function getOwnerId(PublicKey memory pubKeyOwnerToCheck) external pure returns (bytes30); /// @notice Check if the signatures are valid for the account. - /// @param actualDigest Digest of user op with all fields filled in. - /// At least one signature must cover the actualDigest, with a v value >= 32, - /// if it differs from the minimal digest. - /// @param minimalDigest Digest of user op with minimal required fields set: + /// @param input has minimalDigest Digest of user op with minimal required fields set: /// (address sender, uint256 nonce, bytes initCode, bytes callData), and remaining /// fields set to default values. - /// @param account The account to check the signatures for. - /// @param signatures The signatures to check. + /// actualDigest Digest of user op with all fields filled in. + /// At least one signature must cover the actualDigest, with a v value >= 32, + /// if it differs from the minimal digest. + /// account The account to check the signatures for. + /// signatures The signatures to check. /// @return success True if the signatures are valid. /// @return firstFailure first failure, if failed is true. /// (Note: if all signatures are individually valid but do not satisfy the /// multisig, firstFailure will be set to the last signature's index.) - function checkNSignatures(bytes32 actualDigest, bytes32 minimalDigest, address account, bytes memory signatures) + function checkNSignatures(CheckNSignatureInput memory input) external view returns (bool success, uint256 firstFailure); diff --git a/src/msca/6900/v0.7/plugins/v1_0_0/multisig/WeightedWebauthnMultisigPlugin.sol b/src/msca/6900/v0.7/plugins/v1_0_0/multisig/WeightedWebauthnMultisigPlugin.sol index 721e9c8..7154abe 100644 --- a/src/msca/6900/v0.7/plugins/v1_0_0/multisig/WeightedWebauthnMultisigPlugin.sol +++ b/src/msca/6900/v0.7/plugins/v1_0_0/multisig/WeightedWebauthnMultisigPlugin.sol @@ -69,6 +69,20 @@ contract WeightedWebauthnMultisigPlugin is BaseWeightedMultisigPlugin, BaseERC71 bytes32 private constant _MULTISIG_PLUGIN_TYPEHASH = keccak256("CircleWeightedWebauthnMultisigMessage(bytes32 hash)"); + struct CheckNSignatureData { + // lowestOffset of signature dynamic part, must locate after the signature constant part + // 0 means we only have EOA signer so far + uint256 lowestSigDynamicPartOffset; + bytes30 lastOwner; + bytes30 currentOwner; + bool success; + uint256 firstFailure; + // first32Bytes of signature constant part + bytes32 first32Bytes; + // second32Bytes of signature constant part + bytes32 second32Bytes; + } + constructor(address entryPoint) BaseWeightedMultisigPlugin(entryPoint) {} // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ @@ -117,8 +131,8 @@ contract WeightedWebauthnMultisigPlugin is BaseWeightedMultisigPlugin, BaseERC71 /// @inheritdoc IERC1271 function isValidSignature(bytes32 digest, bytes memory signature) external view override returns (bytes4) { bytes32 wrappedDigest = getReplaySafeMessageHash(msg.sender, digest); - (bool success,) = checkNSignatures(wrappedDigest, wrappedDigest, msg.sender, signature); - + CheckNSignatureInput memory input = CheckNSignatureInput(wrappedDigest, wrappedDigest, msg.sender, signature); + (bool success,) = checkNSignatures(input); return success ? EIP1271_VALID_SIGNATURE : EIP1271_INVALID_SIGNATURE; } @@ -301,40 +315,32 @@ contract WeightedWebauthnMultisigPlugin is BaseWeightedMultisigPlugin, BaseERC71 // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ /// @inheritdoc IWeightedMultisigPlugin - function checkNSignatures(bytes32 actualDigest, bytes32 minimalDigest, address account, bytes memory signatures) + function checkNSignatures(CheckNSignatureInput memory input) public view override returns (bool success, uint256 firstFailure) { - if (signatures.length < _INDIVIDUAL_SIGNATURE_BYTES_LEN) { + if (input.signatures.length < _INDIVIDUAL_SIGNATURE_BYTES_LEN) { revert InvalidSigLength(); } - uint256 thresholdWeight = _ownerMetadata[account].thresholdWeight; + uint256 thresholdWeight = _ownerMetadata[input.account].thresholdWeight; // (Account is not initialized) if (thresholdWeight == 0) { revert InvalidThresholdWeight(); } + CheckNSignatureData memory checkNSignatureData; uint256 accumulatedWeight; - uint256 signatureCount; - bytes30 lastOwner; - bytes30 currentOwner; - // first32Bytes of signature constant part - bytes32 first32Bytes; - // second32Bytes of signature constant part - bytes32 second32Bytes; // lastByte of signature constant part uint8 sigType; - // lowestOffset of signature dynamic part, must locate after the signature constant part - // 0 means we only have EOA signer so far - uint256 lowestSigDynamicPartOffset = 0; + uint256 signatureCount; // if the digests differ, make sure we have exactly 1 sig on the actual digest - uint256 numSigsOnActualDigest = (actualDigest != minimalDigest) ? 1 : 0; + uint256 numSigsOnActualDigest = (input.actualDigest != input.minimalDigest) ? 1 : 0; // tracks whether `signatures` is a complete and valid multisig signature - success = true; + checkNSignatureData.success = true; while (accumulatedWeight < thresholdWeight) { // Fail if the next 65 bytes would exceed signature length // or lowest dynamic part signature offset, where next 65 bytes is defined as @@ -345,23 +351,26 @@ contract WeightedWebauthnMultisigPlugin is BaseWeightedMultisigPlugin, BaseERC71 signatureCount * _INDIVIDUAL_SIGNATURE_BYTES_LEN + _INDIVIDUAL_SIGNATURE_BYTES_LEN; // do not fail if only have EOA signer so far if ( - (lowestSigDynamicPartOffset != 0 && sigConstantPartEndPos > lowestSigDynamicPartOffset) - || sigConstantPartEndPos > signatures.length + ( + checkNSignatureData.lowestSigDynamicPartOffset != 0 + && sigConstantPartEndPos > checkNSignatureData.lowestSigDynamicPartOffset + ) || sigConstantPartEndPos > input.signatures.length ) { - if (success) { + if (checkNSignatureData.success) { return (false, signatureCount); } else { - return (false, firstFailure); + return (false, checkNSignatureData.firstFailure); } } - (sigType, first32Bytes, second32Bytes) = _signatureSplit(signatures, signatureCount); + (sigType, checkNSignatureData.first32Bytes, checkNSignatureData.second32Bytes) = + _signatureSplit(input.signatures, signatureCount); // sigType >= 32 implies it's signed over the actual digest, so we deduct it according to encoding rule // if sigType > 60, it will eventually fail the ecdsa recover check below bytes32 digest; if (sigType >= 32) { - digest = actualDigest; + digest = input.actualDigest; sigType -= 32; // can have unchecked since we check against zero at the end // underflow would wrap the value to 2 ^ 256 - 1 @@ -370,132 +379,33 @@ contract WeightedWebauthnMultisigPlugin is BaseWeightedMultisigPlugin, BaseERC71 numSigsOnActualDigest -= 1; } } else { - digest = minimalDigest; + digest = input.minimalDigest; } // sigType == 0 is the contract signature case if (sigType == 0) { - // first32Bytes contains the address to perform 1271 validation on - address contractAddress = address(uint160(uint256(first32Bytes))); - // make sure upper bits are clean - if (uint256(first32Bytes) > uint256(uint160(contractAddress))) { - revert InvalidAddress(); - } - currentOwner = contractAddress.toBytes30(); - if (ownerDataPerAccount[currentOwner][account].addr != contractAddress) { - if (success) { - firstFailure = signatureCount; - success = false; - } - } - // retrieve contract signature - bytes memory contractSignature; - { - // offset of current signature dynamic part - // second32Bytes is the memory offset containing the signature - uint256 sigDynamicPartOffset = uint256(second32Bytes); - if ( - sigDynamicPartOffset > signatures.length - || sigDynamicPartOffset < _INDIVIDUAL_SIGNATURE_BYTES_LEN - ) { - revert InvalidSigOffset(); - } - // total length of current signature dynamic part - uint256 sigDynamicPartTotalLen; - // 1. load contractSignature content starting from the correct memory offset - // 2. calculate total length including the content and the prefix storing the length - assembly ("memory-safe") { - contractSignature := add(add(signatures, sigDynamicPartOffset), 0x20) - sigDynamicPartTotalLen := add(mload(contractSignature), 0x20) - } - // signature dynamic part should not exceed the total signature length - if (sigDynamicPartOffset + sigDynamicPartTotalLen > signatures.length) { - revert InvalidContractSigLength(); - } - if (sigDynamicPartOffset < lowestSigDynamicPartOffset || lowestSigDynamicPartOffset == 0) { - lowestSigDynamicPartOffset = sigDynamicPartOffset; - } - } - if (!SignatureChecker.isValidERC1271SignatureNow(contractAddress, digest, contractSignature)) { - if (success) { - firstFailure = signatureCount; - success = false; - } - } + _validateContractSignature(checkNSignatureData, input, digest, signatureCount); } else if (sigType == 2) { - // secp256r1 sig, webauthn and public key data bytes - bytes memory sigDynamicPartBytes; - // first32Bytes stores public key on-chain identifier - currentOwner = bytes30(uint240(uint256(first32Bytes))); - OwnerData memory currentOwnerData = ownerDataPerAccount[currentOwner][account]; - uint256 x = currentOwnerData.publicKeyX; - uint256 y = currentOwnerData.publicKeyY; - // retrieve sig dynamic part bytes - WebAuthnSigDynamicPart memory sigDynamicPart; - { - // second32Bytes is the memory offset containing the sigDynamicPart - uint256 sigDynamicPartOffset = uint256(second32Bytes); - if ( - sigDynamicPartOffset > signatures.length - || sigDynamicPartOffset < _INDIVIDUAL_SIGNATURE_BYTES_LEN - ) { - revert InvalidSigOffset(); - } - uint256 sigDynamicPartTotalLen; - // 1. load the content starting from the correct memory offset - // 2. calculate total length including the content and the prefix storing the length - assembly ("memory-safe") { - sigDynamicPartBytes := add(add(signatures, sigDynamicPartOffset), 0x20) - sigDynamicPartTotalLen := add(mload(sigDynamicPartBytes), 0x20) - } - if (sigDynamicPartOffset + sigDynamicPartTotalLen > signatures.length) { - revert InvalidSigLength(); - } - if (sigDynamicPartOffset < lowestSigDynamicPartOffset || lowestSigDynamicPartOffset == 0) { - lowestSigDynamicPartOffset = sigDynamicPartOffset; - } - sigDynamicPart = abi.decode(sigDynamicPartBytes, (WebAuthnSigDynamicPart)); - } - if ( - !WebAuthnLib.verify({ - challenge: abi.encode(digest), - webAuthnData: sigDynamicPart.webAuthnData, - r: sigDynamicPart.r, - s: sigDynamicPart.s, - x: x, - y: y - }) - ) { - if (success) { - firstFailure = signatureCount; - success = false; - } - } + _validateWebauthnSignature(checkNSignatureData, input, digest, signatureCount); } else { // reverts if signature has the wrong s value, wrong v value, or if it's a bad point on the k1 curve - address signer = digest.recover(sigType, first32Bytes, second32Bytes); - currentOwner = signer.toBytes30(); - if (ownerDataPerAccount[currentOwner][account].addr != signer) { - if (success) { - firstFailure = signatureCount; - success = false; - } - } + _validateEOASignature(checkNSignatureData, input, digest, signatureCount, sigType); } if ( // if the signature is out of order or duplicate // or is not an owner - currentOwner <= lastOwner || !_owners.contains(account, SetValue.wrap(currentOwner)) + checkNSignatureData.currentOwner <= checkNSignatureData.lastOwner + || !_owners.contains(input.account, SetValue.wrap(checkNSignatureData.currentOwner)) ) { - if (success) { - firstFailure = signatureCount; - success = false; + if (checkNSignatureData.success) { + checkNSignatureData.firstFailure = signatureCount; + checkNSignatureData.success = false; } } - accumulatedWeight += ownerDataPerAccount[currentOwner][account].weight; - lastOwner = currentOwner; + accumulatedWeight += ownerDataPerAccount[checkNSignatureData.currentOwner][input.account].weight; + checkNSignatureData.lastOwner = checkNSignatureData.currentOwner; signatureCount++; } @@ -504,7 +414,145 @@ contract WeightedWebauthnMultisigPlugin is BaseWeightedMultisigPlugin, BaseERC71 if (numSigsOnActualDigest != 0) { revert InvalidNumSigsOnActualDigest(numSigsOnActualDigest); } - return (success, firstFailure); + return (checkNSignatureData.success, checkNSignatureData.firstFailure); + } + + function _validateContractSignature( + CheckNSignatureData memory checkNSignatureData, + CheckNSignatureInput memory input, + bytes32 digest, + uint256 signatureCount + ) internal view { + // first32Bytes contains the address to perform 1271 validation on + address contractAddress = address(uint160(uint256(checkNSignatureData.first32Bytes))); + // make sure upper bits are clean + if (uint256(checkNSignatureData.first32Bytes) > uint256(uint160(contractAddress))) { + revert InvalidAddress(); + } + checkNSignatureData.currentOwner = contractAddress.toBytes30(); + if (ownerDataPerAccount[checkNSignatureData.currentOwner][input.account].addr != contractAddress) { + if (checkNSignatureData.success) { + checkNSignatureData.firstFailure = signatureCount; + checkNSignatureData.success = false; + } + } + // offset of current signature dynamic part + // second32Bytes is the memory offset containing the signature + uint256 sigDynamicPartOffset = uint256(checkNSignatureData.second32Bytes); + // retrieve contract signature + bytes memory contractSignature; + { + // offset of current signature dynamic part + // second32Bytes is the memory offset containing the signature + if ( + sigDynamicPartOffset > input.signatures.length || sigDynamicPartOffset < _INDIVIDUAL_SIGNATURE_BYTES_LEN + ) { + revert InvalidSigOffset(); + } + // total length of current signature dynamic part + uint256 sigDynamicPartTotalLen; + // 0. load the signatures from CheckNSignatureInput struct + // 1. load contractSignature content starting from the correct memory offset + // 2. calculate total length including the content and the prefix storing the length + assembly ("memory-safe") { + let signatures := mload(add(input, 0x60)) + contractSignature := add(add(signatures, sigDynamicPartOffset), 0x20) + sigDynamicPartTotalLen := add(mload(contractSignature), 0x20) + } + // signature dynamic part should not exceed the total signature length + if (sigDynamicPartOffset + sigDynamicPartTotalLen > input.signatures.length) { + revert InvalidContractSigLength(); + } + if ( + sigDynamicPartOffset < checkNSignatureData.lowestSigDynamicPartOffset + || checkNSignatureData.lowestSigDynamicPartOffset == 0 + ) { + checkNSignatureData.lowestSigDynamicPartOffset = sigDynamicPartOffset; + } + } + if (!SignatureChecker.isValidERC1271SignatureNow(contractAddress, digest, contractSignature)) { + if (checkNSignatureData.success) { + checkNSignatureData.firstFailure = signatureCount; + checkNSignatureData.success = false; + } + } + } + + function _validateWebauthnSignature( + CheckNSignatureData memory checkNSignatureData, + CheckNSignatureInput memory input, + bytes32 digest, + uint256 signatureCount + ) internal view { + bytes memory sigDynamicPartBytes; + // first32Bytes stores public key on-chain identifier + checkNSignatureData.currentOwner = bytes30(uint240(uint256(checkNSignatureData.first32Bytes))); + OwnerData memory currentOwnerData = ownerDataPerAccount[checkNSignatureData.currentOwner][input.account]; + uint256 x = currentOwnerData.publicKeyX; + uint256 y = currentOwnerData.publicKeyY; + // retrieve sig dynamic part bytes + WebAuthnSigDynamicPart memory sigDynamicPart; + // second32Bytes is the memory offset containing the sigDynamicPart + uint256 sigDynamicPartOffset = uint256(checkNSignatureData.second32Bytes); + { + if ( + sigDynamicPartOffset > input.signatures.length || sigDynamicPartOffset < _INDIVIDUAL_SIGNATURE_BYTES_LEN + ) { + revert InvalidSigOffset(); + } + uint256 sigDynamicPartTotalLen; + // 0. load the signatures from CheckNSignatureInput struct + // 1. load the content starting from the correct memory offset + // 2. calculate total length including the content and the prefix storing the length + assembly ("memory-safe") { + let signatures := mload(add(input, 0x60)) + sigDynamicPartBytes := add(add(signatures, sigDynamicPartOffset), 0x20) + sigDynamicPartTotalLen := add(mload(sigDynamicPartBytes), 0x20) + } + if (sigDynamicPartOffset + sigDynamicPartTotalLen > input.signatures.length) { + revert InvalidSigLength(); + } + if ( + sigDynamicPartOffset < checkNSignatureData.lowestSigDynamicPartOffset + || checkNSignatureData.lowestSigDynamicPartOffset == 0 + ) { + checkNSignatureData.lowestSigDynamicPartOffset = sigDynamicPartOffset; + } + sigDynamicPart = abi.decode(sigDynamicPartBytes, (WebAuthnSigDynamicPart)); + } + if ( + !WebAuthnLib.verify({ + challenge: abi.encode(digest), + webAuthnData: sigDynamicPart.webAuthnData, + r: sigDynamicPart.r, + s: sigDynamicPart.s, + x: x, + y: y + }) + ) { + if (checkNSignatureData.success) { + checkNSignatureData.firstFailure = signatureCount; + checkNSignatureData.success = false; + } + } + } + + function _validateEOASignature( + CheckNSignatureData memory checkNSignatureData, + CheckNSignatureInput memory input, + bytes32 digest, + uint256 signatureCount, + uint8 sigType + ) internal view { + // reverts if signature has the wrong s value, wrong v value, or if it's a bad point on the k1 curve + address signer = digest.recover(sigType, checkNSignatureData.first32Bytes, checkNSignatureData.second32Bytes); + checkNSignatureData.currentOwner = signer.toBytes30(); + if (ownerDataPerAccount[checkNSignatureData.currentOwner][input.account].addr != signer) { + if (checkNSignatureData.success) { + checkNSignatureData.firstFailure = signatureCount; + checkNSignatureData.success = false; + } + } } /// @inheritdoc BaseERC712CompliantModule diff --git a/src/msca/6900/v0.7/plugins/v1_0_0/utility/DefaultTokenCallbackPlugin.sol b/src/msca/6900/v0.7/plugins/v1_0_0/utility/DefaultTokenCallbackPlugin.sol index 6cefb43..1493e88 100644 --- a/src/msca/6900/v0.7/plugins/v1_0_0/utility/DefaultTokenCallbackPlugin.sol +++ b/src/msca/6900/v0.7/plugins/v1_0_0/utility/DefaultTokenCallbackPlugin.sol @@ -40,11 +40,11 @@ import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Recei contract DefaultTokenCallbackPlugin is BasePlugin, IERC721Receiver, IERC1155Receiver, IERC777Recipient { string public constant NAME = "Default Token Callback Plugin"; - function onInstall(bytes calldata data) external override { + function onInstall(bytes calldata data) external pure override { (data); } - function onUninstall(bytes calldata data) external override { + function onUninstall(bytes calldata data) external pure override { (data); } diff --git a/src/msca/6900/v0.8/README.md b/src/msca/6900/v0.8/README.md index 0384641..7111e60 100644 --- a/src/msca/6900/v0.8/README.md +++ b/src/msca/6900/v0.8/README.md @@ -1,3 +1,5 @@ # Modular Smart Contract Account -## v0.8 MSCA that's in compliant with [EIP-6900](https://eips.ethereum.org/EIPS/eip-6900) + +## Signature Encoding +TODO. diff --git a/src/msca/6900/v0.8/account/BaseMSCA.sol b/src/msca/6900/v0.8/account/BaseMSCA.sol index b346b5c..9f2be20 100644 --- a/src/msca/6900/v0.8/account/BaseMSCA.sol +++ b/src/msca/6900/v0.8/account/BaseMSCA.sol @@ -37,38 +37,41 @@ import {AddressDLLLib} from "../../shared/libs/AddressDLLLib.sol"; import {Bytes32DLLLib} from "../../shared/libs/Bytes32DLLLib.sol"; import {Bytes4DLLLib} from "../../shared/libs/Bytes4DLLLib.sol"; import {ValidationDataLib} from "../../shared/libs/ValidationDataLib.sol"; -import {DIRECT_CALL_VALIDATION_ENTITY_ID, GLOBAL_VALIDATION_FLAG, MAX_VALIDATION_HOOKS} from "../common/Constants.sol"; +import {GLOBAL_VALIDATION_FLAG} from "../common/Constants.sol"; -import {ExecutionManifest, ManifestExecutionHook} from "../common/ModuleManifest.sol"; +import {Bytes32DLL, ExecutionStorage, PostExecHookToRun, ValidationStorage} from "../common/Structs.sol"; import { - Bytes32DLL, - Call, - ExecutionDataView, - ExecutionDetail, - PostExecHookToRun, - ValidationDataView, - ValidationDetail -} from "../common/Structs.sol"; + ExecutionManifest, ManifestExecutionHook +} from "@erc6900/reference-implementation/interfaces/IExecutionModule.sol"; -import {HookConfig, ModuleEntity, ValidationConfig} from "../common/Types.sol"; +import { + Call, + HookConfig, + ModuleEntity, + ValidationConfig +} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol"; -import {IExecutionHookModule} from "../interfaces/IExecutionHookModule.sol"; -import {IModularAccount} from "../interfaces/IModularAccount.sol"; -import {IModularAccountView} from "../interfaces/IModularAccountView.sol"; -import {IModule} from "../interfaces/IModule.sol"; +import {IExecutionHookModule} from "@erc6900/reference-implementation/interfaces/IExecutionHookModule.sol"; +import {IModularAccount} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol"; +import { + ExecutionDataView, + IModularAccountView, + ValidationDataView +} from "@erc6900/reference-implementation/interfaces/IModularAccountView.sol"; +import {IModule} from "@erc6900/reference-implementation/interfaces/IModule.sol"; -import {IValidationHookModule} from "../interfaces/IValidationHookModule.sol"; -import {IValidationModule} from "../interfaces/IValidationModule.sol"; +import {IValidationHookModule} from "@erc6900/reference-implementation/interfaces/IValidationHookModule.sol"; +import {IValidationModule} from "@erc6900/reference-implementation/interfaces/IValidationModule.sol"; -import {HookConfigLib} from "../libs/HookConfigLib.sol"; import {SelectorRegistryLib} from "../libs/SelectorRegistryLib.sol"; import {WalletStorageLib} from "../libs/WalletStorageLib.sol"; -import {ModuleEntityLib} from "../libs/thirdparty/ModuleEntityLib.sol"; -import {ValidationConfigLib} from "../libs/thirdparty/ValidationConfigLib.sol"; +import {HookConfigLib} from "@erc6900/reference-implementation/libraries/HookConfigLib.sol"; +import {ModuleEntityLib} from "@erc6900/reference-implementation/libraries/ModuleEntityLib.sol"; +import {ValidationConfigLib} from "@erc6900/reference-implementation/libraries/ValidationConfigLib.sol"; -import {SparseCalldataSegmentLib} from "../libs/thirdparty/SparseCalldataSegmentLib.sol"; import {StandardExecutor} from "../managers/StandardExecutor.sol"; import {WalletStorageInitializable} from "./WalletStorageInitializable.sol"; +import {SparseCalldataSegmentLib} from "@erc6900/reference-implementation/libraries/SparseCalldataSegmentLib.sol"; import {IAccount} from "@account-abstraction/contracts/interfaces/IAccount.sol"; import {IAccountExecute} from "@account-abstraction/contracts/interfaces/IAccountExecute.sol"; @@ -76,6 +79,12 @@ import {IEntryPoint} from "@account-abstraction/contracts/interfaces/IEntryPoint import {PackedUserOperation} from "@account-abstraction/contracts/interfaces/PackedUserOperation.sol"; import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; +import {HookLib} from "../libs/HookLib.sol"; + +import { + DIRECT_CALL_VALIDATION_ENTITY_ID, + MAX_VALIDATION_ASSOC_HOOKS +} from "@erc6900/reference-implementation/helpers/Constants.sol"; import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; @@ -95,8 +104,8 @@ abstract contract BaseMSCA is { using Bytes32DLLLib for Bytes32DLL; using ModuleEntityLib for ModuleEntity; - using HookConfigLib for Bytes32DLL; - using HookConfigLib for PostExecHookToRun[]; + using HookLib for Bytes32DLL; + using HookLib for PostExecHookToRun[]; using ExecutionUtils for address; using StandardExecutor for address; using StandardExecutor for Call[]; @@ -107,7 +116,8 @@ abstract contract BaseMSCA is using Bytes4DLLLib for Bytes4DLL; using ValidationConfigLib for ValidationConfig; using HookConfigLib for HookConfig; - using HookConfigLib for bytes32; + using HookLib for HookConfig; + using HookLib for bytes32; enum ValidationCheckingType { GLOBAL, @@ -125,7 +135,7 @@ abstract contract BaseMSCA is error InvalidSignatureValidation(ModuleEntity sigValidation); error InvalidUserOpValidation(ModuleEntity userOpValidation); error ExecFromModuleToSelectorNotPermitted(address module, bytes4 selector); - error ValidationFunctionMissing(bytes4 selector, ModuleEntity validation); + error InvalidValidationFunction(bytes4 selector, ModuleEntity validation); error InvalidCalldataLength(uint256 actualLength, uint256 requiredLength); error RequireUserOperationContext(); error SelfCallRecursionDepthExceeded(); @@ -137,7 +147,7 @@ abstract contract BaseMSCA is error InvalidExecutionSelector(address module, bytes4 selector); error InvalidExecutionHook(address module, bytes4 selector); error GlobalValidationFunctionAlreadySet(ModuleEntity validationFunction); - error PreValidationHookLimitExceeded(); + error MaxHooksExceeded(); error NullModule(); error InterfaceNotSupported(address module, bytes4 interfaceId); error InvalidHookUninstallData(); @@ -145,7 +155,7 @@ abstract contract BaseMSCA is /** * @dev Wraps execution of a native function (as opposed to a function added by modules) with runtime validations * (not from EP) - * and hooks. Used by execute, executeBatch, installModule, uninstallModule, upgradeTo and upgradeToAndCall. + * and hooks. Used by native execution functions. * If the call is from entry point, then validateUserOp will run. * https://eips.ethereum.org/assets/eip-6900/Modular_Account_Call_Flow.svg */ @@ -153,11 +163,10 @@ abstract contract BaseMSCA is if (!msg.sig._isNativeExecutionFunction()) { revert NotNativeFunctionSelector(msg.sig); } - WalletStorageLib.Layout storage walletStorage = WalletStorageLib.getLayout(); ( PostExecHookToRun[] memory postExecHooksFromDirectCallValidation, PostExecHookToRun[] memory postExecHooksFromExecutionSelector - ) = _checkCallPermission(walletStorage); + ) = _checkCallPermission(); _; postExecHooksFromExecutionSelector._processPostExecHooks(); postExecHooksFromDirectCallValidation._processPostExecHooks(); @@ -184,8 +193,7 @@ abstract contract BaseMSCA is /// @dev Route calls to execution functions based on incoming msg.sig /// If there's no module associated with this function selector, revert fallback(bytes calldata) external payable returns (bytes memory result) { - WalletStorageLib.Layout storage walletStorage = WalletStorageLib.getLayout(); - address executionFunctionModule = walletStorage.executionDetails[msg.sig].module; + address executionFunctionModule = WalletStorageLib.getLayout().executionStorage[msg.sig].module; // valid module address should not be address(0) if (executionFunctionModule == address(0)) { revert InvalidExecutionFunction(msg.sig); @@ -193,7 +201,7 @@ abstract contract BaseMSCA is ( PostExecHookToRun[] memory postExecHooksFromDirectCallValidation, PostExecHookToRun[] memory postExecHooksFromExecutionSelector - ) = _checkCallPermission(walletStorage); + ) = _checkCallPermission(); result = executionFunctionModule.callWithReturnDataOrRevert(msg.value, msg.data); postExecHooksFromExecutionSelector._processPostExecHooks(); postExecHooksFromDirectCallValidation._processPostExecHooks(); @@ -215,7 +223,6 @@ abstract contract BaseMSCA is */ function validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds) external - virtual override returns (uint256 validationData) { @@ -247,21 +254,11 @@ abstract contract BaseMSCA is /// @inheritdoc IERC1271 function isValidSignature(bytes32 hash, bytes calldata signature) public view override returns (bytes4) { ModuleEntity sigValidation = ModuleEntity.wrap(bytes24(signature)); - if (!WalletStorageLib.getLayout().validationDetails[sigValidation].isSignatureValidation) { + if (!WalletStorageLib.getLayout().validationStorage[sigValidation].isSignatureValidation) { revert InvalidSignatureValidation(sigValidation); } signature = signature[24:]; - HookConfig[] memory validationHooks = - WalletStorageLib.getLayout().validationDetails[sigValidation].validationHooks; - for (uint256 i = 0; i < validationHooks.length; ++i) { - (address hookModule, uint32 hookEntityId) = validationHooks[i].unpackValidationHook().unpack(); - bytes memory currentSignatureSegment; - (currentSignatureSegment, signature) = signature.advanceSegmentIfAtIndex(uint8(i)); - IValidationHookModule(hookModule).preSignatureValidationHook( - hookEntityId, msg.sender, hash, currentSignatureSegment - ); - } - // validation function + signature = _runSigValidationHooks(sigValidation, hash, signature); signature = signature.getFinalSegment(); (address module, uint32 entityId) = sigValidation.unpack(); if ( @@ -273,6 +270,36 @@ abstract contract BaseMSCA is return EIP1271_INVALID_SIGNATURE; } + function _runSigValidationHooks(ModuleEntity sigValidation, bytes32 hash, bytes calldata signature) + internal + view + returns (bytes calldata) + { + Bytes32DLL storage validationHooks = + WalletStorageLib.getLayout().validationStorage[sigValidation].validationHooks; + uint8 segmentIndex = 0; + bytes32 startHook = SENTINEL_BYTES32; + while (segmentIndex < MAX_VALIDATION_ASSOC_HOOKS) { + (bytes32[] memory hooks, bytes32 nextHook) = validationHooks.getPaginated(startHook, 50); + for (uint256 j = 0; j < hooks.length; ++j) { + bytes memory currentSignatureSegment; + (currentSignatureSegment, signature) = signature.advanceSegmentIfAtIndex(segmentIndex); + _runSigValidationHook(hooks[j], hash, currentSignatureSegment); + segmentIndex++; + } + if (nextHook == SENTINEL_BYTES32) { + break; + } + startHook = nextHook; + } + return signature; + } + + function _runSigValidationHook(bytes32 hook, bytes32 hash, bytes memory currentSignature) internal view { + (address hookModule, uint32 hookEntityId) = hook.toHookConfig().unpackValidationHook().unpack(); + IValidationHookModule(hookModule).preSignatureValidationHook(hookEntityId, msg.sender, hash, currentSignature); + } + /** * @dev Return the account nonce. * This method returns the next sequential nonce. @@ -337,11 +364,12 @@ abstract contract BaseMSCA is revert UnauthorizedCaller(); } // use case: spending limits hook - Bytes32DLL storage executionHooks = WalletStorageLib.getLayout().validationDetails[ModuleEntity.wrap( + Bytes32DLL storage executionHooks = WalletStorageLib.getLayout().validationStorage[ModuleEntity.wrap( bytes24(userOp.signature[:24]) )].executionHooks; PostExecHookToRun[] memory postExecHooks = executionHooks._processPreExecHooks(msg.data); - address(this).callWithReturnDataOrRevert(0, userOp.callData[4:]); + // no return data needed + address(this).callAndRevert(0, userOp.callData[4:]); postExecHooks._processPostExecHooks(); } @@ -376,40 +404,22 @@ abstract contract BaseMSCA is payable returns (bytes memory result) { - if (data.length < 4) { - revert InvalidCalldataLength(data.length, 4); - } - if (bytes4(data[:4]) == bytes4(0)) { - revert NotFoundSelector(); - } - // TODO: update this check to include other fields if (authorization.length < 25) { revert InvalidAuthorizationOrSigLength(authorization.length, 25); } ModuleEntity validationFunction = ModuleEntity.wrap(bytes24(authorization[:24])); bool isGlobalValidation = uint8(authorization[24]) == GLOBAL_VALIDATION_FLAG; - WalletStorageLib.Layout storage walletStorage = WalletStorageLib.getLayout(); - if ( - !_checkValidationForCalldata( - walletStorage, - data, - validationFunction, - isGlobalValidation ? ValidationCheckingType.GLOBAL : ValidationCheckingType.SELECTOR - ) - ) { - // just use the outer selector for error - revert ValidationFunctionMissing(bytes4(data[:4]), validationFunction); - } - _processRuntimeHooksAndValidation( - walletStorage.validationDetails[validationFunction].validationHooks, - validationFunction, + _checkValidationForCalldata( data, - authorization[25:] + validationFunction, + isGlobalValidation ? ValidationCheckingType.GLOBAL : ValidationCheckingType.SELECTOR ); + _processRuntimeHooksAndValidation(validationFunction, data, authorization[25:]); // run execution checks - Bytes32DLL storage execHooks = WalletStorageLib.getLayout().validationDetails[validationFunction].executionHooks; - PostExecHookToRun[] memory postExecHooks = execHooks._processPreExecHooks(msg.data); + PostExecHookToRun[] memory postExecHooks = WalletStorageLib.getLayout().validationStorage[validationFunction] + .executionHooks + ._processPreExecHooks(msg.data); // self execute call result = address(this).callWithReturnDataOrRevert(0, data); postExecHooks._processPostExecHooks(); @@ -418,15 +428,15 @@ abstract contract BaseMSCA is /// @inheritdoc IModularAccountView function getExecutionData(bytes4 selector) external view returns (ExecutionDataView memory executionData) { - if (selector._isGlobalValidationAllowedNativeExecutionFunction()) { + if (selector._isNativeFunction()) { executionData.module = address(this); executionData.allowGlobalValidation = true; } else { - ExecutionDetail storage executionDetail = WalletStorageLib.getLayout().executionDetails[selector]; - executionData.module = executionDetail.module; - executionData.skipRuntimeValidation = executionDetail.skipRuntimeValidation; - executionData.allowGlobalValidation = executionDetail.allowGlobalValidation; - executionData.executionHooks = executionDetail.executionHooks._getExecutionHooks(); + ExecutionStorage storage executionStorage = WalletStorageLib.getLayout().executionStorage[selector]; + executionData.module = executionStorage.module; + executionData.skipRuntimeValidation = executionStorage.skipRuntimeValidation; + executionData.allowGlobalValidation = executionStorage.allowGlobalValidation; + executionData.executionHooks = executionStorage.executionHooks._toHookConfigs(); } return executionData; } @@ -437,13 +447,13 @@ abstract contract BaseMSCA is view returns (ValidationDataView memory validationData) { - ValidationDetail storage validationDetail = WalletStorageLib.getLayout().validationDetails[validationFunction]; - validationData.isGlobal = validationDetail.isGlobal; - validationData.isSignatureValidation = validationDetail.isSignatureValidation; - validationData.isUserOpValidation = validationDetail.isUserOpValidation; - validationData.validationHooks = validationDetail.validationHooks; - validationData.executionHooks = validationDetail.executionHooks._getExecutionHooks(); - validationData.selectors = validationDetail.selectors.getAll(); + ValidationStorage storage validationStorage = WalletStorageLib.getLayout().validationStorage[validationFunction]; + validationData.isGlobal = validationStorage.isGlobal; + validationData.isSignatureValidation = validationStorage.isSignatureValidation; + validationData.isUserOpValidation = validationStorage.isUserOpValidation; + validationData.validationHooks = validationStorage.validationHooks._toHookConfigs(); + validationData.executionHooks = validationStorage.executionHooks._toHookConfigs(); + validationData.selectors = validationStorage.selectors.getAll(); return validationData; } @@ -490,112 +500,130 @@ abstract contract BaseMSCA is returns (uint256 validationData) { // onlyFromEntryPoint is applied in the caller - // if there is no function defined for the selector, or if userOp.callData.length < 4, then execution MUST - // revert - if (userOp.callData.length < 4) { - revert InvalidCalldataLength(userOp.callData.length, 4); - } - if (bytes4(userOp.callData[:4]) == bytes4(0)) { - revert NotFoundSelector(); - } - // TODO: update this check to include other fields if (userOp.signature.length < 25) { revert InvalidAuthorizationOrSigLength(userOp.signature.length, 25); } ModuleEntity validationFunction = ModuleEntity.wrap(bytes24(userOp.signature[:24])); bool isGlobalValidation = uint8(userOp.signature[24]) == GLOBAL_VALIDATION_FLAG; - WalletStorageLib.Layout storage walletStorage = WalletStorageLib.getLayout(); - if ( - !_checkValidationForCalldata( - walletStorage, - userOp.callData, - validationFunction, - isGlobalValidation ? ValidationCheckingType.GLOBAL : ValidationCheckingType.SELECTOR - ) - ) { - // just use the outer selector for error - revert ValidationFunctionMissing(bytes4(userOp.callData[0:4]), validationFunction); - } + _checkValidationForCalldata( + userOp.callData, + validationFunction, + isGlobalValidation ? ValidationCheckingType.GLOBAL : ValidationCheckingType.SELECTOR + ); // check if there are permission hooks associated with the validation function, and revert if we're not calling // `executeUserOp`, // we need some userOp context (e.g. signature) from validation to execution to tell which permission hooks // should have ran because the hooks // are associated with validation function in storage, // only `executeUserOp` would have access to userOp - ValidationDetail storage validationDetail = walletStorage.validationDetails[validationFunction]; - if (validationDetail.executionHooks.size() > 0 && bytes4(userOp.callData[:4]) != this.executeUserOp.selector) { + if ( + bytes4(userOp.callData[:4]) != this.executeUserOp.selector + && WalletStorageLib.getLayout().validationStorage[validationFunction].executionHooks.size() > 0 + ) { revert RequireUserOperationContext(); } - - if (!validationDetail.isUserOpValidation) { - revert InvalidUserOpValidation(validationFunction); - } - // use restored signature - return _processUserOpHooksAndValidation( - validationDetail.validationHooks, validationFunction, userOp, userOp.signature[25:], userOpHash - ); + return _processUserOpHooksAndValidation(validationFunction, userOp, userOp.signature[25:], userOpHash); } function _processUserOpHooksAndValidation( - HookConfig[] memory validationHookFunctions, ModuleEntity validationFunction, PackedUserOperation memory userOp, bytes calldata signature, bytes32 userOpHash - ) internal virtual returns (uint256 validationData) { - ValidationData memory unpackedValidationData = ValidationData(0, 0xFFFFFFFFFFFF, address(0)); - // if the function selector has associated pre user operation validation hooks, then those hooks MUST be run - // sequentially - uint256 currentValidationData; - for (uint256 i = 0; i < validationHookFunctions.length; ++i) { - (userOp.signature, signature) = signature.advanceSegmentIfAtIndex(uint8(i)); - // send the userOp with signature segment for this particular hook function - (address hookModule, uint32 hookEntityId) = validationHookFunctions[i].unpackValidationHook().unpack(); - currentValidationData = - IValidationHookModule(hookModule).preUserOpValidationHook(hookEntityId, userOp, userOpHash); - unpackedValidationData = unpackedValidationData._intersectValidationData(currentValidationData); - // if any return an authorizer value other than 0 or 1, execution MUST revert - if (unpackedValidationData.authorizer != address(0) && unpackedValidationData.authorizer != address(1)) { - revert InvalidAuthorizer(); - } + ) internal returns (uint256 validationData) { + ValidationStorage storage validationStorage = WalletStorageLib.getLayout().validationStorage[validationFunction]; + if (!validationStorage.isUserOpValidation) { + revert InvalidUserOpValidation(validationFunction); } - // validation function + Bytes32DLL storage validationHookFunctions = validationStorage.validationHooks; + ValidationData memory unpackedValidationData; + (unpackedValidationData, signature) = + _runPreUserOpValidationHooks(validationHookFunctions, userOp, signature, userOpHash); + userOp.signature = signature.getFinalSegment(); - (address module, uint32 entityId) = validationFunction.unpack(); - // execute the validation function with the user operation and its hash as parameters using the call opcode - currentValidationData = IValidationModule(module).validateUserOp(entityId, userOp, userOpHash); - // intersect with validation function call + uint256 currentValidationData = _runValidationFunction(validationFunction, userOp, userOpHash); unpackedValidationData = unpackedValidationData._intersectValidationData(currentValidationData); if (unpackedValidationData.authorizer != address(0) && unpackedValidationData.authorizer != address(1)) { - // only revert on unexpected values revert InvalidAuthorizer(); } return unpackedValidationData._packValidationData(); } + function _runPreUserOpValidationHooks( + Bytes32DLL storage validationHookFunctions, + PackedUserOperation memory userOp, + bytes calldata signature, + bytes32 userOpHash + ) internal returns (ValidationData memory, bytes calldata) { + ValidationData memory unpackedValidationData = ValidationData(0, 0xFFFFFFFFFFFF, address(0)); + uint8 segmentIndex = 0; + bytes32 startHook = SENTINEL_BYTES32; + while (segmentIndex < MAX_VALIDATION_ASSOC_HOOKS) { + (bytes32[] memory hooks, bytes32 nextHook) = validationHookFunctions.getPaginated(startHook, 50); + for (uint256 j = 0; j < hooks.length; ++j) { + (userOp.signature, signature) = signature.advanceSegmentIfAtIndex(segmentIndex); + uint256 currentValidationData = _runPreUserOpValidationHook(hooks[j], userOp, userOpHash); + unpackedValidationData = unpackedValidationData._intersectValidationData(currentValidationData); + if (unpackedValidationData.authorizer != address(0) && unpackedValidationData.authorizer != address(1)) + { + revert InvalidAuthorizer(); + } + segmentIndex++; + } + if (nextHook == SENTINEL_BYTES32) { + break; + } + startHook = nextHook; + } + return (unpackedValidationData, signature); + } + + function _runPreUserOpValidationHook(bytes32 hook, PackedUserOperation memory userOp, bytes32 userOpHash) + internal + returns (uint256) + { + (address hookModule, uint32 hookEntityId) = hook.toHookConfig().unpackValidationHook().unpack(); + return IValidationHookModule(hookModule).preUserOpValidationHook(hookEntityId, userOp, userOpHash); + } + + function _runValidationFunction( + ModuleEntity validationFunction, + PackedUserOperation memory userOp, + bytes32 userOpHash + ) internal returns (uint256) { + (address module, uint32 entityId) = validationFunction.unpack(); + // execute the validation function with the user operation and its hash as parameters using the call opcode + return IValidationModule(module).validateUserOp(entityId, userOp, userOpHash); + } + /** * @dev Default validation logic is from installed modules. However, you can override this validation logic in MSCA * implementations. For instance, semi MSCA such as single owner semi MSCA may want to honor the validation * from native owner. */ function _processRuntimeHooksAndValidation( - HookConfig[] memory validationHookFunctions, ModuleEntity validationFunction, bytes calldata data, bytes calldata authorizationData ) internal virtual { - for (uint256 i = 0; i < validationHookFunctions.length; ++i) { - bytes memory currentAuthorization; - (currentAuthorization, authorizationData) = authorizationData.advanceSegmentIfAtIndex(uint8(i)); - // send the authorization segment for this particular hook function - (address hookModule, uint32 hookEntityId) = validationHookFunctions[i].unpackValidationHook().unpack(); - try IValidationHookModule(hookModule).preRuntimeValidationHook( - hookEntityId, msg.sender, msg.value, data, currentAuthorization - ) {} catch { - bytes memory revertReason = ExecutionUtils.fetchReturnData(); - revert PreRuntimeValidationHookFailed(hookModule, hookEntityId, revertReason); + Bytes32DLL storage validationHookFunctions = + WalletStorageLib.getLayout().validationStorage[validationFunction].validationHooks; + bytes32 startHook = SENTINEL_BYTES32; + uint8 segmentIndex = 0; + while (segmentIndex < MAX_VALIDATION_ASSOC_HOOKS) { + (bytes32[] memory hooks, bytes32 nextHook) = validationHookFunctions.getPaginated(startHook, 50); + for (uint256 j = 0; j < hooks.length; ++j) { + bytes memory currentAuthorization; + (currentAuthorization, authorizationData) = authorizationData.advanceSegmentIfAtIndex(segmentIndex); + // send the authorization segment for this particular hook function + _runPreRuntimeValidationHook(hooks[j], data, currentAuthorization); + segmentIndex++; + } + if (nextHook == SENTINEL_BYTES32) { + break; } + startHook = nextHook; } // validation function authorizationData = authorizationData.getFinalSegment(); @@ -608,6 +636,18 @@ abstract contract BaseMSCA is } } + function _runPreRuntimeValidationHook(bytes32 hook, bytes calldata data, bytes memory currentAuthorization) + internal + { + (address hookModule, uint32 hookEntityId) = hook.toHookConfig().unpackValidationHook().unpack(); + try IValidationHookModule(hookModule).preRuntimeValidationHook( + hookEntityId, msg.sender, msg.value, data, currentAuthorization + ) {} catch { + bytes memory revertReason = ExecutionUtils.fetchReturnData(); + revert PreRuntimeValidationHookFailed(hookModule, hookEntityId, revertReason); + } + } + function _installExecution(address module, ExecutionManifest calldata manifest, bytes calldata moduleInstallData) internal { @@ -627,16 +667,16 @@ abstract contract BaseMSCA is length = manifest.executionFunctions.length; for (uint256 i = 0; i < length; ++i) { bytes4 selector = manifest.executionFunctions[i].executionSelector; - if (storageLayout.executionDetails[selector].module != address(0)) { + if (storageLayout.executionStorage[selector].module != address(0)) { revert ExecutionDetailAlreadySet(module, selector); } if (selector._isNativeFunction() || selector._isErc4337Function() || selector._isIModuleFunction()) { revert InvalidExecutionSelector(module, selector); } - storageLayout.executionDetails[selector].module = module; - storageLayout.executionDetails[selector].skipRuntimeValidation = + storageLayout.executionStorage[selector].module = module; + storageLayout.executionStorage[selector].skipRuntimeValidation = manifest.executionFunctions[i].skipRuntimeValidation; - storageLayout.executionDetails[selector].allowGlobalValidation = + storageLayout.executionStorage[selector].allowGlobalValidation = manifest.executionFunctions[i].allowGlobalValidation; } @@ -654,7 +694,7 @@ abstract contract BaseMSCA is _hasPre: manifestExecHook.isPreHook, _hasPost: manifestExecHook.isPostHook }); - storageLayout.executionDetails[selector].executionHooks.append(hookConfig.toBytes32()); + storageLayout.executionStorage[selector].executionHooks.append(hookConfig.toBytes32()); } // call onInstall to initialize module data for the modular account @@ -680,7 +720,7 @@ abstract contract BaseMSCA is _hasPre: manifestExecHook.isPreHook, _hasPost: manifestExecHook.isPostHook }); - storageLayout.executionDetails[manifestExecHook.executionSelector].executionHooks.remove( + storageLayout.executionStorage[manifestExecHook.executionSelector].executionHooks.remove( hookConfig.toBytes32() ); } @@ -688,9 +728,9 @@ abstract contract BaseMSCA is length = manifest.executionFunctions.length; for (uint256 i = 0; i < length; ++i) { bytes4 selector = manifest.executionFunctions[i].executionSelector; - storageLayout.executionDetails[selector].module = address(0); - storageLayout.executionDetails[selector].skipRuntimeValidation = false; - storageLayout.executionDetails[selector].allowGlobalValidation = false; + storageLayout.executionStorage[selector].module = address(0); + storageLayout.executionStorage[selector].skipRuntimeValidation = false; + storageLayout.executionStorage[selector].allowGlobalValidation = false; } length = manifest.interfaceIds.length; @@ -710,36 +750,40 @@ abstract contract BaseMSCA is bytes[] calldata hooks ) internal { ModuleEntity validationModuleEntity = validationConfig.moduleEntity(); - ValidationDetail storage validationDetail = - WalletStorageLib.getLayout().validationDetails[validationModuleEntity]; + ValidationStorage storage validationStorage = + WalletStorageLib.getLayout().validationStorage[validationModuleEntity]; uint256 hooksLength = hooks.length; for (uint256 i = 0; i < hooksLength; ++i) { HookConfig hookConfig = HookConfig.wrap(bytes25(hooks[i][:25])); bytes calldata hookData = hooks[i][25:]; if (hookConfig.isValidationHook()) { - validationDetail.validationHooks.push(hookConfig); // Avoid collision between reserved index and actual indices - if (validationDetail.validationHooks.length > MAX_VALIDATION_HOOKS) { - revert PreValidationHookLimitExceeded(); + if (validationStorage.validationHooks.size() + 1 > MAX_VALIDATION_ASSOC_HOOKS) { + revert MaxHooksExceeded(); } - _onInstall(hookConfig.getModule(), hookData, type(IValidationHookModule).interfaceId); + validationStorage.validationHooks.append(hookConfig.toBytes32()); + _onInstall(hookConfig.module(), hookData, type(IValidationHookModule).interfaceId); } else { - validationDetail.executionHooks.append(hookConfig.toBytes32()); - _onInstall(hookConfig.getModule(), hookData, type(IExecutionHookModule).interfaceId); + if (validationStorage.executionHooks.size() + 1 > MAX_VALIDATION_ASSOC_HOOKS) { + revert MaxHooksExceeded(); + } + validationStorage.executionHooks.append(hookConfig.toBytes32()); + _onInstall(hookConfig.module(), hookData, type(IExecutionHookModule).interfaceId); } } uint256 selectorsLength = selectors.length; for (uint256 i = 0; i < selectorsLength; ++i) { // revert internally - validationDetail.selectors.append(selectors[i]); + validationStorage.selectors.append(selectors[i]); } - validationDetail.isGlobal = validationConfig.isGlobal(); - validationDetail.isSignatureValidation = validationConfig.isSignatureValidation(); - validationDetail.isUserOpValidation = validationConfig.isUserOpValidation(); + validationStorage.isGlobal = validationConfig.isGlobal(); + validationStorage.isSignatureValidation = validationConfig.isSignatureValidation(); + validationStorage.isUserOpValidation = validationConfig.isUserOpValidation(); // call onInstall to initialize module data for the modular account - _onInstall(validationModuleEntity.module(), installData, type(IValidationModule).interfaceId); + (address moduleAddr,) = validationModuleEntity.unpack(); + _onInstall(moduleAddr, installData, type(IValidationModule).interfaceId); } function _uninstallValidation( @@ -747,41 +791,50 @@ abstract contract BaseMSCA is bytes calldata uninstallData, bytes[] calldata hookUninstallData ) internal returns (bool) { - ValidationDetail storage validationDetail = WalletStorageLib.getLayout().validationDetails[validationFunction]; - validationDetail.isGlobal = false; - validationDetail.isSignatureValidation = false; - validationDetail.isUserOpValidation = false; + ValidationStorage storage validationStorage = WalletStorageLib.getLayout().validationStorage[validationFunction]; + validationStorage.isGlobal = false; + validationStorage.isSignatureValidation = false; + validationStorage.isUserOpValidation = false; bool onUninstallSucceeded = true; if (hookUninstallData.length > 0) { // verify the structure of uninstall data is provided correctly if ( hookUninstallData.length - != validationDetail.validationHooks.length + validationDetail.executionHooks.size() + != validationStorage.validationHooks.size() + validationStorage.executionHooks.size() ) { revert InvalidHookUninstallData(); } - // uninstall pre validation hooks + // uninstall validation hooks uint256 uninstalled = 0; - uint256 hooksLength = validationDetail.validationHooks.length; + Bytes32DLL storage validationHooks = validationStorage.validationHooks; + uint256 hooksLength = validationHooks.size(); + bytes32 startHook = SENTINEL_BYTES32; for (uint256 i = 0; i < hooksLength; ++i) { - bytes calldata hookData = hookUninstallData[uninstalled]; - address hookModule = ModuleEntityLib.module(validationDetail.validationHooks[i].getModuleEntity()); - onUninstallSucceeded = onUninstallSucceeded && _onUninstall(hookModule, hookData); - uninstalled++; + (bytes32[] memory hooksToRemove, bytes32 nextHook) = validationHooks.getPaginated(startHook, 50); + for (uint256 j = 0; j < hooksToRemove.length; ++j) { + bytes calldata hookData = hookUninstallData[uninstalled]; + address hookModule = hooksToRemove[j].toHookConfig().module(); + onUninstallSucceeded = onUninstallSucceeded && _onUninstall(hookModule, hookData); + uninstalled++; + } + if (nextHook == SENTINEL_BYTES32) { + break; + } + startHook = nextHook; } - delete validationDetail.validationHooks; + delete validationStorage.validationHooks; // uninstall execution hooks - Bytes32DLL storage executionHooks = validationDetail.executionHooks; + Bytes32DLL storage executionHooks = validationStorage.executionHooks; hooksLength = executionHooks.size(); - bytes32 startHook = SENTINEL_BYTES32; + startHook = SENTINEL_BYTES32; for (uint256 i = 0; i < hooksLength; ++i) { - (bytes32[] memory hooksToRemove, bytes32 nextHook) = executionHooks.getPaginated(startHook, 10); + (bytes32[] memory hooksToRemove, bytes32 nextHook) = executionHooks.getPaginated(startHook, 50); for (uint256 j = 0; j < hooksToRemove.length; ++j) { onUninstallSucceeded = onUninstallSucceeded - && _onUninstall(hooksToRemove[j].toHookConfig().getModule(), hookUninstallData[uninstalled]); + && _onUninstall(hooksToRemove[j].toHookConfig().module(), hookUninstallData[uninstalled]); executionHooks.remove(hooksToRemove[j]); uninstalled++; } @@ -792,11 +845,11 @@ abstract contract BaseMSCA is } } - Bytes4DLL storage selectors = validationDetail.selectors; + Bytes4DLL storage selectors = validationStorage.selectors; uint256 selectorsLength = selectors.size(); bytes4 startSelector = SENTINEL_BYTES4; for (uint256 i = 0; i < selectorsLength; ++i) { - (bytes4[] memory selectorsToRemove, bytes4 nextSelector) = selectors.getPaginated(startSelector, 10); + (bytes4[] memory selectorsToRemove, bytes4 nextSelector) = selectors.getPaginated(startSelector, 50); for (uint256 j = 0; j < selectorsToRemove.length; ++j) { selectors.remove(selectorsToRemove[j]); } @@ -807,12 +860,13 @@ abstract contract BaseMSCA is } // call validation uninstall - return onUninstallSucceeded && _onUninstall(validationFunction.module(), uninstallData); + (address moduleAddr,) = validationFunction.unpack(); + return onUninstallSucceeded && _onUninstall(moduleAddr, uninstallData); } function _onInstall(address module, bytes calldata data, bytes4 interfaceId) internal { if (data.length > 0) { - if (!ERC165Checker.supportsInterface(module, interfaceId)) { + if (!ERC165Checker.supportsERC165InterfaceUnchecked(module, interfaceId)) { revert InterfaceNotSupported(module, interfaceId); } // solhint-disable-next-line no-empty-blocks @@ -847,64 +901,72 @@ abstract contract BaseMSCA is // For other calls (e.g. from modules), we need directCallValidation to protect the account storage. This is // done by running the associated preRuntimeValidationHooks and preExecutionHooks. // For both scenarios, we run preExecutionHooks associated with the selector (msg.sig). - function _checkCallPermission(WalletStorageLib.Layout storage walletStorage) + function _checkCallPermission() internal returns ( PostExecHookToRun[] memory postExecHooksFromDirectCallValidation, PostExecHookToRun[] memory postExecHooksFromExecutionSelector ) { - ExecutionDetail storage executionDetail = WalletStorageLib.getLayout().executionDetails[msg.sig]; - if (msg.sender == address(ENTRY_POINT) || msg.sender == address(this) || executionDetail.skipRuntimeValidation) + WalletStorageLib.Layout storage walletStorage = WalletStorageLib.getLayout(); + ExecutionStorage storage executionStorage = walletStorage.executionStorage[msg.sig]; + if (msg.sender == address(ENTRY_POINT) || msg.sender == address(this) || executionStorage.skipRuntimeValidation) { // no directCallValidation associated pre hooks postExecHooksFromDirectCallValidation = new PostExecHookToRun[](0); } else { ModuleEntity directCallValidation = ModuleEntityLib.pack(msg.sender, DIRECT_CALL_VALIDATION_ENTITY_ID); // check directCallValidation - if ( - !_checkValidationForCalldata( - walletStorage, msg.data, directCallValidation, ValidationCheckingType.EITHER - ) - ) { - revert ValidationFunctionMissing(msg.sig, directCallValidation); - } + _checkValidationForCalldata(msg.data, directCallValidation, ValidationCheckingType.EITHER); // if direct call is allowed, run associated validationHooks - ValidationDetail storage validationDetail = walletStorage.validationDetails[directCallValidation]; - HookConfig[] memory validationHooks = validationDetail.validationHooks; - uint256 length = validationHooks.length; - for (uint256 i = 0; i < length; ++i) { - (address module, uint32 entityId) = validationHooks[i].unpackValidationHook().unpack(); - // solhint-disable-next-line no-empty-blocks - try IValidationHookModule(module).preRuntimeValidationHook( - entityId, msg.sender, msg.value, msg.data, "" - ) {} catch { - bytes memory revertReason = ExecutionUtils.fetchReturnData(); - revert PreRuntimeValidationHookFailed(module, entityId, revertReason); + ValidationStorage storage validationStorage = walletStorage.validationStorage[directCallValidation]; + Bytes32DLL storage validationHooks = validationStorage.validationHooks; + uint256 hooksLength = validationHooks.size(); + bytes32 startHook = SENTINEL_BYTES32; + for (uint256 i = 0; i < hooksLength; ++i) { + (bytes32[] memory hooks, bytes32 nextHook) = validationHooks.getPaginated(startHook, 50); + for (uint256 j = 0; j < hooks.length; ++j) { + (address module, uint32 entityId) = hooks[j].toHookConfig().unpackValidationHook().unpack(); + // solhint-disable-next-line no-empty-blocks + try IValidationHookModule(module).preRuntimeValidationHook( + entityId, msg.sender, msg.value, msg.data, "" + ) {} catch { + bytes memory revertReason = ExecutionUtils.fetchReturnData(); + revert PreRuntimeValidationHookFailed(module, entityId, revertReason); + } + } + if (nextHook == SENTINEL_BYTES32) { + break; } + startHook = nextHook; } // if direct call is allowed, run associated preExecutionHooks - postExecHooksFromDirectCallValidation = validationDetail.executionHooks._processPreExecHooks(msg.data); + postExecHooksFromDirectCallValidation = validationStorage.executionHooks._processPreExecHooks(msg.data); } // run preExecutionHooks for the selector (msg.sig) - postExecHooksFromExecutionSelector = executionDetail.executionHooks._processPreExecHooks(msg.data); + postExecHooksFromExecutionSelector = executionStorage.executionHooks._processPreExecHooks(msg.data); return (postExecHooksFromDirectCallValidation, postExecHooksFromExecutionSelector); } /// @dev Check if the validation function is enabled for the calldata. /// @notice Self-call rule: if the function selector is execute, we don't allow self call because the inner call may /// instead be pulled up to the top-level call, - /// if the function selector is executeBatch, then we inspect the selector in each Call + /// if the function selector is executeBatch, then we inspect the selector in each Call /// where the target is the account itself, - /// * the validation currently being used must apply to inner call selector - /// * the inner call selector must not recursively call into the account's execute or + /// 1. the validation currently being used must apply to inner call selector + /// 2. the inner call selector must not recursively call into the account's execute or /// executeBatch functions function _checkValidationForCalldata( - WalletStorageLib.Layout storage walletStorage, bytes calldata callData, ModuleEntity validationFunction, ValidationCheckingType validationCheckingType - ) internal view returns (bool) { + ) internal view { + if (callData.length < 4) { + revert InvalidCalldataLength(callData.length, 4); + } + if (bytes4(callData[:4]) == bytes4(0)) { + revert NotFoundSelector(); + } if (ModuleEntity.unwrap(validationFunction) == EMPTY_MODULE_ENTITY) { revert InvalidModuleEntity(validationFunction); } @@ -920,9 +982,7 @@ abstract contract BaseMSCA is callData = callData[4:]; } // check outer selector is allowed by the validation function - if (!_checkValidationForSelector(walletStorage, outerSelector, validationFunction, validationCheckingType)) { - return false; - } + _checkValidationForSelector(outerSelector, validationFunction, validationCheckingType); // executeBatch may be used to batch calls into the account itself, but not for execute if (outerSelector == IModularAccount.execute.selector) { (address target,,) = abi.decode(callData[4:], (address, uint256, bytes)); @@ -943,61 +1003,55 @@ abstract contract BaseMSCA is revert SelfCallRecursionDepthExceeded(); } // check all of the inner calls are allowed by the validation function - if ( - !_checkValidationForSelector( - walletStorage, innerSelector, validationFunction, validationCheckingType - ) - ) { - return false; - } + _checkValidationForSelector(innerSelector, validationFunction, validationCheckingType); } } } - return true; } /// @dev Check if the validation function is enabled for the selector, either global or per selector. function _checkValidationForSelector( - WalletStorageLib.Layout storage walletStorage, bytes4 selector, ModuleEntity validationFunction, ValidationCheckingType validationCheckingType - ) internal view returns (bool) { + ) internal view { if (validationCheckingType == ValidationCheckingType.GLOBAL) { - if (_isAllowedForGlobalValidation(walletStorage, selector, validationFunction)) { - return true; + if (_isAllowedForGlobalValidation(selector, validationFunction)) { + return; } } else if (validationCheckingType == ValidationCheckingType.SELECTOR) { - if (_isAllowedForSelectorValidation(walletStorage, selector, validationFunction)) { - return true; + if (_isAllowedForSelectorValidation(selector, validationFunction)) { + return; } } else if (validationCheckingType == ValidationCheckingType.EITHER) { if ( - _isAllowedForGlobalValidation(walletStorage, selector, validationFunction) - || _isAllowedForSelectorValidation(walletStorage, selector, validationFunction) + _isAllowedForGlobalValidation(selector, validationFunction) + || _isAllowedForSelectorValidation(selector, validationFunction) ) { - return true; + return; } } - return false; + revert InvalidValidationFunction(selector, validationFunction); } - function _isAllowedForGlobalValidation( - WalletStorageLib.Layout storage walletStorage, - bytes4 selector, - ModuleEntity validationFunction - ) internal view returns (bool) { + function _isAllowedForGlobalValidation(bytes4 selector, ModuleEntity validationFunction) + internal + view + returns (bool) + { // 1. the function selector need to be enabled for global validation // 2. the global validation has been registered - return (selector._isNativeExecutionFunction() || walletStorage.executionDetails[selector].allowGlobalValidation) - && walletStorage.validationDetails[validationFunction].isGlobal; + return ( + selector._isNativeExecutionFunction() + || WalletStorageLib.getLayout().executionStorage[selector].allowGlobalValidation + ) && WalletStorageLib.getLayout().validationStorage[validationFunction].isGlobal; } - function _isAllowedForSelectorValidation( - WalletStorageLib.Layout storage walletStorage, - bytes4 selector, - ModuleEntity validationFunction - ) internal view returns (bool) { - return walletStorage.validationDetails[validationFunction].selectors.contains(selector); + function _isAllowedForSelectorValidation(bytes4 selector, ModuleEntity validationFunction) + internal + view + returns (bool) + { + return WalletStorageLib.getLayout().validationStorage[validationFunction].selectors.contains(selector); } } diff --git a/src/msca/6900/v0.8/account/UpgradableMSCA.sol b/src/msca/6900/v0.8/account/UpgradableMSCA.sol index 0e220f4..97a878a 100644 --- a/src/msca/6900/v0.8/account/UpgradableMSCA.sol +++ b/src/msca/6900/v0.8/account/UpgradableMSCA.sol @@ -21,13 +21,13 @@ pragma solidity 0.8.24; import {DefaultCallbackHandler} from "../../../../callback/DefaultCallbackHandler.sol"; import {ExecutionUtils} from "../../../../utils/ExecutionUtils.sol"; -import {ValidationConfig} from "../common/Types.sol"; import {BaseMSCA} from "./BaseMSCA.sol"; import {IEntryPoint} from "@account-abstraction/contracts/interfaces/IEntryPoint.sol"; +import {ValidationConfig} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import {IModularAccount} from "../interfaces/IModularAccount.sol"; -import {ValidationConfigLib} from "../libs/thirdparty/ValidationConfigLib.sol"; +import {IModularAccount} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol"; +import {ValidationConfigLib} from "@erc6900/reference-implementation/libraries/ValidationConfigLib.sol"; import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; diff --git a/src/msca/6900/v0.8/common/Constants.sol b/src/msca/6900/v0.8/common/Constants.sol index 98a6849..688fc50 100644 --- a/src/msca/6900/v0.8/common/Constants.sol +++ b/src/msca/6900/v0.8/common/Constants.sol @@ -23,13 +23,3 @@ uint8 constant PER_SELECTOR_VALIDATION_FLAG = 0; // global validation enabled uint8 constant GLOBAL_VALIDATION_FLAG = 1; - -// maximum number of validation hooks that can be installed, [0, 255) hooks, then validation function at -// RESERVED_VALIDATION_DATA_INDEX -uint8 constant MAX_VALIDATION_HOOKS = 255; - -// index marking the start of the validation function data -uint8 constant RESERVED_VALIDATION_DATA_INDEX = 255; - -// magic value for the Entity ID of direct call validation -uint32 constant DIRECT_CALL_VALIDATION_ENTITY_ID = type(uint32).max; diff --git a/src/msca/6900/v0.8/common/ModuleManifest.sol b/src/msca/6900/v0.8/common/ModuleManifest.sol deleted file mode 100644 index db2c166..0000000 --- a/src/msca/6900/v0.8/common/ModuleManifest.sol +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2024 Circle Internet Group, Inc. All rights reserved. - - * SPDX-License-Identifier: GPL-3.0-or-later - - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -pragma solidity 0.8.24; - -// Module Manifest - -struct ManifestExecutionFunction { - bytes4 executionSelector; - // If true, the function will not need runtime validation and can be called by anyone - bool skipRuntimeValidation; - // If true, the function can be validated by a global validation function - bool allowGlobalValidation; -} - -struct ManifestExecutionHook { - bytes4 executionSelector; - uint32 entityId; - bool isPreHook; - bool isPostHook; -} - -struct SelectorPermission { - bytes4 functionSelector; - string permissionDescription; -} - -/// @dev A struct holding fields to describe the module in a purely view context. Intended for front end clients. -struct ModuleMetadata { - // A human-readable name of the module. - string name; - // The version of the module, following the semantic versioning scheme. - string version; - // The author field SHOULD be a username representing the identity of the user or organization - // that created this module. - string author; - // String descriptions of the relative sensitivity of specific functions. The selectors MUST be selectors for - // functions implemented by this module. - SelectorPermission[] permissionDescriptors; - // A list of all ERC-7715 permission strings that the module could possibly use. - string[] permissionRequest; -} - -/// @dev A struct describing how the module should be installed on a modular account. -struct ExecutionManifest { - // List of ERC-165 interface IDs to add to account to support introspection checks. This MUST NOT include - // IModule's interface ID. - bytes4[] interfaceIds; - // Execution functions defined in this module to be installed on the MSCA. - ManifestExecutionFunction[] executionFunctions; - // for executionFunctions - ManifestExecutionHook[] executionHooks; -} diff --git a/src/msca/6900/v0.8/common/Structs.sol b/src/msca/6900/v0.8/common/Structs.sol index 6863f56..db796aa 100644 --- a/src/msca/6900/v0.8/common/Structs.sol +++ b/src/msca/6900/v0.8/common/Structs.sol @@ -19,7 +19,7 @@ pragma solidity 0.8.24; import {Bytes32DLL, Bytes4DLL} from "../../shared/common/Structs.sol"; -import {HookConfig, ModuleEntity} from "./Types.sol"; +import {ModuleEntity} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol"; // Standard executor struct Call { @@ -36,7 +36,8 @@ struct PostExecHookToRun { ModuleEntity postExecHook; } -struct ValidationDetail { +/// @notice Represents stored data associated with a specific validation function. +struct ValidationStorage { // 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. @@ -44,15 +45,15 @@ struct ValidationDetail { // Whether or not this validation is allowed to validate ERC-4337 user operations. bool isUserOpValidation; // The validation hooks for this validation function. - HookConfig[] validationHooks; + Bytes32DLL validationHooks; // Execution hooks to run with this validation function. Bytes32DLL executionHooks; // The set of selectors that may be validated by this validation function. Bytes4DLL selectors; } -// execution detail associated with selector -struct ExecutionDetail { +/// @notice Represents stored data associated with a specific function selector. +struct ExecutionStorage { address module; // module address that implements the execution function, for native functions, the value should be // address(0) // Whether or not the function needs runtime validation, or can be called by anyone. @@ -63,35 +64,3 @@ struct ExecutionDetail { bool allowGlobalValidation; Bytes32DLL executionHooks; } - -// Represents data associated with a specific function selector. -struct ExecutionDataView { - // The module that implements this execution function. - // If this is a native function, the address must remain address(0). - address module; - // Whether or not the function needs runtime validation, or can be called by anyone. The function can still be - // state changing if this flag is set to true. - // Note that even if this is set to true, user op validation will still be required, otherwise anyone could - // drain the account of native tokens by wasting gas. - bool skipRuntimeValidation; - // Whether or not a global validation function may be used to validate this function. - bool allowGlobalValidation; - // The execution hooks for this function selector. - HookConfig[] executionHooks; -} - -// Represents data associated with a specific validation function. -struct ValidationDataView { - // Whether or not this validation can be used as a global validation function. - bool isGlobal; - // Whether or not this validation is a ERC-1271 signature validation function. - bool isSignatureValidation; - // Whether or not this validation function is a user operation validation function. - bool isUserOpValidation; - // The validation hooks for this validation function. - HookConfig[] validationHooks; - // Execution hooks to run with this validation function. - HookConfig[] executionHooks; - // The set of selectors that may be validated by this validation function. - bytes4[] selectors; -} diff --git a/src/msca/6900/v0.8/common/Types.sol b/src/msca/6900/v0.8/common/Types.sol deleted file mode 100644 index 10994ee..0000000 --- a/src/msca/6900/v0.8/common/Types.sol +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2024 Circle Internet Group, Inc. All rights reserved. - - * SPDX-License-Identifier: GPL-3.0-or-later - - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -pragma solidity 0.8.24; - -// ModuleEntity is a packed representation of a module function -// Layout: -// 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA________________________ // Address -// 0x________________________________________BBBBBBBB________________ // Entity ID -// 0x________________________________________________0000000000000000 // unused -type ModuleEntity is bytes24; - -// ValidationConfig is a packed representation of a validation function and flags for its configuration. -// Layout: -// 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA________________________ // Address -// 0x________________________________________BBBBBBBB________________ // Entity ID -// 0x________________________________________________CC______________ // Validation flags -// 0x__________________________________________________00000000000000 // unused -// -// Validation flags layout: -// 0b00000___ // unused -// 0b_____A__ // isGlobal -// 0b______B_ // isSignatureValidation -// 0b_______C // isUserOpValidation -type ValidationConfig is bytes25; - -// HookConfig is a packed representation of a hook function and flags for its configuration. -// Layout: -// 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA________________________ // Address -// 0x________________________________________BBBBBBBB________________ // Entity ID -// 0x________________________________________________CC______________ // Hook Flags -// -// Hook flags layout: -// 0b00000___ // unused -// 0b_____A__ // hasPre (exec only) -// 0b______B_ // hasPost (exec only) -// 0b_______C // hook type (0 for exec, 1 for validation) -type HookConfig is bytes25; diff --git a/src/msca/6900/v0.8/factories/UpgradableMSCAFactory.sol b/src/msca/6900/v0.8/factories/UpgradableMSCAFactory.sol index c8262a5..01d86f1 100644 --- a/src/msca/6900/v0.8/factories/UpgradableMSCAFactory.sol +++ b/src/msca/6900/v0.8/factories/UpgradableMSCAFactory.sol @@ -21,9 +21,12 @@ pragma solidity 0.8.24; import {Create2FailedDeployment, InvalidLength} from "../../shared/common/Errors.sol"; import {UpgradableMSCA} from "../account/UpgradableMSCA.sol"; -import {ValidationConfig} from "../common/Types.sol"; -import {ValidationConfigLib} from "../libs/thirdparty/ValidationConfigLib.sol"; import {IEntryPoint} from "@account-abstraction/contracts/interfaces/IEntryPoint.sol"; +import {ValidationConfig} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol"; + +import {HookConfig} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol"; +import {HookConfigLib} from "@erc6900/reference-implementation/libraries/HookConfigLib.sol"; +import {ValidationConfigLib} from "@erc6900/reference-implementation/libraries/ValidationConfigLib.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; @@ -38,6 +41,7 @@ import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; */ contract UpgradableMSCAFactory is Ownable2Step { using ValidationConfigLib for ValidationConfig; + using HookConfigLib for HookConfig; // logic implementation UpgradableMSCA public immutable ACCOUNT_IMPLEMENTATION; @@ -147,6 +151,22 @@ contract UpgradableMSCAFactory is Ownable2Step { if (!isModuleAllowed[module]) { revert ModuleIsNotAllowed(module); } + for (uint256 i = 0; i < _hooks.length; ++i) { + address hookModule; + bytes memory hook = _hooks[i]; + if (hook.length < 20) { + revert ModuleIsNotAllowed(hookModule); + } + // Skips the first 32 bytes that represent the length, then loads the next word + // Shifts the loaded 32 bytes to the right by 12 bytes to retrieve the address + // solhint-disable-next-line no-inline-assembly + assembly ("memory-safe") { + hookModule := shr(96, mload(add(hook, 0x20))) + } + if (!isModuleAllowed[hookModule]) { + revert ModuleIsNotAllowed(hookModule); + } + } mixedSalt = keccak256(abi.encodePacked(_sender, _salt)); bytes32 code = keccak256( abi.encodePacked( diff --git a/src/msca/6900/v0.8/interfaces/IExecutionHookModule.sol b/src/msca/6900/v0.8/interfaces/IExecutionHookModule.sol deleted file mode 100644 index 016d665..0000000 --- a/src/msca/6900/v0.8/interfaces/IExecutionHookModule.sol +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2024 Circle Internet Group, Inc. All rights reserved. - - * SPDX-License-Identifier: GPL-3.0-or-later - - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -pragma solidity 0.8.24; - -import {IModule} from "./IModule.sol"; - -/** - * @dev Implements https://eips.ethereum.org/EIPS/eip-6900. Modules must implement this interface to support module - * management and interactions with MSCAs. - */ -interface IExecutionHookModule is IModule { - /// @notice Run the pre execution hook specified by the `entityId`. - /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param entityId An identifier that routes the call to different internal implementations, should there - /// be more than one. - /// @param sender The caller address. - /// @param value The call value. - /// @param data The calldata sent. For `executeUserOp` calls, hook modules should receive the full msg.data. - /// @return Context to pass to a post execution hook, if present. An empty bytes array MAY be returned. - function preExecutionHook(uint32 entityId, address sender, uint256 value, bytes calldata data) - external - returns (bytes memory); - - /// @notice Run the post execution hook specified by the `entityId`. - /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param entityId An identifier that routes the call to different internal implementations, should there - /// be more than one. - /// @param preExecHookData The context returned by its associated pre execution hook. - function postExecutionHook(uint32 entityId, bytes calldata preExecHookData) external; -} diff --git a/src/msca/6900/v0.8/interfaces/IExecutionModule.sol b/src/msca/6900/v0.8/interfaces/IExecutionModule.sol deleted file mode 100644 index 0c8dbc8..0000000 --- a/src/msca/6900/v0.8/interfaces/IExecutionModule.sol +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2024 Circle Internet Group, Inc. All rights reserved. - - * SPDX-License-Identifier: GPL-3.0-or-later - - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -pragma solidity 0.8.24; - -import {ExecutionManifest} from "../common/ModuleManifest.sol"; -import {IModule} from "./IModule.sol"; - -/** - * @dev Implements https://eips.ethereum.org/EIPS/eip-6900. Modules must implement this interface to support module - * management and interactions with MSCAs. - */ -interface IExecutionModule is IModule { - /// @notice Describe the contents and intended configuration of the module. - /// @dev This manifest MUST stay constant over time. - /// @return A manifest describing the contents and intended configuration of the module. - function executionManifest() external pure returns (ExecutionManifest memory); -} diff --git a/src/msca/6900/v0.8/interfaces/IModularAccount.sol b/src/msca/6900/v0.8/interfaces/IModularAccount.sol deleted file mode 100644 index a557191..0000000 --- a/src/msca/6900/v0.8/interfaces/IModularAccount.sol +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2024 Circle Internet Group, Inc. All rights reserved. - - * SPDX-License-Identifier: GPL-3.0-or-later - - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -pragma solidity 0.8.24; - -import {ExecutionManifest} from "../common/ModuleManifest.sol"; -import {Call} from "../common/Structs.sol"; -import {ModuleEntity, ValidationConfig} from "../common/Types.sol"; - -/** - * @dev Implements https://eips.ethereum.org/EIPS/eip-6900. MSCAs must implement this interface to support open-ended - * execution. - */ -interface IModularAccount { - event ExecutionInstalled(address indexed module, ExecutionManifest manifest); - event ExecutionUninstalled(address indexed module, bool onUninstallSucceeded, ExecutionManifest manifest); - event ValidationInstalled(address indexed module, uint32 indexed entityId); - event ValidationUninstalled(address indexed module, uint32 indexed entityId, bool onUninstallSucceeded); - - /// @notice Standard execute method. - /// @param target The target address for the account to call. - /// @param value The value to send with the call. - /// @param data The calldata for the call. - /// @return The return data from the call. - function execute(address target, uint256 value, bytes calldata data) external payable returns (bytes memory); - - /// @notice Standard executeBatch method. - /// @dev If the target is a module, the call SHOULD revert. If any of the calls revert, the entire batch MUST - /// revert. - /// @param calls The array of calls. - /// @return An array containing the return data from the calls. - function executeBatch(Call[] calldata calls) external payable returns (bytes[] memory); - - /// @notice Execute a call using the specified runtime validation. - /// @param data The calldata to send to the account. - /// @param authorization The authorization data to use for the call. The first 24 bytes is a ModuleEntity which - /// specifies which runtime validation to use, and the rest is sent as a parameter to runtime validation. - function executeWithRuntimeValidation(bytes calldata data, bytes calldata authorization) - external - payable - returns (bytes memory); - - /// @notice Install a module to the modular account. - /// @param module The module to install. - /// @param manifest the manifest describing functions to install. - /// @param installData Optional data to be used by the account to handle the initial execution setup. Data - /// encoding is implementation-specific. - function installExecution(address module, ExecutionManifest calldata manifest, bytes calldata installData) - external; - - /// @notice Uninstall a module from the modular account. - /// @param module The module to uninstall. - /// @param manifest the manifest describing functions to uninstall. - /// @param uninstallData Optional data to be used by the account to handle the execution uninstallation. Data - /// encoding is implementation-specific. - function uninstallExecution(address module, ExecutionManifest calldata manifest, bytes calldata uninstallData) - external; - - /// @notice Installs a validation function across a set of execution selectors, and optionally mark it as a - /// global validation function. - /// @dev This does not validate anything against the manifest - the caller must ensure validity. - /// @param validationConfig The validation function to install, along with configuration flags. - /// @param selectors The selectors to install the validation function for. - /// @param installData Optional data to be used by the account to handle the initial validation setup. Data - /// encoding is implementation-specific. - /// @param hooks Optional hooks to install and associate with the validation function. Data encoding is - /// implementation-specific. - function installValidation( - ValidationConfig validationConfig, - bytes4[] calldata selectors, - bytes calldata installData, - bytes[] calldata hooks - ) external; - - /// @notice Uninstall a validation function from a set of execution selectors. - /// @param validationFunction The validation function to uninstall. - /// @param uninstallData Optional data to be used by the account to handle the validation uninstallation. Data - /// encoding is implementation-specific. - /// @param hookUninstallData Optional data to be used by the account to handle hook uninstallation. Data - /// encoding is implementation-specific. - function uninstallValidation( - ModuleEntity validationFunction, - bytes calldata uninstallData, - bytes[] calldata hookUninstallData - ) external; - - /// @notice Return a unique identifier for the account implementation. - /// @dev This function MUST return a string in the format "vendor.account.semver". The vendor and account - /// names MUST NOT contain a period character. - /// @return The account ID. - function accountId() external view returns (string memory); -} diff --git a/src/msca/6900/v0.8/interfaces/IModularAccountView.sol b/src/msca/6900/v0.8/interfaces/IModularAccountView.sol deleted file mode 100644 index 74ad049..0000000 --- a/src/msca/6900/v0.8/interfaces/IModularAccountView.sol +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2024 Circle Internet Group, Inc. All rights reserved. - - * SPDX-License-Identifier: GPL-3.0-or-later - - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -pragma solidity 0.8.24; - -import {ExecutionDataView, ValidationDataView} from "../common/Structs.sol"; -import {ModuleEntity} from "../common/Types.sol"; - -/** - * @dev Implements https://eips.ethereum.org/EIPS/eip-6900. MSCAs may implement this interface to support visibility in - * module configurations on-chain. - */ -interface IModularAccountView { - /// @notice Get the execution data for a selector. - /// @dev If the selector is a native function, the module address will be the address of the account. - /// @param selector The selector to get the data for. - /// @return ExecutionData The module address for this selector. - function getExecutionData(bytes4 selector) external view returns (ExecutionDataView memory); - - /// @notice Get the validation data for a validation. - /// @dev If the selector is a native function, the module address will be the address of the account. - /// @param validationFunction The validation function to get the data for. - /// @return ValidationData The module address for this selector. - function getValidationData(ModuleEntity validationFunction) external view returns (ValidationDataView memory); -} diff --git a/src/msca/6900/v0.8/interfaces/IModule.sol b/src/msca/6900/v0.8/interfaces/IModule.sol deleted file mode 100644 index 59960da..0000000 --- a/src/msca/6900/v0.8/interfaces/IModule.sol +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2024 Circle Internet Group, Inc. All rights reserved. - - * SPDX-License-Identifier: GPL-3.0-or-later - - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -pragma solidity 0.8.24; - -import {IERC165} from "@openzeppelin/contracts/interfaces/IERC165.sol"; - -/** - * @dev Implements https://eips.ethereum.org/EIPS/eip-6900. Modules must implement this interface to support module - * management and interactions with MSCAs. - */ -interface IModule is IERC165 { - /// @notice Initialize module data for the modular account. - /// @dev Called by the modular account during `installModule`. - /// @param data Optional bytes array to be decoded and used by the module to setup initial module data for the - /// modular account. - function onInstall(bytes calldata data) external; - - /// @notice Clear module data for the modular account. - /// @dev Called by the modular account during `uninstallModule`. - /// @param data Optional bytes array to be decoded and used by the module to clear module data for the modular - /// account. - function onUninstall(bytes calldata data) external; - - /// @notice Return a unique identifier for the module. - /// @dev This function MUST return a string in the format "vendor.module.semver". The vendor and module - /// names MUST NOT contain a period character. - /// @return The module ID. - function moduleId() external view returns (string memory); -} diff --git a/src/msca/6900/v0.8/interfaces/IValidationHookModule.sol b/src/msca/6900/v0.8/interfaces/IValidationHookModule.sol deleted file mode 100644 index e573cd2..0000000 --- a/src/msca/6900/v0.8/interfaces/IValidationHookModule.sol +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2024 Circle Internet Group, Inc. All rights reserved. - - * SPDX-License-Identifier: GPL-3.0-or-later - - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -pragma solidity 0.8.24; - -import {IModule} from "./IModule.sol"; -import {PackedUserOperation} from "@account-abstraction/contracts/interfaces/PackedUserOperation.sol"; - -/** - * @dev Implements https://eips.ethereum.org/EIPS/eip-6900. Modules must implement this interface to support module - * management and interactions with MSCAs. - */ -interface IValidationHookModule is IModule { - /// @notice Run the pre user operation validation hook specified by the `entityId`. - /// @dev Pre user operation validation hooks MUST NOT return an authorizer value other than 0 or 1. - /// @param entityId An identifier that routes the call to different internal implementations, should there - /// be more than one. - /// @param userOp The user operation. - /// @param userOpHash The user operation hash. - /// @return Packed validation data for validAfter (6 bytes), validUntil (6 bytes), and authorizer (20 bytes). - function preUserOpValidationHook(uint32 entityId, PackedUserOperation calldata userOp, bytes32 userOpHash) - external - returns (uint256); - - /// @notice Run the pre runtime validation hook specified by the `entityId`. - /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param entityId An identifier that routes the call to different internal implementations, should there - /// be more than one. - /// @param sender The caller address. - /// @param value The call value. - /// @param data The calldata sent. - function preRuntimeValidationHook( - uint32 entityId, - address sender, - uint256 value, - bytes calldata data, - bytes calldata authorization - ) external; - - /// @notice Run the pre signature validation hook specified by the `entityId`. - /// @dev To indicate the call should revert, the function MUST revert. - /// @param entityId An identifier that routes the call to different internal implementations, should there - /// be more than one. - /// @param sender The caller address. - /// @param hash The hash of the message being signed. - /// @param signature The signature of the message. - function preSignatureValidationHook(uint32 entityId, address sender, bytes32 hash, bytes calldata signature) - external - view; -} diff --git a/src/msca/6900/v0.8/interfaces/IValidationModule.sol b/src/msca/6900/v0.8/interfaces/IValidationModule.sol deleted file mode 100644 index 5b545b7..0000000 --- a/src/msca/6900/v0.8/interfaces/IValidationModule.sol +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2024 Circle Internet Group, Inc. All rights reserved. - - * SPDX-License-Identifier: GPL-3.0-or-later - - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -pragma solidity 0.8.24; - -import {IModule} from "./IModule.sol"; -import {PackedUserOperation} from "@account-abstraction/contracts/interfaces/PackedUserOperation.sol"; - -/** - * @dev Implements https://eips.ethereum.org/EIPS/eip-6900. Modules must implement this interface to support module - * management and interactions with MSCAs. - */ -interface IValidationModule is IModule { - /// @notice Run the user operation validationFunction specified by the `entityId`. - /// @param entityId An identifier that routes the call to different internal implementations, should there - /// be more than one. - /// @param userOp The user operation. - /// @param userOpHash The user operation hash. - /// @return Packed validation data for validAfter (6 bytes), validUntil (6 bytes), and authorizer (20 bytes). - function validateUserOp(uint32 entityId, PackedUserOperation calldata userOp, bytes32 userOpHash) - external - returns (uint256); - - /// @notice Run the runtime validationFunction specified by the `entityId`. - /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param account the account to validate for. - /// @param entityId An identifier that routes the call to different internal implementations, should there - /// be more than one. - /// @param sender The caller address. - /// @param value The call value. - /// @param data The calldata sent. - /// @param authorization Additional data for the validation function to use. - function validateRuntime( - address account, - uint32 entityId, - address sender, - uint256 value, - bytes calldata data, - bytes calldata authorization - ) external; - - /// @notice Validates a signature using ERC-1271. - /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param account the account to validate for. - /// @param entityId An identifier that routes the call to different internal implementations, should there - /// be more than one. - /// @param sender the address that sent the ERC-1271 request to the smart account - /// @param hash the hash of the ERC-1271 request - /// @param signature the signature of the ERC-1271 request - /// @return The ERC-1271 `MAGIC_VALUE` if the signature is valid, or 0xFFFFFFFF if invalid. - function validateSignature(address account, uint32 entityId, address sender, bytes32 hash, bytes calldata signature) - external - view - returns (bytes4); -} diff --git a/src/msca/6900/v0.8/libs/HookConfigLib.sol b/src/msca/6900/v0.8/libs/HookConfigLib.sol deleted file mode 100644 index f0c47df..0000000 --- a/src/msca/6900/v0.8/libs/HookConfigLib.sol +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright 2024 Circle Internet Group, Inc. All rights reserved. - - * SPDX-License-Identifier: GPL-3.0-or-later - - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -pragma solidity 0.8.24; - -import {EMPTY_MODULE_ENTITY, SENTINEL_BYTES32} from "../../../../common/Constants.sol"; - -import {ExecutionUtils} from "../../../../utils/ExecutionUtils.sol"; -import {Bytes32DLL} from "../../shared/common/Structs.sol"; -import {Bytes32DLLLib} from "../../shared/libs/Bytes32DLLLib.sol"; -import {PostExecHookToRun} from "../common/Structs.sol"; -import {HookConfig, ModuleEntity} from "../common/Types.sol"; -import {IExecutionHookModule} from "../interfaces/IExecutionHookModule.sol"; -import {ModuleEntityLib} from "./thirdparty/ModuleEntityLib.sol"; - -// Hook fields: -// module address -// entity ID -// hook flags - -// Hook flags: -// hook type -// exec hook: hasPre, hasPost -// validation hook - -// Hook config is a packed representation of a hook function and flags for its configuration. -// Layout: -// 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA________________________ // Address -// 0x________________________________________BBBBBBBB________________ // Entity ID -// 0x________________________________________________CC______________ // Hook Flags - -// Hook flags layout: -// 0b00000___ // unused -// 0b_____A__ // hasPre (exec only) -// 0b______B_ // hasPost (exec only) -// 0b_______C // hook type (0 for exec, 1 for validation) -/// @notice Built on top of 6900 reference impl. -library HookConfigLib { - using ModuleEntityLib for ModuleEntity; - using Bytes32DLLLib for Bytes32DLL; - - error PreExecHookFailed(ModuleEntity moduleEntity, bytes revertReason); - error PostExecHookFailed(ModuleEntity moduleEntity, bytes revertReason); - // Hook type constants - - // Exec has no bits set - bytes32 internal constant _HOOK_TYPE_EXEC = bytes32(uint256(0)); - // Validation has 1 bit in 8th bit the 25th byte - bytes32 internal constant _HOOK_TYPE_VALIDATION = bytes32(uint256(1) << 56); - - // Exec hook flags constants - // Pre hook has 1 bit in 6th bit in the 25th byte - bytes32 internal constant _EXEC_HOOK_HAS_PRE = bytes32(uint256(1) << 58); - // Post hook has 1 bit in 7th bit in the 25th byte - bytes32 internal constant _EXEC_HOOK_HAS_POST = bytes32(uint256(1) << 57); - - function _processPreExecHooks(Bytes32DLL storage executionHooks, bytes calldata data) - internal - returns (PostExecHookToRun[] memory postExecHooksToRun) - { - uint256 hooksCount = executionHooks.size(); - postExecHooksToRun = new PostExecHookToRun[](hooksCount); - uint256 totalPostExecHooksToRunCount = 0; - // copy post hook first - bytes32 startHook = SENTINEL_BYTES32; - for (uint256 i = 0; i < hooksCount; ++i) { - (bytes32[] memory execHooks, bytes32 nextHook) = executionHooks.getPaginated(startHook, 10); - for (uint256 j = 0; j < execHooks.length; ++j) { - HookConfig hookConfig = toHookConfig(execHooks[j]); - (ModuleEntity hookFunction, bool hasPre, bool hasPost) = unpackExecHook(hookConfig); - if (hasPost) { - postExecHooksToRun[totalPostExecHooksToRunCount++].postExecHook = hookFunction; - } - // run pre hook and copy the return data if there's a post hook - if (hasPre) { - bytes memory preExecHookReturnData = _processPreExecHook(hookFunction, data); - if (hasPost) { - // store the data in last postExecHook - postExecHooksToRun[totalPostExecHooksToRunCount - 1].preExecHookReturnData = - preExecHookReturnData; - } - } - } - if (nextHook == SENTINEL_BYTES32) { - break; - } - startHook = nextHook; - } - } - - /// @dev return preExecHookReturnData - function _processPreExecHook(ModuleEntity preExecHook, bytes calldata data) internal returns (bytes memory) { - (address module, uint32 entityId) = preExecHook.unpack(); - try IExecutionHookModule(module).preExecutionHook(entityId, msg.sender, msg.value, data) returns ( - bytes memory returnData - ) { - return returnData; - } catch { - bytes memory revertReason = ExecutionUtils.fetchReturnData(); - revert PreExecHookFailed(preExecHook, revertReason); - } - } - - function _processPostExecHooks(PostExecHookToRun[] memory postExecHooksToRun) internal { - uint256 length = postExecHooksToRun.length; - for (uint256 i = 0; i < length; ++i) { - ModuleEntity postExecHook = postExecHooksToRun[i].postExecHook; - if (ModuleEntity.unwrap(postExecHooksToRun[i].postExecHook) == EMPTY_MODULE_ENTITY) { - // from preOnlyHook - continue; - } - (address module, uint32 entityId) = postExecHook.unpack(); - // solhint-disable-next-line no-empty-blocks - try IExecutionHookModule(module).postExecutionHook(entityId, postExecHooksToRun[i].preExecHookReturnData) {} - catch { - bytes memory revertReason = ExecutionUtils.fetchReturnData(); - revert PostExecHookFailed(postExecHook, revertReason); - } - } - } - - function _getExecutionHooks(Bytes32DLL storage executionHooks) internal view returns (HookConfig[] memory hooks) { - uint256 hooksCount = executionHooks.size(); - hooks = new HookConfig[](hooksCount); - uint256 totalExecHooksCount = 0; - bytes32 startHook = SENTINEL_BYTES32; - for (uint256 i = 0; i < hooksCount; ++i) { - (bytes32[] memory execHooks, bytes32 nextHook) = executionHooks.getPaginated(startHook, 10); - for (uint256 j = 0; j < execHooks.length; ++j) { - hooks[totalExecHooksCount] = toHookConfig(execHooks[j]); - totalExecHooksCount++; - } - if (nextHook == SENTINEL_BYTES32) { - break; - } - startHook = nextHook; - } - return hooks; - } - - function packValidationHook(ModuleEntity _hookFunction) internal pure returns (HookConfig) { - return HookConfig.wrap(bytes25(bytes25(ModuleEntity.unwrap(_hookFunction)) | bytes25(_HOOK_TYPE_VALIDATION))); - } - - function packValidationHook(address _module, uint32 _entityId) internal pure returns (HookConfig) { - return HookConfig.wrap( - bytes25( - // module address stored in the first 20 bytes - bytes25(bytes20(_module)) - // entityId stored in the 21st - 24th byte - | bytes25(bytes24(uint192(_entityId))) | bytes25(_HOOK_TYPE_VALIDATION) - ) - ); - } - - function packExecHook(ModuleEntity _hookFunction, bool _hasPre, bool _hasPost) internal pure returns (HookConfig) { - return HookConfig.wrap( - bytes25( - bytes25(ModuleEntity.unwrap(_hookFunction)) - // | bytes25(_HOOK_TYPE_EXEC) // Can omit because exec type is 0 - | bytes25(_hasPre ? _EXEC_HOOK_HAS_PRE : bytes32(0)) - | bytes25(_hasPost ? _EXEC_HOOK_HAS_POST : bytes32(0)) - ) - ); - } - - function packExecHook(address _module, uint32 _entityId, bool _hasPre, bool _hasPost) - internal - pure - returns (HookConfig) - { - return HookConfig.wrap( - bytes25( - // module address stored in the first 20 bytes - bytes25(bytes20(_module)) - // entityId stored in the 21st - 24th byte - | bytes25(bytes24(uint192(_entityId))) - // | bytes25(_HOOK_TYPE_EXEC) // Can omit because exec type is 0 - | bytes25(_hasPre ? _EXEC_HOOK_HAS_PRE : bytes32(0)) - | bytes25(_hasPost ? _EXEC_HOOK_HAS_POST : bytes32(0)) - ) - ); - } - - function unpackValidationHook(HookConfig _config) internal pure returns (ModuleEntity _hookFunction) { - bytes25 configBytes = HookConfig.unwrap(_config); - _hookFunction = ModuleEntity.wrap(bytes24(configBytes)); - } - - function unpackExecHook(HookConfig _config) - internal - pure - returns (ModuleEntity _hookFunction, bool _hasPre, bool _hasPost) - { - bytes25 configBytes = HookConfig.unwrap(_config); - _hookFunction = ModuleEntity.wrap(bytes24(configBytes)); - _hasPre = configBytes & _EXEC_HOOK_HAS_PRE != 0; - _hasPost = configBytes & _EXEC_HOOK_HAS_POST != 0; - } - - function getModule(HookConfig _config) internal pure returns (address) { - return address(bytes20(HookConfig.unwrap(_config))); - } - - function getEntityId(HookConfig _config) internal pure returns (uint32) { - return uint32(bytes4(HookConfig.unwrap(_config) << 160)); - } - - function getModuleEntity(HookConfig _config) internal pure returns (ModuleEntity) { - return ModuleEntity.wrap(bytes24(HookConfig.unwrap(_config))); - } - - // Check if the hook is a validation hook - function isValidationHook(HookConfig _config) internal pure returns (bool) { - return HookConfig.unwrap(_config) & _HOOK_TYPE_VALIDATION != 0; - } - - // @notice Check if the exec hook has a pre hook - // Undefined behavior if the hook is not an exec hook - function hasPreHook(HookConfig _config) internal pure returns (bool) { - return HookConfig.unwrap(_config) & _EXEC_HOOK_HAS_PRE != 0; - } - - // @notice Check if the exec hook has a post hook - // Undefined behavior if the hook is not an exec hook - function hasPostHook(HookConfig _config) internal pure returns (bool) { - return HookConfig.unwrap(_config) & _EXEC_HOOK_HAS_POST != 0; - } - - function toBytes32(HookConfig _config) internal pure returns (bytes32) { - return bytes32(HookConfig.unwrap(_config)); - } - - function toHookConfig(bytes32 _value) internal pure returns (HookConfig hookConfig) { - return HookConfig.wrap(bytes25(_value)); - } -} diff --git a/src/msca/6900/v0.8/libs/HookLib.sol b/src/msca/6900/v0.8/libs/HookLib.sol new file mode 100644 index 0000000..115aa25 --- /dev/null +++ b/src/msca/6900/v0.8/libs/HookLib.sol @@ -0,0 +1,152 @@ +/* + * Copyright 2024 Circle Internet Group, Inc. All rights reserved. + + * SPDX-License-Identifier: GPL-3.0-or-later + + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +pragma solidity 0.8.24; + +import {EMPTY_MODULE_ENTITY, SENTINEL_BYTES32} from "../../../../common/Constants.sol"; + +import {ExecutionUtils} from "../../../../utils/ExecutionUtils.sol"; +import {Bytes32DLL} from "../../shared/common/Structs.sol"; +import {Bytes32DLLLib} from "../../shared/libs/Bytes32DLLLib.sol"; +import {PostExecHookToRun} from "../common/Structs.sol"; + +import {IExecutionHookModule} from "@erc6900/reference-implementation/interfaces/IExecutionHookModule.sol"; +import {HookConfig, ModuleEntity} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol"; +import {HookConfigLib} from "@erc6900/reference-implementation/libraries/HookConfigLib.sol"; +import {ModuleEntityLib} from "@erc6900/reference-implementation/libraries/ModuleEntityLib.sol"; + +library HookLib { + using Bytes32DLLLib for Bytes32DLL; + using HookConfigLib for HookConfig; + using ModuleEntityLib for ModuleEntity; + + error PreExecHookFailed(ModuleEntity moduleEntity, bytes revertReason); + error PostExecHookFailed(ModuleEntity moduleEntity, bytes revertReason); + + function _processPreExecHooks(Bytes32DLL storage executionHooks, bytes calldata data) + internal + returns (PostExecHookToRun[] memory postExecHooksToRun) + { + HookConfig[] memory hookConfigs; + // copy post hook first so the post hooks will not be affected by state changes + (postExecHooksToRun, hookConfigs) = _copyPostExecHooks(executionHooks); + for (uint256 i = 0; i < postExecHooksToRun.length; ++i) { + (ModuleEntity hookFunction, bool hasPre, bool hasPost) = hookConfigs[i].unpackExecHook(); + // run pre hook and copy the return data if there's a post hook + if (hasPre) { + bytes memory preExecHookReturnData = _processPreExecHook(hookFunction, data); + if (hasPost) { + postExecHooksToRun[i].preExecHookReturnData = preExecHookReturnData; + } + } + } + } + + /// @return postExecHooksToRun and hookConfigs, hookConfigs are used for running pre hooks + function _copyPostExecHooks(Bytes32DLL storage executionHooks) + internal + view + returns (PostExecHookToRun[] memory postExecHooksToRun, HookConfig[] memory hookConfigs) + { + uint256 hooksCount = executionHooks.size(); + postExecHooksToRun = new PostExecHookToRun[](hooksCount); + hookConfigs = new HookConfig[](hooksCount); + uint256 totalPostExecHooksToRunCount = 0; + bytes32 startHook = SENTINEL_BYTES32; + for (uint256 i = 0; i < hooksCount; ++i) { + (bytes32[] memory execHooks, bytes32 nextHook) = executionHooks.getPaginated(startHook, 50); + for (uint256 j = 0; j < execHooks.length; ++j) { + hookConfigs[totalPostExecHooksToRunCount] = toHookConfig(execHooks[j]); + (ModuleEntity hookFunction,, bool hasPost) = hookConfigs[totalPostExecHooksToRunCount].unpackExecHook(); + if (hasPost) { + postExecHooksToRun[totalPostExecHooksToRunCount].postExecHook = hookFunction; + } + unchecked { + totalPostExecHooksToRunCount++; + } + } + if (nextHook == SENTINEL_BYTES32) { + break; + } + startHook = nextHook; + } + } + + /// @dev return preExecHookReturnData + function _processPreExecHook(ModuleEntity preExecHook, bytes calldata data) internal returns (bytes memory) { + (address module, uint32 entityId) = preExecHook.unpack(); + try IExecutionHookModule(module).preExecutionHook(entityId, msg.sender, msg.value, data) returns ( + bytes memory returnData + ) { + return returnData; + } catch { + bytes memory revertReason = ExecutionUtils.fetchReturnData(); + revert PreExecHookFailed(preExecHook, revertReason); + } + } + + // @dev post exec hooks should be executed in reverse order of the pre exec hooks + function _processPostExecHooks(PostExecHookToRun[] memory postExecHooksToRun) internal { + uint256 length = postExecHooksToRun.length; + for (uint256 i = length; i > 0;) { + // adjust for array index + unchecked { + i--; + } + ModuleEntity postExecHook = postExecHooksToRun[i].postExecHook; + if (ModuleEntity.unwrap(postExecHooksToRun[i].postExecHook) == EMPTY_MODULE_ENTITY) { + // from preOnlyHook + continue; + } + (address module, uint32 entityId) = postExecHook.unpack(); + // solhint-disable-next-line no-empty-blocks + try IExecutionHookModule(module).postExecutionHook(entityId, postExecHooksToRun[i].preExecHookReturnData) {} + catch { + bytes memory revertReason = ExecutionUtils.fetchReturnData(); + revert PostExecHookFailed(postExecHook, revertReason); + } + } + } + + function _toHookConfigs(Bytes32DLL storage hooksDLL) internal view returns (HookConfig[] memory hooks) { + uint256 hooksCount = hooksDLL.size(); + hooks = new HookConfig[](hooksCount); + uint256 totalExecHooksCount = 0; + bytes32 startHook = SENTINEL_BYTES32; + for (uint256 i = 0; i < hooksCount; ++i) { + (bytes32[] memory execHooks, bytes32 nextHook) = hooksDLL.getPaginated(startHook, 50); + for (uint256 j = 0; j < execHooks.length; ++j) { + hooks[totalExecHooksCount] = toHookConfig(execHooks[j]); + totalExecHooksCount++; + } + if (nextHook == SENTINEL_BYTES32) { + break; + } + startHook = nextHook; + } + return hooks; + } + + function toBytes32(HookConfig _config) internal pure returns (bytes32) { + return bytes32(HookConfig.unwrap(_config)); + } + + function toHookConfig(bytes32 _value) internal pure returns (HookConfig hookConfig) { + return HookConfig.wrap(bytes25(_value)); + } +} diff --git a/src/msca/6900/v0.8/libs/SelectorRegistryLib.sol b/src/msca/6900/v0.8/libs/SelectorRegistryLib.sol index 17a77ce..ec03fbc 100644 --- a/src/msca/6900/v0.8/libs/SelectorRegistryLib.sol +++ b/src/msca/6900/v0.8/libs/SelectorRegistryLib.sol @@ -18,41 +18,32 @@ */ pragma solidity 0.8.24; -import {IExecutionHookModule} from "../interfaces/IExecutionHookModule.sol"; -import {IModularAccountView} from "../interfaces/IModularAccountView.sol"; -import {IModule} from "../interfaces/IModule.sol"; +import {IExecutionHookModule} from "@erc6900/reference-implementation/interfaces/IExecutionHookModule.sol"; +import {IModularAccountView} from "@erc6900/reference-implementation/interfaces/IModularAccountView.sol"; +import {IModule} from "@erc6900/reference-implementation/interfaces/IModule.sol"; -import {IModularAccount} from "../interfaces/IModularAccount.sol"; +import {IModularAccount} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol"; -import {IValidationHookModule} from "../interfaces/IValidationHookModule.sol"; -import {IValidationModule} from "../interfaces/IValidationModule.sol"; +import {IAccount} from "@account-abstraction/contracts/interfaces/IAccount.sol"; import {IAggregator} from "@account-abstraction/contracts/interfaces/IAggregator.sol"; import {IPaymaster} from "@account-abstraction/contracts/interfaces/IPaymaster.sol"; +import {IExecutionModule} from "@erc6900/reference-implementation/interfaces/IExecutionModule.sol"; +import {IValidationHookModule} from "@erc6900/reference-implementation/interfaces/IValidationHookModule.sol"; +import {IValidationModule} from "@erc6900/reference-implementation/interfaces/IValidationModule.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import {IExecutionModule} from "../interfaces/IExecutionModule.sol"; - -import {IAccount} from "@account-abstraction/contracts/interfaces/IAccount.sol"; import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; library SelectorRegistryLib { /** - * @dev Check if the selector is native execution function that allows global validation. + * @dev Check if the selector is native execution function. * @param selector the function selector. */ - function _isGlobalValidationAllowedNativeExecutionFunction(bytes4 selector) internal pure returns (bool) { + function _isNativeExecutionFunction(bytes4 selector) internal pure returns (bool) { return selector == IModularAccount.execute.selector || selector == IModularAccount.executeBatch.selector || selector == IModularAccount.installExecution.selector || selector == IModularAccount.uninstallExecution.selector - || selector == UUPSUpgradeable.upgradeToAndCall.selector; - } - - /** - * @dev Check if the selector is native execution function. - * @param selector the function selector. - */ - function _isNativeExecutionFunction(bytes4 selector) internal pure returns (bool) { - return _isGlobalValidationAllowedNativeExecutionFunction(selector) + || selector == UUPSUpgradeable.upgradeToAndCall.selector || selector == IModularAccount.installValidation.selector || selector == IModularAccount.uninstallValidation.selector; } diff --git a/src/msca/6900/v0.8/libs/WalletStorageLib.sol b/src/msca/6900/v0.8/libs/WalletStorageLib.sol index d39d02f..90fe966 100644 --- a/src/msca/6900/v0.8/libs/WalletStorageLib.sol +++ b/src/msca/6900/v0.8/libs/WalletStorageLib.sol @@ -18,8 +18,8 @@ */ pragma solidity 0.8.24; -import {ExecutionDetail, ValidationDetail} from "../common/Structs.sol"; -import {ModuleEntity} from "../common/Types.sol"; +import {ExecutionStorage, ValidationStorage} from "../common/Structs.sol"; +import {ModuleEntity} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol"; /// @dev The same storage will be used for ERC6900 v0.8 MSCAs. library WalletStorageLib { @@ -31,8 +31,8 @@ library WalletStorageLib { // interfaceId => counter mapping(bytes4 => uint256) supportedInterfaces; // find module or native function execution detail by selector - mapping(bytes4 => ExecutionDetail) executionDetails; - mapping(ModuleEntity validationFunction => ValidationDetail) validationDetails; + mapping(bytes4 => ExecutionStorage) executionStorage; + mapping(ModuleEntity validationFunction => ValidationStorage) validationStorage; /// indicates that the contract has been initialized uint8 initialized; /// indicates that the contract is in the process of being initialized diff --git a/src/msca/6900/v0.8/libs/thirdparty/ModuleEntityLib.sol b/src/msca/6900/v0.8/libs/thirdparty/ModuleEntityLib.sol deleted file mode 100644 index 0aed61e..0000000 --- a/src/msca/6900/v0.8/libs/thirdparty/ModuleEntityLib.sol +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2024 Circle Internet Group, Inc. All rights reserved. - - * SPDX-License-Identifier: GPL-3.0-or-later - - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -pragma solidity 0.8.24; - -import {ModuleEntity} from "../../common/Types.sol"; - -// ModuleEntity is a packed representation of a module function -// Layout: -// 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA________________________ // Address -// 0x________________________________________BBBBBBBB________________ // Entity ID -// 0x________________________________________________0000000000000000 // unused -/// @notice Inspired by 6900 reference impl with some modifications. -library ModuleEntityLib { - function pack(address addr, uint32 entityId) internal pure returns (ModuleEntity) { - return ModuleEntity.wrap(bytes24(bytes20(addr)) | bytes24(uint192(entityId))); - } - - function unpack(ModuleEntity moduleEntity) internal pure returns (address addr, uint32 entityId) { - bytes24 underlying = ModuleEntity.unwrap(moduleEntity); - addr = address(bytes20(underlying)); - entityId = uint32(bytes4(underlying << 160)); - } - - function isEmpty(ModuleEntity moduleEntity) internal pure returns (bool) { - return ModuleEntity.unwrap(moduleEntity) == bytes24(0); - } - - function notEmpty(ModuleEntity moduleEntity) internal pure returns (bool) { - return ModuleEntity.unwrap(moduleEntity) != bytes24(0); - } - - function eq(ModuleEntity a, ModuleEntity b) internal pure returns (bool) { - return ModuleEntity.unwrap(a) == ModuleEntity.unwrap(b); - } - - function notEq(ModuleEntity a, ModuleEntity b) internal pure returns (bool) { - return ModuleEntity.unwrap(a) != ModuleEntity.unwrap(b); - } - - function module(ModuleEntity moduleEntity) internal pure returns (address addr) { - bytes24 underlying = ModuleEntity.unwrap(moduleEntity); - addr = address(bytes20(underlying)); - } -} diff --git a/src/msca/6900/v0.8/libs/thirdparty/SparseCalldataSegmentLib.sol b/src/msca/6900/v0.8/libs/thirdparty/SparseCalldataSegmentLib.sol deleted file mode 100644 index 28f79e6..0000000 --- a/src/msca/6900/v0.8/libs/thirdparty/SparseCalldataSegmentLib.sol +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2024 Circle Internet Group, Inc. All rights reserved. - - * SPDX-License-Identifier: GPL-3.0-or-later - - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -pragma solidity 0.8.24; - -import {RESERVED_VALIDATION_DATA_INDEX} from "../../common/Constants.sol"; - -// TODO: remove this when SparseCalldataSegmentLib is moved to modular account lib -// https://github.com/erc6900/modular-account-libs/issues/2 -/// @title Sparse Calldata Segment Library -/// @notice Inspired by 6900 reference implementation for working with sparsely-packed calldata segments, identified -/// with an index. -/// @dev The first byte of each segment is the index of the segment. -/// To prevent accidental stack-to-deep errors, the body and index of the segment are extracted separately, rather -/// than inline as part of the tuple returned by `getNextSegment`. -library SparseCalldataSegmentLib { - error NonCanonicalEncoding(); - error SegmentOutOfOrder(); - error ValidationSignatureSegmentMissing(); - - /// @notice Splits out a segment of calldata, sparsely-packed. - /// The expected format is: - /// [uint32(len(segment0)), segment0, uint32(len(segment1)), segment1, ... uint32(len(segmentN)), segmentN] - /// @param source The calldata to extract the segment from. - /// @return segment The extracted segment. Using the above example, this would be segment0. - /// @return remainder The remaining calldata. Using the above example, - /// this would start at uint32(len(segment1)) and continue to the end at segmentN. - function getNextSegment(bytes calldata source) - internal - pure - returns (bytes calldata segment, bytes calldata remainder) - { - // The first 4 bytes hold the length of the segment, excluding the index. - uint32 length = uint32(bytes4(source[:4])); - - // The offset of the remainder of the calldata. - uint256 remainderOffset = 4 + length; - - // The segment is the next `length` + 1 bytes, to account for the index. - // By convention, the first byte of each segment is the index of the segment. - segment = source[4:remainderOffset]; - - // The remainder is the rest of the calldata. - remainder = source[remainderOffset:]; - } - - /// @notice Extracts the index from a segment. - /// @dev The first byte of the segment is the index. - /// @param segment The segment to extract the index from - /// @return The index of the segment - function getIndex(bytes calldata segment) internal pure returns (uint8) { - return uint8(segment[0]); - } - - /// @notice Extracts the body from a segment. - /// @dev The body is the segment without the index. - /// @param segment The segment to extract the body from - /// @return The body of the segment. - function getBody(bytes calldata segment) internal pure returns (bytes calldata) { - return segment[1:]; - } - - /// @notice If the index of the next segment in the source equals the provided index, return the next body and - /// advance the source by one segment. - /// @dev Reverts if the index of the next segment is less than the provided index, or if the extracted segment - /// has length 0. - /// @param source The calldata to extract the segment from. - /// @param index The index of the segment to extract. - /// @return A tuple containing the extracted segment's body, or an empty buffer if the index is not found, and - /// the remaining calldata. - function advanceSegmentIfAtIndex(bytes calldata source, uint8 index) - internal - pure - returns (bytes memory, bytes calldata) - { - // the index of the next segment in the source - uint8 nextIndex = uint8(source[4]); - if (nextIndex < index) { - revert SegmentOutOfOrder(); - } - if (nextIndex == index) { - (bytes calldata segment, bytes calldata remainder) = getNextSegment(source); - segment = getBody(segment); - if (segment.length == 0) { - revert NonCanonicalEncoding(); - } - return (segment, remainder); - } - return ("", source); - } - - /// @notice Extracts the final segment from the calldata. - /// @dev The final segment must have an index equal to `RESERVED_VALIDATION_DATA_INDEX`. - /// Reverts if the final segment is not found or if there is remaining calldata after the final segment. - /// @param source The calldata to extract the final segment from. - /// @return The body of the final segment. - function getFinalSegment(bytes calldata source) internal pure returns (bytes calldata) { - (bytes calldata segment, bytes calldata remainder) = getNextSegment(source); - // a single byte index caps the total number of pre-validation hooks at 255 - if (getIndex(segment) != RESERVED_VALIDATION_DATA_INDEX) { - revert ValidationSignatureSegmentMissing(); - } - if (remainder.length != 0) { - revert NonCanonicalEncoding(); - } - return getBody(segment); - } -} diff --git a/src/msca/6900/v0.8/libs/thirdparty/ValidationConfigLib.sol b/src/msca/6900/v0.8/libs/thirdparty/ValidationConfigLib.sol deleted file mode 100644 index f58262d..0000000 --- a/src/msca/6900/v0.8/libs/thirdparty/ValidationConfigLib.sol +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright 2024 Circle Internet Group, Inc. All rights reserved. - - * SPDX-License-Identifier: GPL-3.0-or-later - - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -pragma solidity 0.8.24; - -import {ModuleEntity, ValidationConfig} from "../../common/Types.sol"; - -// ValidationConfig is a packed representation of a validation function and flags for its configuration. -// Layout: -// 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA________________________ // Address -// 0x________________________________________BBBBBBBB________________ // Entity ID -// 0x________________________________________________CC______________ // Validation flags -// 0x__________________________________________________00000000000000 // unused -// -// Validation flags layout: -// 0b00000___ // unused -// 0b_____A__ // isGlobal -// 0b______B_ // isSignatureValidation -// 0b_______C // isUserOpValidation -// @notice Inspired by 6900 reference impl with some modifications. -library ValidationConfigLib { - // is user op validation flag stored in last bit of the 25th byte - bytes32 internal constant _VALIDATION_FLAG_IS_USER_OP = bytes32(uint256(1) << 56); - // is signature validation flag stored in second to last bit of the 25th byte - bytes32 internal constant _VALIDATION_FLAG_IS_SIGNATURE = bytes32(uint256(1) << 57); - // is global flag stored in the third to last bit of the 25th byte - bytes32 internal constant _VALIDATION_FLAG_IS_GLOBAL = bytes32(uint256(1) << 58); - - function pack( - ModuleEntity _validationFunction, - bool _isGlobal, - bool _isSignatureValidation, - bool _isUserOpValidation - ) internal pure returns (ValidationConfig) { - return ValidationConfig.wrap( - bytes25( - bytes25(ModuleEntity.unwrap(_validationFunction)) - | bytes25(bytes32(_isGlobal ? _VALIDATION_FLAG_IS_GLOBAL : bytes32(0))) - | bytes25(bytes32(_isSignatureValidation ? _VALIDATION_FLAG_IS_SIGNATURE : bytes32(0))) - | bytes25(bytes32(_isUserOpValidation ? _VALIDATION_FLAG_IS_USER_OP : bytes32(0))) - ) - ); - } - - function pack( - address _module, - uint32 _entityId, - bool _isGlobal, - bool _isSignatureValidation, - bool _isUserOpValidation - ) internal pure returns (ValidationConfig) { - return ValidationConfig.wrap( - bytes25( - // module address stored in the first 20 bytes - bytes25(bytes20(_module)) - // entityId stored in the 21st - 24th byte - | bytes25(bytes24(uint192(_entityId))) - | bytes25(bytes32(_isGlobal ? _VALIDATION_FLAG_IS_GLOBAL : bytes32(0))) - | bytes25(bytes32(_isSignatureValidation ? _VALIDATION_FLAG_IS_SIGNATURE : bytes32(0))) - | bytes25(bytes32(_isUserOpValidation ? _VALIDATION_FLAG_IS_USER_OP : bytes32(0))) - ) - ); - } - - function unpackUnderlying(ValidationConfig config) - internal - pure - returns (address _module, uint32 _entityId, uint8 flags) - { - bytes25 configBytes = ValidationConfig.unwrap(config); - _module = address(bytes20(configBytes)); - _entityId = uint32(bytes4(configBytes << 160)); - flags = uint8(configBytes[24]); - } - - function unpack(ValidationConfig config) internal pure returns (ModuleEntity _validationFunction, uint8 flags) { - bytes25 configBytes = ValidationConfig.unwrap(config); - _validationFunction = ModuleEntity.wrap(bytes24(configBytes)); - flags = uint8(configBytes[24]); - } - - function module(ValidationConfig config) internal pure returns (address) { - return address(bytes20(ValidationConfig.unwrap(config))); - } - - function entityId(ValidationConfig config) internal pure returns (uint32) { - return uint32(bytes4(ValidationConfig.unwrap(config) << 160)); - } - - function moduleEntity(ValidationConfig config) internal pure returns (ModuleEntity) { - return ModuleEntity.wrap(bytes24(ValidationConfig.unwrap(config))); - } - - function isGlobal(ValidationConfig config) internal pure returns (bool) { - return ValidationConfig.unwrap(config) & _VALIDATION_FLAG_IS_GLOBAL != 0; - } - - function isGlobal(uint8 flags) internal pure returns (bool) { - // 00000100 - return flags & 0x04 != 0; - } - - function isSignatureValidation(ValidationConfig config) internal pure returns (bool) { - return ValidationConfig.unwrap(config) & _VALIDATION_FLAG_IS_SIGNATURE != 0; - } - - function isSignatureValidation(uint8 flags) internal pure returns (bool) { - // 00000010 - return flags & 0x02 != 0; - } - - function isUserOpValidation(ValidationConfig config) internal pure returns (bool) { - return ValidationConfig.unwrap(config) & _VALIDATION_FLAG_IS_USER_OP != 0; - } - - function isUserOpValidation(uint8 flags) internal pure returns (bool) { - // 00000001 - return flags & 0x01 != 0; - } -} diff --git a/src/msca/6900/v0.8/managers/StandardExecutor.sol b/src/msca/6900/v0.8/managers/StandardExecutor.sol index c6ce4b2..d46c17d 100644 --- a/src/msca/6900/v0.8/managers/StandardExecutor.sol +++ b/src/msca/6900/v0.8/managers/StandardExecutor.sol @@ -19,7 +19,7 @@ pragma solidity 0.8.24; import {ExecutionUtils} from "../../../../utils/ExecutionUtils.sol"; -import {Call} from "../common/Structs.sol"; +import {Call} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol"; /** * @dev Default implementation of https://eips.ethereum.org/EIPS/eip-6900. MSCAs must implement this interface to diff --git a/src/msca/6900/v0.8/modules/BaseModule.sol b/src/msca/6900/v0.8/modules/BaseModule.sol index 2d27741..274e9a8 100644 --- a/src/msca/6900/v0.8/modules/BaseModule.sol +++ b/src/msca/6900/v0.8/modules/BaseModule.sol @@ -18,7 +18,7 @@ */ pragma solidity 0.8.24; -import {IModule} from "../interfaces/IModule.sol"; +import {IModule} from "@erc6900/reference-implementation/interfaces/IModule.sol"; import {IERC165} from "@openzeppelin/contracts/interfaces/IERC165.sol"; import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; diff --git a/src/msca/6900/v0.8/modules/addressbook/ColdStorageAddressBookModule.sol b/src/msca/6900/v0.8/modules/addressbook/ColdStorageAddressBookModule.sol index b8edcaf..2112d74 100644 --- a/src/msca/6900/v0.8/modules/addressbook/ColdStorageAddressBookModule.sol +++ b/src/msca/6900/v0.8/modules/addressbook/ColdStorageAddressBookModule.sol @@ -21,14 +21,19 @@ pragma solidity 0.8.24; import {SIG_VALIDATION_FAILED, SIG_VALIDATION_SUCCEEDED} from "../../../../../common/Constants.sol"; import {CastLib} from "../../../../../libs/CastLib.sol"; import {RecipientAddressLib} from "../../../../../libs/RecipientAddressLib.sol"; -import {Unsupported} from "../../../shared/common/Errors.sol"; -import {ExecutionManifest, ManifestExecutionFunction} from "../../common/ModuleManifest.sol"; +import {SignatureInflation, Unsupported} from "../../../shared/common/Errors.sol"; + import {Call} from "../../common/Structs.sol"; -import {IModule} from "../../interfaces/IModule.sol"; -import {IValidationHookModule} from "../../interfaces/IValidationHookModule.sol"; + import {BaseModule} from "../BaseModule.sol"; import {IAddressBookModule} from "./IAddressBookModule.sol"; import {PackedUserOperation} from "@account-abstraction/contracts/interfaces/PackedUserOperation.sol"; +import { + ExecutionManifest, + ManifestExecutionFunction +} from "@erc6900/reference-implementation/interfaces/IExecutionModule.sol"; +import {IModule} from "@erc6900/reference-implementation/interfaces/IModule.sol"; +import {IValidationHookModule} from "@erc6900/reference-implementation/interfaces/IValidationHookModule.sol"; import { AssociatedLinkedListSet, @@ -143,6 +148,10 @@ contract ColdStorageAddressBookModule is IAddressBookModule, BaseModule { override returns (uint256 validationData) { + // TODO: add tests when we revamp this WIP module soon + if (userOp.signature.length > 0) { + revert SignatureInflation(); + } (userOpHash); if (entityId == uint32(EntityId.PRE_VALIDATION_HOOK_EXECUTE_ADDRESS_BOOK)) { // This functionality is exclusively compatible with the IStandardExecutor.execute as delineated in the @@ -214,7 +223,7 @@ contract ColdStorageAddressBookModule is IAddressBookModule, BaseModule { function preSignatureValidationHook(uint32 entityId, address sender, bytes32 hash, bytes calldata signature) external - view + pure override { (entityId, sender, hash, signature); diff --git a/src/msca/6900/v0.8/modules/addressbook/IAddressBookModule.sol b/src/msca/6900/v0.8/modules/addressbook/IAddressBookModule.sol index 7f50097..3b2fd17 100644 --- a/src/msca/6900/v0.8/modules/addressbook/IAddressBookModule.sol +++ b/src/msca/6900/v0.8/modules/addressbook/IAddressBookModule.sol @@ -18,8 +18,8 @@ */ pragma solidity 0.8.24; -import {IExecutionModule} from "../../interfaces/IExecutionModule.sol"; -import {IValidationHookModule} from "../../interfaces/IValidationHookModule.sol"; +import {IExecutionModule} from "@erc6900/reference-implementation/interfaces/IExecutionModule.sol"; +import {IValidationHookModule} from "@erc6900/reference-implementation/interfaces/IValidationHookModule.sol"; /** * @dev Interface for address book module. diff --git a/src/msca/6900/v0.8/modules/multisig/BaseMultisigModule.sol b/src/msca/6900/v0.8/modules/multisig/BaseMultisigModule.sol index 13ca953..bb0859a 100644 --- a/src/msca/6900/v0.8/modules/multisig/BaseMultisigModule.sol +++ b/src/msca/6900/v0.8/modules/multisig/BaseMultisigModule.sol @@ -28,9 +28,10 @@ import { } from "../../../../../common/Constants.sol"; import {NotImplementedFunction} from "../../../shared/common/Errors.sol"; -import {IValidationModule} from "../../interfaces/IValidationModule.sol"; + import {BaseModule} from "../BaseModule.sol"; import {PackedUserOperation} from "@account-abstraction/contracts/interfaces/PackedUserOperation.sol"; +import {IValidationModule} from "@erc6900/reference-implementation/interfaces/IValidationModule.sol"; import { AssociatedLinkedListSet, AssociatedLinkedListSetLib @@ -128,7 +129,7 @@ abstract contract BaseMultisigModule is IValidationModule, BaseModule { uint256 value, bytes calldata data, bytes calldata authorization - ) external view override { + ) external pure override { // TODO: implement this - the signatures can be put in the validationData field of the runtime validation // function (account, sender, value, data, authorization); diff --git a/src/msca/6900/v0.8/modules/multisig/BaseWeightedMultisigModule.sol b/src/msca/6900/v0.8/modules/multisig/BaseWeightedMultisigModule.sol index 4f0a701..c2d2b3d 100644 --- a/src/msca/6900/v0.8/modules/multisig/BaseWeightedMultisigModule.sol +++ b/src/msca/6900/v0.8/modules/multisig/BaseWeightedMultisigModule.sol @@ -25,12 +25,12 @@ import {AddressBytesLib} from "../../../../../libs/AddressBytesLib.sol"; import {PublicKeyLib} from "../../../../../libs/PublicKeyLib.sol"; import {SetValueLib} from "../../../../../libs/SetValueLib.sol"; import {NotImplemented} from "../../../shared/common/Errors.sol"; -import {IModule} from "../../interfaces/IModule.sol"; +import {IModule} from "@erc6900/reference-implementation/interfaces/IModule.sol"; -import {IValidationModule} from "../../interfaces/IValidationModule.sol"; import {BaseModule} from "../BaseModule.sol"; import {BaseMultisigModule} from "./BaseMultisigModule.sol"; import {IWeightedMultisigModule} from "./IWeightedMultisigModule.sol"; +import {IValidationModule} from "@erc6900/reference-implementation/interfaces/IValidationModule.sol"; import { AssociatedLinkedListSet, @@ -258,7 +258,7 @@ abstract contract BaseWeightedMultisigModule is IWeightedMultisigModule, BaseMul bytes30[] memory ownersToUpdate, OwnerData[] memory newOwnersData, uint256 newThresholdWeight - ) internal { + ) internal pure { (ownersToUpdate, newOwnersData, newThresholdWeight); revert NotImplemented(msg.sig, 0); } diff --git a/src/msca/6900/v0.8/modules/multisig/README.md b/src/msca/6900/v0.8/modules/multisig/README.md index deed275..08b7287 100644 --- a/src/msca/6900/v0.8/modules/multisig/README.md +++ b/src/msca/6900/v0.8/modules/multisig/README.md @@ -4,7 +4,7 @@ Weighted Multisig Module is an ERC6900-compatible weighted multisig ownership mo ## Core Functionalities -Weighted Multisig Module is a module that provides validation functions for a weighted multisig ownership scheme. **Multisig validation only works in the user operation context.** +Weighted Multisig Module is an module that provides validation functions for a weighted multisig ownership scheme. **Multisig validation only works in the user operation context.** Its core features include: 1. Weighted multisig user operation validation on native account functions (`installModule`, `uninstallModule`, `execute`, `executeBatch`, `upgradeToAndCall`). diff --git a/src/msca/6900/v0.8/modules/multisig/WeightedWebauthnMultisigModule.sol b/src/msca/6900/v0.8/modules/multisig/WeightedWebauthnMultisigModule.sol deleted file mode 100644 index 4bc785a..0000000 --- a/src/msca/6900/v0.8/modules/multisig/WeightedWebauthnMultisigModule.sol +++ /dev/null @@ -1,375 +0,0 @@ -/* - * Copyright 2024 Circle Internet Group, Inc. All rights reserved. - - * SPDX-License-Identifier: GPL-3.0-or-later - - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -pragma solidity 0.8.24; - -import {EIP1271_INVALID_SIGNATURE, EIP1271_VALID_SIGNATURE} from "../../../../../common/Constants.sol"; -import {BaseWeightedMultisigModule} from "./BaseWeightedMultisigModule.sol"; -import {IWeightedMultisigModule} from "./IWeightedMultisigModule.sol"; - -import { - AssociatedLinkedListSet, - AssociatedLinkedListSetLib -} from "@modular-account-libs/libraries/AssociatedLinkedListSetLib.sol"; -import {SetValue} from "@modular-account-libs/libraries/Constants.sol"; -import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; - -import {OwnerData, PublicKey, WebAuthnSigDynamicPart} from "../../../../../common/CommonStructs.sol"; -import {AddressBytesLib} from "../../../../../libs/AddressBytesLib.sol"; -import {PublicKeyLib} from "../../../../../libs/PublicKeyLib.sol"; -import {WebAuthnLib} from "../../../../../libs/WebAuthnLib.sol"; - -import {NotImplementedFunction} from "../../../shared/common/Errors.sol"; -import {BaseERC712CompliantModule} from "../../../shared/erc712/BaseERC712CompliantModule.sol"; - -import {IModule} from "../../interfaces/IModule.sol"; -import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; - -/// @title Weighted Multisig Module That Supports Additional Webauthn Authentication -/// @author Circle -/// @notice This module adds a weighted threshold ownership scheme to a ERC6900 smart contract account. -contract WeightedWebauthnMultisigModule is BaseWeightedMultisigModule, BaseERC712CompliantModule { - using ECDSA for bytes32; - using AssociatedLinkedListSetLib for AssociatedLinkedListSet; - using PublicKeyLib for PublicKey[]; - using PublicKeyLib for PublicKey; - using AddressBytesLib for address; - - // a unique identifier in the format "vendor.module.semver" for the account implementation - string public constant MODULE_ID = "circle.weighted-webauthn-multisig-module.2.0.0"; - bytes32 private constant _HASHED_MODULE_ID = keccak256(bytes(MODULE_ID)); - bytes32 private constant _MULTISIG_MODULE_TYPEHASH = - keccak256("CircleWeightedWebauthnMultisigMessage(bytes message)"); - - constructor(address entryPoint) BaseWeightedMultisigModule(entryPoint) {} - - // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Execution functions ┃ - // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - /// @inheritdoc IWeightedMultisigModule - function addOwners( - address[] calldata ownersToAdd, - uint256[] calldata weightsToAdd, - PublicKey[] calldata publicKeyOwnersToAdd, - uint256[] calldata pubicKeyWeightsToAdd, - uint256 newThresholdWeight - ) external override isInitialized(msg.sender) { - (bytes30[] memory _totalOwners, OwnerData[] memory _ownersData) = - _mergeOwnersData(ownersToAdd, weightsToAdd, publicKeyOwnersToAdd, pubicKeyWeightsToAdd); - _addOwnersAndUpdateMultisigMetadata(_totalOwners, _ownersData, newThresholdWeight); - } - - /// @inheritdoc IWeightedMultisigModule - function removeOwners( - address[] calldata ownersToRemove, - PublicKey[] calldata publicKeyOwnersToRemove, - uint256 newThresholdWeight - ) external override isInitialized(msg.sender) { - bytes30[] memory _totalOwners = _mergeOwners(ownersToRemove, publicKeyOwnersToRemove); - _removeOwners(_totalOwners, newThresholdWeight); - } - - /// @inheritdoc IWeightedMultisigModule - function updateMultisigWeights( - address[] calldata ownersToUpdate, - uint256[] calldata newWeightsToUpdate, - PublicKey[] calldata publicKeyOwnersToUpdate, - uint256[] calldata pubicKeyNewWeightsToUpdate, - uint256 newThresholdWeight - ) external override isInitialized(msg.sender) { - (bytes30[] memory _totalOwners, OwnerData[] memory _ownersData) = - _mergeOwnersData(ownersToUpdate, newWeightsToUpdate, publicKeyOwnersToUpdate, pubicKeyNewWeightsToUpdate); - _updateMultisigWeights(_totalOwners, _ownersData, newThresholdWeight); - } - - // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Execution view functions ┃ - // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - - /// @inheritdoc BaseWeightedMultisigModule - function validateSignature(address account, uint32 entityId, address sender, bytes32 hash, bytes memory signature) - external - view - override - returns (bytes4) - { - (sender); - if (entityId == uint32(EntityId.VALIDATION_OWNER)) { - bytes32 wrappedDigest = getReplaySafeMessageHash(account, hash); - (bool success,) = checkNSignatures(wrappedDigest, wrappedDigest, account, signature); - return success ? EIP1271_VALID_SIGNATURE : EIP1271_INVALID_SIGNATURE; - } - revert NotImplementedFunction(msg.sig, entityId); - } - - // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Module interface functions ┃ - // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - - /// @notice Initialize module data for the modular account. - /// @dev Called by the modular account during `installModule`. - /// Constraints: - /// - initialOwners must be non-empty - /// - length of ownerWeights must == length of initialOwners - /// - each weight must be between [1, 1000000], inclusive. - /// each owner in ownersToAdd must not be address(0) or an existing owner. - /// thresholdWeight must be nonzero. - /// @param data bytes array to be decoded and used by the module to setup initial module data for the modular - /// account. Format: - /// address[] memory initialOwners, PublicKey[] memory initialPublicKeyOwners, uint256[] memory ownerWeights, - /// uint256[] memory publicKeyOwnerWeights, uint256 thresholdWeight - /// @dev The owner array cannot have 0 or duplicated addresses. - function onInstall(bytes calldata data) external override isNotInitialized(msg.sender) { - ( - address[] memory initialOwners, - uint256[] memory ownerWeights, - PublicKey[] memory initialPublicKeyOwners, - uint256[] memory publicKeyOwnerWeights, - uint256 thresholdWeight - ) = abi.decode(data, (address[], uint256[], PublicKey[], uint256[], uint256)); - (bytes30[] memory _totalOwners, OwnerData[] memory _ownersData) = - _mergeOwnersData(initialOwners, ownerWeights, initialPublicKeyOwners, publicKeyOwnerWeights); - _onInstall(_totalOwners, _ownersData, thresholdWeight); - } - - /// @inheritdoc IModule - function moduleId() external pure returns (string memory) { - return MODULE_ID; - } - - // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Module only view functions ┃ - // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - /// @inheritdoc BaseERC712CompliantModule - function _getModuleTypeHash() internal pure override returns (bytes32) { - return _MULTISIG_MODULE_TYPEHASH; - } - - /// @inheritdoc BaseERC712CompliantModule - function _getModuleIdHash() internal pure override returns (bytes32) { - return _HASHED_MODULE_ID; - } - - /// @inheritdoc IWeightedMultisigModule - function checkNSignatures(bytes32 actualDigest, bytes32 minimalDigest, address account, bytes memory signatures) - public - view - override - returns (bool success, uint256 firstFailure) - { - if (signatures.length < _INDIVIDUAL_SIGNATURE_BYTES_LEN) { - revert InvalidSigLength(); - } - - uint256 thresholdWeight = _ownerMetadata[account].thresholdWeight; - // (Account is not initialized) - if (thresholdWeight == 0) { - revert InvalidThresholdWeight(); - } - - uint256 accumulatedWeight; - uint256 signatureCount; - bytes30 lastOwner; - bytes30 currentOwner; - // first32Bytes of signature constant part - bytes32 first32Bytes; - // second32Bytes of signature constant part - bytes32 second32Bytes; - // lastByte of signature constant part - uint8 sigType; - // lowestOffset of signature dynamic part, must locate after the signature constant part - // 0 means we only have EOA signer so far - uint256 lowestSigDynamicPartOffset = 0; - // if the digests differ, make sure we have exactly 1 sig on the actual digest - uint256 numSigsOnActualDigest = (actualDigest != minimalDigest) ? 1 : 0; - - // tracks whether `signatures` is a complete and valid multisig signature - success = true; - while (accumulatedWeight < thresholdWeight) { - // Fail if the next 65 bytes would exceed signature length - // or lowest dynamic part signature offset, where next 65 bytes is defined as - // [signatureCount _INDIVIDUAL_SIGNATURE_BYTES_LEN, signatureCount _INDIVIDUAL_SIGNATURE_BYTES_LEN + - // _INDIVIDUAL_SIGNATURE_BYTES_LEN) - // exclusive - uint256 sigConstantPartEndPos = - signatureCount * _INDIVIDUAL_SIGNATURE_BYTES_LEN + _INDIVIDUAL_SIGNATURE_BYTES_LEN; - // do not fail if only have EOA signer so far - if ( - (lowestSigDynamicPartOffset != 0 && sigConstantPartEndPos > lowestSigDynamicPartOffset) - || sigConstantPartEndPos > signatures.length - ) { - if (success) { - return (false, signatureCount); - } else { - return (false, firstFailure); - } - } - - (sigType, first32Bytes, second32Bytes) = _signatureSplit(signatures, signatureCount); - - // sigType >= 32 implies it's signed over the actual digest, so we deduct it according to encoding rule - // if sigType > 60, it will eventually fail the ecdsa recover check below - bytes32 digest; - if (sigType >= 32) { - digest = actualDigest; - sigType -= 32; - // can have unchecked since we check against zero at the end - // underflow would wrap the value to 2 ^ 256 - 1 - unchecked { - // we now have one sig on actual digest - numSigsOnActualDigest -= 1; - } - } else { - digest = minimalDigest; - } - - // sigType == 0 is the contract signature case - if (sigType == 0) { - // first32Bytes contains the address to perform 1271 validation on - address contractAddress = address(uint160(uint256(first32Bytes))); - // make sure upper bits are clean - if (uint256(first32Bytes) > uint256(uint160(contractAddress))) { - revert InvalidAddress(); - } - currentOwner = contractAddress.toBytes30(); - if (ownerDataPerAccount[currentOwner][account].addr != contractAddress) { - if (success) { - firstFailure = signatureCount; - success = false; - } - } - // retrieve contract signature - bytes memory contractSignature; - { - // offset of current signature dynamic part - // second32Bytes is the memory offset containing the signature - uint256 sigDynamicPartOffset = uint256(second32Bytes); - if ( - sigDynamicPartOffset > signatures.length - || sigDynamicPartOffset < _INDIVIDUAL_SIGNATURE_BYTES_LEN - ) { - revert InvalidSigOffset(); - } - // total length of current signature dynamic part - uint256 sigDynamicPartTotalLen; - // 1. load contractSignature content starting from the correct memory offset - // 2. calculate total length including the content and the prefix storing the length - assembly ("memory-safe") { - contractSignature := add(add(signatures, sigDynamicPartOffset), 0x20) - sigDynamicPartTotalLen := add(mload(contractSignature), 0x20) - } - // signature dynamic part should not exceed the total signature length - if (sigDynamicPartOffset + sigDynamicPartTotalLen > signatures.length) { - revert InvalidContractSigLength(); - } - if (sigDynamicPartOffset < lowestSigDynamicPartOffset || lowestSigDynamicPartOffset == 0) { - lowestSigDynamicPartOffset = sigDynamicPartOffset; - } - } - if (!SignatureChecker.isValidERC1271SignatureNow(contractAddress, digest, contractSignature)) { - if (success) { - firstFailure = signatureCount; - success = false; - } - } - } else if (sigType == 2) { - // secp256r1 sig, webauthn and public key data bytes - bytes memory sigDynamicPartBytes; - // first32Bytes stores public key on-chain identifier - currentOwner = bytes30(uint240(uint256(first32Bytes))); - OwnerData memory currentOwnerData = ownerDataPerAccount[currentOwner][account]; - uint256 x = currentOwnerData.publicKeyX; - uint256 y = currentOwnerData.publicKeyY; - // retrieve sig dynamic part bytes - WebAuthnSigDynamicPart memory sigDynamicPart; - { - // second32Bytes is the memory offset containing the sigDynamicPart - uint256 sigDynamicPartOffset = uint256(second32Bytes); - if ( - sigDynamicPartOffset > signatures.length - || sigDynamicPartOffset < _INDIVIDUAL_SIGNATURE_BYTES_LEN - ) { - revert InvalidSigOffset(); - } - uint256 sigDynamicPartTotalLen; - // 1. load the content starting from the correct memory offset - // 2. calculate total length including the content and the prefix storing the length - assembly ("memory-safe") { - sigDynamicPartBytes := add(add(signatures, sigDynamicPartOffset), 0x20) - sigDynamicPartTotalLen := add(mload(sigDynamicPartBytes), 0x20) - } - if (sigDynamicPartOffset + sigDynamicPartTotalLen > signatures.length) { - revert InvalidSigLength(); - } - if (sigDynamicPartOffset < lowestSigDynamicPartOffset || lowestSigDynamicPartOffset == 0) { - lowestSigDynamicPartOffset = sigDynamicPartOffset; - } - sigDynamicPart = abi.decode(sigDynamicPartBytes, (WebAuthnSigDynamicPart)); - } - if ( - !WebAuthnLib.verify({ - challenge: abi.encode(digest), - webAuthnData: sigDynamicPart.webAuthnData, - r: sigDynamicPart.r, - s: sigDynamicPart.s, - x: x, - y: y - }) - ) { - if (success) { - firstFailure = signatureCount; - success = false; - } - } - } else { - // reverts if signature has the wrong s value, wrong v value, or if it's a bad point on the k1 curve - address signer = digest.recover(sigType, first32Bytes, second32Bytes); - currentOwner = signer.toBytes30(); - if (ownerDataPerAccount[currentOwner][account].addr != signer) { - if (success) { - firstFailure = signatureCount; - success = false; - } - } - } - - if ( - // if the signature is out of order or duplicate - // or is not an owner - currentOwner <= lastOwner || !_owners.contains(account, SetValue.wrap(currentOwner)) - ) { - if (success) { - firstFailure = signatureCount; - success = false; - } - } - - accumulatedWeight += ownerDataPerAccount[currentOwner][account].weight; - lastOwner = currentOwner; - signatureCount++; - } - - // if we need a signature on the actual digest, and we didn't get exactly one, revert - // we avoid reverting early to facilitate fee estimation - if (numSigsOnActualDigest != 0) { - revert InvalidNumSigsOnActualDigest(numSigsOnActualDigest); - } - return (success, firstFailure); - } -} diff --git a/src/msca/6900/v0.8/modules/validation/ISingleSignerValidationModule.sol b/src/msca/6900/v0.8/modules/validation/ISingleSignerValidationModule.sol index 8e997e1..94e4964 100644 --- a/src/msca/6900/v0.8/modules/validation/ISingleSignerValidationModule.sol +++ b/src/msca/6900/v0.8/modules/validation/ISingleSignerValidationModule.sol @@ -18,7 +18,7 @@ */ pragma solidity 0.8.24; -import {IValidationModule} from "../../interfaces/IValidationModule.sol"; +import {IValidationModule} from "@erc6900/reference-implementation/interfaces/IValidationModule.sol"; interface ISingleSignerValidationModule is IValidationModule { event SignerTransferred( diff --git a/src/msca/6900/v0.8/modules/validation/SingleSignerValidationModule.sol b/src/msca/6900/v0.8/modules/validation/SingleSignerValidationModule.sol index e8151a2..0ca1940 100644 --- a/src/msca/6900/v0.8/modules/validation/SingleSignerValidationModule.sol +++ b/src/msca/6900/v0.8/modules/validation/SingleSignerValidationModule.sol @@ -25,9 +25,10 @@ import { SIG_VALIDATION_SUCCEEDED } from "../../../../../common/Constants.sol"; import {UnauthorizedCaller} from "../../../shared/common/Errors.sol"; -import {IModule} from "../../interfaces/IModule.sol"; -import {IValidationModule} from "../../interfaces/IValidationModule.sol"; + import {BaseModule} from "../BaseModule.sol"; +import {IModule} from "@erc6900/reference-implementation/interfaces/IModule.sol"; +import {IValidationModule} from "@erc6900/reference-implementation/interfaces/IValidationModule.sol"; import {BaseERC712CompliantModule} from "../../../shared/erc712/BaseERC712CompliantModule.sol"; import {ISingleSignerValidationModule} from "./ISingleSignerValidationModule.sol"; diff --git a/test/SponsorPaymaster.t.sol b/test/SponsorPaymaster.t.sol index 1041e7f..af1ccb0 100644 --- a/test/SponsorPaymaster.t.sol +++ b/test/SponsorPaymaster.t.sol @@ -121,7 +121,7 @@ contract SponsorPaymasterTest is TestUtils { assertEq(actualVerifyingSigners.length, 2); } - function testParsePaymasterAndData_validData() public { + function testParsePaymasterAndData_validData() public view { bytes memory paymasterAndData = abi.encodePacked( address(sponsorPaymaster), MOCK_PAYMASTER_VERIFICATION_GAS_LIMIT, @@ -265,7 +265,6 @@ contract SponsorPaymasterTest is TestUtils { // verifyingSigner2 signature (v, r, s) = vm.sign(verifyingSigner2PrivateKey, paymasterHash); - bytes memory paymasterSig2 = abi.encodePacked(r, s, v); bytes memory actualPaymasterAndData2 = abi.encodePacked( address(sponsorPaymaster), MOCK_PAYMASTER_VERIFICATION_GAS_LIMIT, diff --git a/test/libs/AddressBytesLib.t.sol b/test/libs/AddressBytesLib.t.sol index 814de55..9e6b336 100644 --- a/test/libs/AddressBytesLib.t.sol +++ b/test/libs/AddressBytesLib.t.sol @@ -24,7 +24,7 @@ import {AddressBytesLibCaller} from "./AddressBytesLibCaller.sol"; contract AddressBytesLibTest is TestUtils { AddressBytesLibCaller private addressBytesLibCaller = new AddressBytesLibCaller(); - function testFuzz_toBytes30(address addr) public { + function testFuzz_toBytes30(address addr) public view { assertEq(addressBytesLibCaller.toBytes30(addr), leftPadWithZeros(addr)); } diff --git a/test/libs/PublicKeyLib.t.sol b/test/libs/PublicKeyLib.t.sol index 56f7920..a7b1cac 100644 --- a/test/libs/PublicKeyLib.t.sol +++ b/test/libs/PublicKeyLib.t.sol @@ -25,7 +25,7 @@ import {PublicKeyLibCaller} from "./PublicKeyLibCaller.sol"; contract PublicKeyLibTest is TestUtils { PublicKeyLibCaller private publicKeyLibCaller = new PublicKeyLibCaller(); - function testFuzz_toBytes30(uint256 rand, uint256 x, uint256 y) public { + function testFuzz_toBytes30(uint256 rand, uint256 x, uint256 y) public view { rand = bound(rand, 0, 1); x = bound(x, 0, UINT256_MAX); y = bound(y, 0, UINT256_MAX); @@ -39,7 +39,7 @@ contract PublicKeyLibTest is TestUtils { assertNotEq(publicKeyLibCaller.toBytes30(x, y), bytes30(0)); } - function testFuzz_toBytes30PubKey(uint256 rand, PublicKey memory pubKey) public { + function testFuzz_toBytes30PubKey(uint256 rand, PublicKey memory pubKey) public view { rand = bound(rand, 0, 1); pubKey.x = bound(pubKey.x, 0, UINT256_MAX); pubKey.y = bound(pubKey.y, 0, UINT256_MAX); diff --git a/test/libs/RecipientAddressLib.t.sol b/test/libs/RecipientAddressLib.t.sol index b4cf361..8bcd432 100644 --- a/test/libs/RecipientAddressLib.t.sol +++ b/test/libs/RecipientAddressLib.t.sol @@ -77,13 +77,13 @@ contract RecipientAddressLibTest is TestUtils { testERC20 = new TestLiquidityPool("20", "$$$"); } - function testFuzz_getERC20TokenRecipientForTransfer(address expectedRecipient, uint256 amount) public { + function testFuzz_getERC20TokenRecipientForTransfer(address expectedRecipient, uint256 amount) public view { bytes memory data = abi.encodeCall(testERC20.transfer, (expectedRecipient, amount)); address recipient = recipientAddressLib.getERC20TokenRecipient(data); assertEq(recipient, expectedRecipient); } - function testFuzz_getERC20TokenRecipientForApprove(address expectedRecipient, uint256 amount) public { + function testFuzz_getERC20TokenRecipientForApprove(address expectedRecipient, uint256 amount) public view { bytes memory data = abi.encodeCall(testERC20.approve, (expectedRecipient, amount)); address recipient = recipientAddressLib.getERC20TokenRecipient(data); assertEq(recipient, expectedRecipient); @@ -91,31 +91,38 @@ contract RecipientAddressLibTest is TestUtils { function testFuzz_getERC20TokenRecipientForTransferFrom(address expectedRecipient, address from, uint256 amount) public + view { bytes memory data = abi.encodeCall(testERC20.transferFrom, (from, expectedRecipient, amount)); address recipient = recipientAddressLib.getERC20TokenRecipient(data); assertEq(recipient, expectedRecipient); } - function testFuzz_getERC20TokenRecipientForIncreaseAllowance(address expectedRecipient, uint256 amount) public { + function testFuzz_getERC20TokenRecipientForIncreaseAllowance(address expectedRecipient, uint256 amount) + public + view + { bytes memory data = abi.encodeWithSelector(ERC20_INCREASE_ALLOWANCE, expectedRecipient, amount); address recipient = recipientAddressLib.getERC20TokenRecipient(data); assertEq(recipient, expectedRecipient); } - function testFuzz_getERC20TokenRecipientForDecreaseAllowance(address expectedRecipient, uint256 amount) public { + function testFuzz_getERC20TokenRecipientForDecreaseAllowance(address expectedRecipient, uint256 amount) + public + view + { bytes memory data = abi.encodeWithSelector(ERC20_DECREASE_ALLOWANCE, expectedRecipient, amount); address recipient = recipientAddressLib.getERC20TokenRecipient(data); assertEq(recipient, expectedRecipient); } - function testFuzz_getERC20TokenRecipientForUnknownSelector(address expectedRecipient, uint256 amount) public { + function testFuzz_getERC20TokenRecipientForUnknownSelector(address expectedRecipient, uint256 amount) public view { bytes memory data = abi.encodeCall(testERC20.mint, (expectedRecipient, amount)); address recipient = recipientAddressLib.getERC20TokenRecipient(data); assertEq(recipient, address(0)); } - function testFuzz_getERC1155TokenRecipientForSafeApproval(address expectedRecipient, bool approved) public { + function testFuzz_getERC1155TokenRecipientForSafeApproval(address expectedRecipient, bool approved) public view { bytes memory data = abi.encodeCall(testERC1155.setApprovalForAll, (expectedRecipient, approved)); address recipient = recipientAddressLib.getERC1155TokenRecipient(data); assertEq(recipient, expectedRecipient); @@ -127,7 +134,7 @@ contract RecipientAddressLibTest is TestUtils { uint256 tokenId, uint256 amount, bytes memory callData - ) public { + ) public view { bytes memory data = abi.encodeCall(testERC1155.safeTransferFrom, (from, expectedRecipient, tokenId, amount, callData)); address recipient = recipientAddressLib.getERC1155TokenRecipient(data); @@ -140,7 +147,7 @@ contract RecipientAddressLibTest is TestUtils { uint256[] memory ids, uint256[] memory amounts, bytes memory callData - ) public { + ) public view { bytes memory data = abi.encodeCall(testERC1155.safeBatchTransferFrom, (from, expectedRecipient, ids, amounts, callData)); address recipient = recipientAddressLib.getERC1155TokenRecipient(data); @@ -152,19 +159,19 @@ contract RecipientAddressLibTest is TestUtils { uint256 tokenId, uint256 amount, bytes memory callData - ) public { + ) public view { bytes memory data = abi.encodeCall(testERC1155.mint, (expectedRecipient, tokenId, amount, callData)); address recipient = recipientAddressLib.getERC1155TokenRecipient(data); assertEq(recipient, address(0)); } - function testFuzz_getERC721TokenRecipientForSafeApproval(address expectedRecipient, bool approved) public { + function testFuzz_getERC721TokenRecipientForSafeApproval(address expectedRecipient, bool approved) public view { bytes memory data = abi.encodeCall(testERC721.setApprovalForAll, (expectedRecipient, approved)); address recipient = recipientAddressLib.getERC721TokenRecipient(data); assertEq(recipient, expectedRecipient); } - function testFuzz_getERC721TokenRecipientForApproval(address expectedRecipient, uint256 tokenId) public { + function testFuzz_getERC721TokenRecipientForApproval(address expectedRecipient, uint256 tokenId) public view { bytes memory data = abi.encodeCall(testERC721.approve, (expectedRecipient, tokenId)); address recipient = recipientAddressLib.getERC721TokenRecipient(data); assertEq(recipient, expectedRecipient); @@ -174,7 +181,7 @@ contract RecipientAddressLibTest is TestUtils { address expectedRecipient, address from, uint256 tokenId - ) public { + ) public view { bytes memory data = abi.encodeWithSignature("safeTransferFrom(address,address,uint256)", from, expectedRecipient, tokenId); address recipient = recipientAddressLib.getERC721TokenRecipient(data); @@ -190,7 +197,7 @@ contract RecipientAddressLibTest is TestUtils { address from, uint256 tokenId, bytes memory callData - ) public { + ) public view { bytes memory data = abi.encodeWithSignature( "safeTransferFrom(address,address,uint256,bytes)", from, expectedRecipient, tokenId, callData ); @@ -198,13 +205,16 @@ contract RecipientAddressLibTest is TestUtils { assertEq(recipient, expectedRecipient); } - function testFuzz_getERC721TokenRecipientForUnknownSelector(address expectedRecipient, uint256 tokenId) public { + function testFuzz_getERC721TokenRecipientForUnknownSelector(address expectedRecipient, uint256 tokenId) + public + view + { bytes memory data = abi.encodeWithSignature("safeMint(address,uint256)", expectedRecipient, tokenId); address recipient = recipientAddressLib.getERC721TokenRecipient(data); assertEq(recipient, address(0)); } - function testFuzz_getERC20TokenRecipientForTransferWithRandomBytes(bytes memory randomBytes) public { + function testFuzz_getERC20TokenRecipientForTransferWithRandomBytes(bytes memory randomBytes) public view { // 4 accounts for function selector vm.assume(randomBytes.length < (RecipientAddressLib.TRANSFER_OR_APPROVE_MIN_LEN - 4) && randomBytes.length > 0); bytes memory data = abi.encodePacked(testERC20.transfer.selector, randomBytes); @@ -212,7 +222,7 @@ contract RecipientAddressLibTest is TestUtils { assertEq(recipient, address(0)); } - function testFuzz_getERC20TokenRecipientForApproveWithRandomBytes(bytes memory randomBytes) public { + function testFuzz_getERC20TokenRecipientForApproveWithRandomBytes(bytes memory randomBytes) public view { // 4 accounts for function selector vm.assume(randomBytes.length < (RecipientAddressLib.TRANSFER_OR_APPROVE_MIN_LEN - 4) && randomBytes.length > 0); bytes memory data = abi.encodePacked(testERC20.approve.selector, randomBytes); @@ -220,7 +230,7 @@ contract RecipientAddressLibTest is TestUtils { assertEq(recipient, address(0)); } - function testFuzz_getERC20TokenRecipientForIncreaseAllowanceWithRandomBytes(bytes memory randomBytes) public { + function testFuzz_getERC20TokenRecipientForIncreaseAllowanceWithRandomBytes(bytes memory randomBytes) public view { // 4 accounts for function selector vm.assume(randomBytes.length < (RecipientAddressLib.TRANSFER_OR_APPROVE_MIN_LEN - 4) && randomBytes.length > 0); bytes memory data = abi.encodePacked(ERC20_INCREASE_ALLOWANCE, randomBytes); @@ -228,7 +238,7 @@ contract RecipientAddressLibTest is TestUtils { assertEq(recipient, address(0)); } - function testFuzz_getERC20TokenRecipientForDecreaseAllowanceWithRandomBytes(bytes memory randomBytes) public { + function testFuzz_getERC20TokenRecipientForDecreaseAllowanceWithRandomBytes(bytes memory randomBytes) public view { // 4 accounts for function selector vm.assume(randomBytes.length < (RecipientAddressLib.TRANSFER_OR_APPROVE_MIN_LEN - 4) && randomBytes.length > 0); bytes memory data = abi.encodePacked(ERC20_DECREASE_ALLOWANCE, randomBytes); @@ -236,7 +246,7 @@ contract RecipientAddressLibTest is TestUtils { assertEq(recipient, address(0)); } - function testFuzz_getERC20TokenRecipientForTransferFromWithRandomBytes(bytes memory randomBytes) public { + function testFuzz_getERC20TokenRecipientForTransferFromWithRandomBytes(bytes memory randomBytes) public view { // 4 accounts for function selector vm.assume(randomBytes.length < (RecipientAddressLib.TRANSFER_FROM_MIN_LEN - 4) && randomBytes.length > 0); bytes memory data = abi.encodePacked(testERC20.transferFrom.selector, randomBytes); @@ -244,7 +254,7 @@ contract RecipientAddressLibTest is TestUtils { assertEq(recipient, address(0)); } - function testFuzz_getERC1155TokenRecipientForSafeApprovalWithRandomBytes(bytes memory randomBytes) public { + function testFuzz_getERC1155TokenRecipientForSafeApprovalWithRandomBytes(bytes memory randomBytes) public view { // 4 accounts for function selector vm.assume(randomBytes.length < (RecipientAddressLib.TRANSFER_OR_APPROVE_MIN_LEN - 4) && randomBytes.length > 0); bytes memory data = abi.encodePacked(testERC1155.setApprovalForAll.selector, randomBytes); @@ -252,7 +262,10 @@ contract RecipientAddressLibTest is TestUtils { assertEq(recipient, address(0)); } - function testFuzz_getERC1155TokenRecipientForSafeTransferFromWithRandomBytes(bytes memory randomBytes) public { + function testFuzz_getERC1155TokenRecipientForSafeTransferFromWithRandomBytes(bytes memory randomBytes) + public + view + { // 4 accounts for function selector vm.assume( randomBytes.length < (RecipientAddressLib.TRANSFER_FROM_WITH_BYTES_MIN_LEN - 4) && randomBytes.length > 0 @@ -264,6 +277,7 @@ contract RecipientAddressLibTest is TestUtils { function testFuzz_getERC1155TokenRecipientForSafeBatchTransferFromWithRandomBytes(bytes memory randomBytes) public + view { // 4 accounts for function selector vm.assume( @@ -275,7 +289,7 @@ contract RecipientAddressLibTest is TestUtils { assertEq(recipient, address(0)); } - function testFuzz_getERC721TokenRecipientForSafeApprovalWithRandomBytes(bytes memory randomBytes) public { + function testFuzz_getERC721TokenRecipientForSafeApprovalWithRandomBytes(bytes memory randomBytes) public view { // 4 accounts for function selector vm.assume(randomBytes.length < (RecipientAddressLib.TRANSFER_OR_APPROVE_MIN_LEN - 4) && randomBytes.length > 0); bytes memory data = abi.encodePacked(testERC721.setApprovalForAll.selector, randomBytes); @@ -283,7 +297,7 @@ contract RecipientAddressLibTest is TestUtils { assertEq(recipient, address(0)); } - function testFuzz_getERC721TokenRecipientForApprovalWithRandomBytes(bytes memory randomBytes) public { + function testFuzz_getERC721TokenRecipientForApprovalWithRandomBytes(bytes memory randomBytes) public view { // 4 accounts for function selector vm.assume(randomBytes.length < (RecipientAddressLib.TRANSFER_OR_APPROVE_MIN_LEN - 4) && randomBytes.length > 0); bytes memory data = abi.encodePacked(testERC721.approve.selector, randomBytes); @@ -291,7 +305,10 @@ contract RecipientAddressLibTest is TestUtils { assertEq(recipient, address(0)); } - function testFuzz_getERC721TokenRecipientForSafeTransferFrom1WithRandomBytes(bytes memory randomBytes) public { + function testFuzz_getERC721TokenRecipientForSafeTransferFrom1WithRandomBytes(bytes memory randomBytes) + public + view + { // 4 accounts for function selector vm.assume(randomBytes.length < (RecipientAddressLib.TRANSFER_FROM_MIN_LEN - 4) && randomBytes.length > 0); bytes memory data = @@ -304,7 +321,10 @@ contract RecipientAddressLibTest is TestUtils { assertEq(recipient, address(0)); } - function testFuzz_getERC721TokenRecipientForSafeTransferFrom2WithRandomBytes(bytes memory randomBytes) public { + function testFuzz_getERC721TokenRecipientForSafeTransferFrom2WithRandomBytes(bytes memory randomBytes) + public + view + { // 4 accounts for function selector vm.assume( randomBytes.length < (RecipientAddressLib.TRANSFER_FROM_WITHOUT_AMOUNT_WITH_BYTES_MIN_LEN - 4) @@ -316,15 +336,10 @@ contract RecipientAddressLibTest is TestUtils { assertEq(recipient, address(0)); } - function testMinLengthsForERC20() public { + function testMinLengthsForERC20() public view { address expectedRecipient = vm.addr(123); uint256 amount = 1; address from = vm.addr(456); - uint256 tokenId = 0; - uint256[] memory ids; - uint256[] memory amounts; - bytes memory callData; - bool approved; bytes memory data = abi.encodeCall(testERC20.transfer, (expectedRecipient, amount)); console.log("ERC20.transfer MIN LEN", data.length); @@ -339,14 +354,10 @@ contract RecipientAddressLibTest is TestUtils { assertEq(data.length, RecipientAddressLib.TRANSFER_FROM_MIN_LEN); } - function testMinLengthsForERC721() public { + function testMinLengthsForERC721() public view { address expectedRecipient = vm.addr(123); - uint256 amount = 1; address from = vm.addr(456); uint256 tokenId = 0; - uint256[] memory ids; - uint256[] memory amounts; - bytes memory callData; bool approved; bytes memory data = abi.encodeCall(testERC721.setApprovalForAll, (expectedRecipient, approved)); @@ -362,7 +373,7 @@ contract RecipientAddressLibTest is TestUtils { assertEq(data.length, RecipientAddressLib.TRANSFER_FROM_MIN_LEN); } - function testMinLengthsForERC1155() public { + function testMinLengthsForERC1155() public view { address expectedRecipient = vm.addr(123); uint256 amount = 1; address from = vm.addr(456); @@ -385,15 +396,11 @@ contract RecipientAddressLibTest is TestUtils { assertEq(data.length, RecipientAddressLib.BATCH_TRANSFER_FROM_WITH_BYTES_MIN_LEN); } - function testMinLengthsForSafeTransferFrom() public { + function testMinLengthsForSafeTransferFrom() public pure { address expectedRecipient = vm.addr(123); - uint256 amount = 1; address from = vm.addr(456); uint256 tokenId = 0; - uint256[] memory ids; - uint256[] memory amounts; bytes memory callData; - bool approved; bytes memory data = abi.encodeWithSignature("safeTransferFrom(address,address,uint256)", from, expectedRecipient, tokenId); diff --git a/test/libs/SetValueLib.t.sol b/test/libs/SetValueLib.t.sol index 39c04dd..87817cd 100644 --- a/test/libs/SetValueLib.t.sol +++ b/test/libs/SetValueLib.t.sol @@ -25,7 +25,7 @@ import {SetValue} from "@modular-account-libs/libraries/Constants.sol"; contract SetValueLibTest is TestUtils { SetValueLibCaller private setValueLibCaller = new SetValueLibCaller(); - function testFuzz_toBytes30Array(SetValue[] memory values) public { + function testFuzz_toBytes30Array(SetValue[] memory values) public view { bytes30[] memory res = setValueLibCaller.toBytes30Array(values); uint256 len = res.length; for (uint256 i = 0; i < len; ++i) { diff --git a/test/libs/webauthn/WebAuthnLib.t.sol b/test/libs/webauthn/WebAuthnLib.t.sol index 5663cd0..2dddf57 100644 --- a/test/libs/webauthn/WebAuthnLib.t.sol +++ b/test/libs/webauthn/WebAuthnLib.t.sol @@ -27,7 +27,7 @@ import {Base64Url} from "@fcl/utils/Base64Url.sol"; contract WebAuthnTest is TestUtils { bytes private challenge = abi.encode(0xf631058a3ba1116acce12396fad0a125b5041c43f8e15723709f81aa8d5f4ccf); - function testSafari() public { + function testSafari() public view { uint256 x = 28573233055232466711029625910063034642429572463461595413086259353299906450061; uint256 y = 39367742072897599771788408398752356480431855827262528811857788332151452825281; WebAuthnData memory auth = WebAuthnData({ @@ -48,7 +48,7 @@ contract WebAuthnTest is TestUtils { assertTrue(WebAuthnLib.verify(challenge, auth, r, s, x, y)); } - function testChrome() public { + function testChrome() public view { uint256 x = 28573233055232466711029625910063034642429572463461595413086259353299906450061; uint256 y = 39367742072897599771788408398752356480431855827262528811857788332151452825281; WebAuthnData memory auth = WebAuthnData({ diff --git a/test/libs/webauthn/WebAuthnLibFuzz.t.sol b/test/libs/webauthn/WebAuthnLibFuzz.t.sol index 3751161..4bd3ae1 100644 --- a/test/libs/webauthn/WebAuthnLibFuzz.t.sol +++ b/test/libs/webauthn/WebAuthnLibFuzz.t.sol @@ -33,7 +33,7 @@ contract WebAuthnFuzzTest is TestUtils { string internal constant TEST_FILE = "/test/fixtures/assertions_fixture.json"; /// @dev `WebAuthn.verify` should return `false` when `s` is above P256_N_DIV_2. - function testVerify_ShouldReturnFalse_WhenSAboveP256_N_DIV_2() public { + function testVerify_ShouldReturnFalse_WhenSAboveP256_N_DIV_2() public view { string memory rootPath = vm.projectRoot(); string memory path = string.concat(rootPath, TEST_FILE); string memory json = vm.readFile(path); @@ -69,7 +69,7 @@ contract WebAuthnFuzzTest is TestUtils { } /// @dev `WebAuthn.verify` should return `false` when the `up` flag is not set. - function testVerify_ShouldReturnFalse_WhenTheUpFlagIsNotSet() public { + function testVerify_ShouldReturnFalse_WhenTheUpFlagIsNotSet() public view { string memory rootPath = vm.projectRoot(); string memory path = string.concat(rootPath, TEST_FILE); string memory json = vm.readFile(path); @@ -106,7 +106,7 @@ contract WebAuthnFuzzTest is TestUtils { /// @dev `WebAuthn.verify` should return `false` when `requireUV` is `true` but the /// authenticator did not set the `uv` flag. - function testVerify_ShouldReturnFalse_WhenUserVerificationIsRequiredButTestWasNotPerformed() public { + function testVerify_ShouldReturnFalse_WhenUserVerificationIsRequiredButTestWasNotPerformed() public view { string memory rootPath = vm.projectRoot(); string memory path = string.concat(rootPath, TEST_FILE); string memory json = vm.readFile(path); @@ -146,7 +146,7 @@ contract WebAuthnFuzzTest is TestUtils { /// @dev `WebAuthn.verify` should return `true` when `s` is below `P256_N_DIV_2` and `requireUserVerification` /// "matches" with the `uv` flag set by the authenticator. - function testVerify_ShouldReturnTrue_WhenSBelowP256_N_DIV_2() public { + function testVerify_ShouldReturnTrue_WhenSBelowP256_N_DIV_2() public view { string memory rootPath = vm.projectRoot(); string memory path = string.concat(rootPath, TEST_FILE); string memory json = vm.readFile(path); diff --git a/test/msca/6900/shared/libs/Bytes4DLLLib.t.sol b/test/msca/6900/shared/libs/Bytes4DLLLib.t.sol index 5da7524..577f0a5 100644 --- a/test/msca/6900/shared/libs/Bytes4DLLLib.t.sol +++ b/test/msca/6900/shared/libs/Bytes4DLLLib.t.sol @@ -42,7 +42,7 @@ contract TestDLL { return bytes4DLL.size(); } - function contains(bytes4 value) external returns (bool) { + function contains(bytes4 value) external view returns (bool) { return bytes4DLL.contains(value); } diff --git a/test/msca/6900/v0.7/HookFunctionReferenceDLL.t.sol b/test/msca/6900/v0.7/HookFunctionReferenceDLL.t.sol index 3726be6..57f4c59 100644 --- a/test/msca/6900/v0.7/HookFunctionReferenceDLL.t.sol +++ b/test/msca/6900/v0.7/HookFunctionReferenceDLL.t.sol @@ -142,6 +142,7 @@ contract HookFunctionReferenceDLLTest is TestUtils { function bulkGetPreValidationHooks(TestRepeatableFunctionReferenceDLL dll, uint256 totalHooks, uint256 limit) private + view { FunctionReference[] memory results = new FunctionReference[](totalHooks); FunctionReference memory start = SENTINEL_BYTES21.unpack(); diff --git a/test/msca/6900/v0.7/RepeatableFunctionReferenceDLLLib.t.sol b/test/msca/6900/v0.7/RepeatableFunctionReferenceDLLLib.t.sol index 2cf340e..5abf18d 100644 --- a/test/msca/6900/v0.7/RepeatableFunctionReferenceDLLLib.t.sol +++ b/test/msca/6900/v0.7/RepeatableFunctionReferenceDLLLib.t.sol @@ -149,6 +149,7 @@ contract RepeatableFunctionReferenceDLLibTest is TestUtils { function bulkGetPreValidationHooks(TestRepeatableFunctionReferenceDLL dll, uint256 totalHooks, uint256 limit) private + view { FunctionReference[] memory results = new FunctionReference[](totalHooks); FunctionReference memory start = SENTINEL_BYTES21.unpack(); diff --git a/test/msca/6900/v0.7/SingleOwnerMSCA.t.sol b/test/msca/6900/v0.7/SingleOwnerMSCA.t.sol index 681cf49..64ec799 100644 --- a/test/msca/6900/v0.7/SingleOwnerMSCA.t.sol +++ b/test/msca/6900/v0.7/SingleOwnerMSCA.t.sol @@ -34,8 +34,6 @@ import "../../../util/TestUtils.sol"; import {TestTokenPlugin} from "./TestTokenPlugin.sol"; -import {DefaultTokenCallbackPlugin} from - "../../../../src/msca/6900/v0.7/plugins/v1_0_0/utility/DefaultTokenCallbackPlugin.sol"; import {TestUserOpAllPassValidator} from "./TestUserOpAllPassValidator.sol"; import "./TestUserOpValidator.sol"; import "./TestUserOpValidatorHook.sol"; @@ -299,7 +297,6 @@ contract SingleOwnerMSCATest is TestUtils { // install singleOwnerPlugin before renounceNativeOwner address ownerInPlugin = makeAddr("testRenounceOwnershipWithRuntimeValidation_ownerInPlugin"); - SingleOwnerPlugin singleOwnerPlugin = new SingleOwnerPlugin(); vm.startPrank(sendingOwnerAddr); bytes32 manifest = keccak256(abi.encode(singleOwnerPlugin.pluginManifest())); sender.installPlugin( @@ -436,7 +433,6 @@ contract SingleOwnerMSCATest is TestUtils { SingleOwnerMSCA sender = factory.createAccount(sendingOwnerAddr, salt, initializingData); assertEq(sender.getInstalledPlugins().length, 0); vm.deal(address(sender), 1 ether); - SingleOwnerPlugin singleOwnerPlugin = new SingleOwnerPlugin(); // call from owner vm.startPrank(sendingOwnerAddr); bytes32 manifest = keccak256(abi.encode(singleOwnerPlugin.pluginManifest())); @@ -473,7 +469,6 @@ contract SingleOwnerMSCATest is TestUtils { SingleOwnerMSCA sender = factory.createAccount(sendingOwnerAddr, salt, initializingData); assertEq(sender.getInstalledPlugins().length, 0); vm.deal(address(sender), 1 ether); - SingleOwnerPlugin singleOwnerPlugin = new SingleOwnerPlugin(); // call from owner vm.startPrank(sendingOwnerAddr); bytes32 manifest = keccak256(abi.encode(singleOwnerPlugin.pluginManifest())); @@ -507,22 +502,17 @@ contract SingleOwnerMSCATest is TestUtils { bytes memory initializingData = abi.encode(sendingOwnerAddr); SingleOwnerMSCA sender = factory.createAccount(sendingOwnerAddr, salt, initializingData); vm.deal(address(sender), 1 ether); - DefaultTokenCallbackPlugin defaultTokenCallbackPlugin = new DefaultTokenCallbackPlugin(); // call from owner vm.startPrank(sendingOwnerAddr); - bytes32 manifest = keccak256(abi.encode(defaultTokenCallbackPlugin.pluginManifest())); - FunctionReference[] memory emptyFR = new FunctionReference[](0); - sender.installPlugin(address(defaultTokenCallbackPlugin), manifest, "", emptyFR); - ExecutionFunctionConfig memory executionFunctionConfig = sender.getExecutionFunctionConfig(IERC721Receiver.onERC721Received.selector); - assertEq(executionFunctionConfig.plugin, address(defaultTokenCallbackPlugin)); + assertEq(executionFunctionConfig.plugin, address(sender)); vm.stopPrank(); // okay to use a random address to view vm.startPrank(vm.addr(123)); executionFunctionConfig = sender.getExecutionFunctionConfig(IERC721Receiver.onERC721Received.selector); - assertEq(executionFunctionConfig.plugin, address(defaultTokenCallbackPlugin)); + assertEq(executionFunctionConfig.plugin, address(sender)); vm.stopPrank(); } @@ -722,9 +712,7 @@ contract SingleOwnerMSCATest is TestUtils { testLiquidityPool.mint(senderAddr, 2000000); address recipient = address(0x9005Be081B8EC2A31258878409E88675Cd791376); // execute ERC20 token contract directly - address liquidityPoolSpenderAddr = address(testLiquidityPool); bytes memory tokenTransferCallData = abi.encodeCall(testLiquidityPool.transfer, (recipient, 1000000)); - address factoryAddr = address(factory); PackedUserOperation memory userOp = buildPartialUserOp( senderAddr, acctNonce, @@ -790,8 +778,7 @@ contract SingleOwnerMSCATest is TestUtils { (address nativeOwnerAddr, uint256 nativeOwnerPrivateKey) = makeAddrAndKey("testTransferWithEmptyValidation_native"); address semiMSCA = createSemiAccount(nativeOwnerAddr, nativeOwnerPrivateKey); - (address ownerInPluginAddr, uint256 ownerInPluginPrivateKey) = - makeAddrAndKey("testTransferWithEmptyValidation_plugin"); + (address ownerInPluginAddr,) = makeAddrAndKey("testTransferWithEmptyValidation_plugin"); installSingleOwnerPlugin(semiMSCA, nativeOwnerPrivateKey, ownerInPluginAddr); // renounce native ownership using native owner private key renounceNativeOwner(semiMSCA, nativeOwnerPrivateKey); diff --git a/test/msca/6900/v0.7/SingleOwnerPlugin.t.sol b/test/msca/6900/v0.7/SingleOwnerPlugin.t.sol index 386f194..28e9d8a 100644 --- a/test/msca/6900/v0.7/SingleOwnerPlugin.t.sol +++ b/test/msca/6900/v0.7/SingleOwnerPlugin.t.sol @@ -130,7 +130,7 @@ contract SingleOwnerPluginTest is TestUtils { } /// SingleOwnerPlugin is installed in setUp function, this test is just verifying details - function testSingleOwnerPluginDetailsInstalledDuringAccountDeployment() public { + function testSingleOwnerPluginDetailsInstalledDuringAccountDeployment() public view { address sender = address(msca1); // deployment was done in setUp assertTrue(sender.code.length != 0); diff --git a/test/msca/6900/v0.7/TestPermitAnyExternalAddressPlugin.sol b/test/msca/6900/v0.7/TestPermitAnyExternalAddressPlugin.sol index 783d587..00c5ed5 100644 --- a/test/msca/6900/v0.7/TestPermitAnyExternalAddressPlugin.sol +++ b/test/msca/6900/v0.7/TestPermitAnyExternalAddressPlugin.sol @@ -72,12 +72,12 @@ contract TestPermitAnyExternalAddressPlugin is BasePlugin { } /// @inheritdoc BasePlugin - function onInstall(bytes calldata data) external override { + function onInstall(bytes calldata data) external pure override { (data); } /// @inheritdoc BasePlugin - function onUninstall(bytes calldata data) external override { + function onUninstall(bytes calldata data) external pure override { (data); } @@ -88,6 +88,7 @@ contract TestPermitAnyExternalAddressPlugin is BasePlugin { override returns (uint256 validationData) { + (userOp, userOpHash); if (functionId == uint8(FunctionId.PRE_USER_OP_VALIDATION_HOOK_PASS)) { return SIG_VALIDATION_SUCCEEDED; } @@ -101,6 +102,7 @@ contract TestPermitAnyExternalAddressPlugin is BasePlugin { override returns (uint256 validationData) { + (userOp, userOpHash); if (functionId == uint8(FunctionId.USER_OP_VALIDATION)) { return SIG_VALIDATION_SUCCEEDED; } @@ -113,6 +115,7 @@ contract TestPermitAnyExternalAddressPlugin is BasePlugin { pure override { + (sender, value, data); if (functionId == uint8(FunctionId.PRE_RUNTIME_VALIDATION_HOOK_PASS)) { return; } @@ -125,6 +128,7 @@ contract TestPermitAnyExternalAddressPlugin is BasePlugin { pure override { + (sender, value, data); if (functionId == uint8(FunctionId.RUNTIME_VALIDATION)) { return; } diff --git a/test/msca/6900/v0.7/TestPermitAnyExternalAddressWithPostHookOnlyPlugin.sol b/test/msca/6900/v0.7/TestPermitAnyExternalAddressWithPostHookOnlyPlugin.sol index 3e95b46..d71bf0d 100644 --- a/test/msca/6900/v0.7/TestPermitAnyExternalAddressWithPostHookOnlyPlugin.sol +++ b/test/msca/6900/v0.7/TestPermitAnyExternalAddressWithPostHookOnlyPlugin.sol @@ -61,12 +61,12 @@ contract TestPermitAnyExternalAddressWithPostHookOnlyPlugin is BasePlugin { } /// @inheritdoc BasePlugin - function onInstall(bytes calldata data) external override { + function onInstall(bytes calldata data) external pure override { (data); } /// @inheritdoc BasePlugin - function onUninstall(bytes calldata data) external override { + function onUninstall(bytes calldata data) external pure override { (data); } @@ -77,6 +77,7 @@ contract TestPermitAnyExternalAddressWithPostHookOnlyPlugin is BasePlugin { override returns (uint256 validationData) { + (userOp, userOpHash); if (functionId == uint8(FunctionId.USER_OP_VALIDATION)) { return SIG_VALIDATION_SUCCEEDED; } @@ -89,6 +90,7 @@ contract TestPermitAnyExternalAddressWithPostHookOnlyPlugin is BasePlugin { pure override { + (sender, value, data); if (functionId == uint8(FunctionId.RUNTIME_VALIDATION)) { return; } @@ -96,7 +98,7 @@ contract TestPermitAnyExternalAddressWithPostHookOnlyPlugin is BasePlugin { } /// @inheritdoc BasePlugin - function postExecutionHook(uint8 functionId, bytes calldata preExecHookData) external view override { + function postExecutionHook(uint8 functionId, bytes calldata preExecHookData) external pure override { console.logString("postExecutionHook data:"); console.logBytes(preExecHookData); require(preExecHookData.length == 0, "postOnlyHook should not have data"); diff --git a/test/msca/6900/v0.7/TestPermitAnyExternalAddressWithPreHookOnlyPlugin.sol b/test/msca/6900/v0.7/TestPermitAnyExternalAddressWithPreHookOnlyPlugin.sol index e39f1c6..689bc6d 100644 --- a/test/msca/6900/v0.7/TestPermitAnyExternalAddressWithPreHookOnlyPlugin.sol +++ b/test/msca/6900/v0.7/TestPermitAnyExternalAddressWithPreHookOnlyPlugin.sol @@ -63,12 +63,12 @@ contract TestPermitAnyExternalAddressWithPreHookOnlyPlugin is BasePlugin { } /// @inheritdoc BasePlugin - function onInstall(bytes calldata data) external override { + function onInstall(bytes calldata data) external pure override { (data); } /// @inheritdoc BasePlugin - function onUninstall(bytes calldata data) external override { + function onUninstall(bytes calldata data) external pure override { (data); } @@ -79,6 +79,7 @@ contract TestPermitAnyExternalAddressWithPreHookOnlyPlugin is BasePlugin { override returns (uint256 validationData) { + (userOp, userOpHash); if (functionId == uint8(FunctionId.PRE_USER_OP_VALIDATION_HOOK_PASS)) { return SIG_VALIDATION_SUCCEEDED; } @@ -92,6 +93,7 @@ contract TestPermitAnyExternalAddressWithPreHookOnlyPlugin is BasePlugin { override returns (uint256 validationData) { + (userOp, userOpHash); if (functionId == uint8(FunctionId.USER_OP_VALIDATION)) { return SIG_VALIDATION_SUCCEEDED; } @@ -127,7 +129,7 @@ contract TestPermitAnyExternalAddressWithPreHookOnlyPlugin is BasePlugin { /// @inheritdoc BasePlugin function preExecutionHook(uint8 functionId, address sender, uint256 value, bytes calldata data) external - view + pure override returns (bytes memory context) { diff --git a/test/msca/6900/v0.7/TestTokenPlugin.sol b/test/msca/6900/v0.7/TestTokenPlugin.sol index 043545f..cdea1e7 100644 --- a/test/msca/6900/v0.7/TestTokenPlugin.sol +++ b/test/msca/6900/v0.7/TestTokenPlugin.sol @@ -151,6 +151,7 @@ contract TestTokenPlugin is BasePlugin { override returns (uint256 validationData) { + (userOp, userOpHash); if (functionId == uint8(FunctionId.PRE_USER_OP_VALIDATION_HOOK_PASS1)) { return SIG_VALIDATION_SUCCEEDED; } else if (functionId == uint8(FunctionId.PRE_USER_OP_VALIDATION_HOOK_PASS2)) { @@ -166,6 +167,7 @@ contract TestTokenPlugin is BasePlugin { override returns (uint256 validationData) { + (userOp, userOpHash); if (functionId == uint8(FunctionId.USER_OP_VALIDATION)) { return SIG_VALIDATION_SUCCEEDED; } @@ -178,6 +180,7 @@ contract TestTokenPlugin is BasePlugin { pure override { + (sender, value, data); if (functionId == uint8(FunctionId.PRE_RUNTIME_VALIDATION_HOOK_PASS1)) { return; } else if (functionId == uint8(FunctionId.PRE_RUNTIME_VALIDATION_HOOK_PASS2)) { @@ -192,6 +195,7 @@ contract TestTokenPlugin is BasePlugin { pure override { + (sender, value, data); if (functionId == uint8(FunctionId.RUNTIME_VALIDATION)) { return; } diff --git a/test/msca/6900/v0.7/TestTokenWithPostHookOnlyPlugin.sol b/test/msca/6900/v0.7/TestTokenWithPostHookOnlyPlugin.sol index 284475e..18ae0de 100644 --- a/test/msca/6900/v0.7/TestTokenWithPostHookOnlyPlugin.sol +++ b/test/msca/6900/v0.7/TestTokenWithPostHookOnlyPlugin.sol @@ -139,6 +139,7 @@ contract TestTokenWithPostHookOnlyPlugin is BasePlugin { override returns (uint256 validationData) { + (userOp, userOpHash); if (functionId == uint8(FunctionId.USER_OP_VALIDATION)) { return SIG_VALIDATION_SUCCEEDED; } @@ -151,6 +152,7 @@ contract TestTokenWithPostHookOnlyPlugin is BasePlugin { pure override { + (sender, value, data); if (functionId == uint8(FunctionId.RUNTIME_VALIDATION)) { return; } @@ -158,7 +160,7 @@ contract TestTokenWithPostHookOnlyPlugin is BasePlugin { } /// @inheritdoc BasePlugin - function postExecutionHook(uint8 functionId, bytes calldata preExecHookData) external view override { + function postExecutionHook(uint8 functionId, bytes calldata preExecHookData) external pure override { console.logString("postExecutionHook data:"); console.logBytes(preExecHookData); require(preExecHookData.length == 0, "postOnlyHook should not have data"); diff --git a/test/msca/6900/v0.7/TestTokenWithPreHookOnlyPlugin.sol b/test/msca/6900/v0.7/TestTokenWithPreHookOnlyPlugin.sol index 49ae49e..f9c3072 100644 --- a/test/msca/6900/v0.7/TestTokenWithPreHookOnlyPlugin.sol +++ b/test/msca/6900/v0.7/TestTokenWithPreHookOnlyPlugin.sol @@ -143,6 +143,7 @@ contract TestTokenWithPreHookOnlyPlugin is BasePlugin { override returns (uint256 validationData) { + (userOp, userOpHash); if (functionId == uint8(FunctionId.PRE_USER_OP_VALIDATION_HOOK_PASS1)) { return SIG_VALIDATION_SUCCEEDED; } else if (functionId == uint8(FunctionId.PRE_USER_OP_VALIDATION_HOOK_PASS2)) { @@ -158,6 +159,7 @@ contract TestTokenWithPreHookOnlyPlugin is BasePlugin { override returns (uint256 validationData) { + (userOp, userOpHash); if (functionId == uint8(FunctionId.USER_OP_VALIDATION)) { return SIG_VALIDATION_SUCCEEDED; } @@ -170,6 +172,7 @@ contract TestTokenWithPreHookOnlyPlugin is BasePlugin { pure override { + (sender, value, data); if (functionId == uint8(FunctionId.PRE_RUNTIME_VALIDATION_HOOK_PASS1)) { return; } else if (functionId == uint8(FunctionId.PRE_RUNTIME_VALIDATION_HOOK_PASS2)) { @@ -184,6 +187,7 @@ contract TestTokenWithPreHookOnlyPlugin is BasePlugin { pure override { + (sender, value, data); if (functionId == uint8(FunctionId.RUNTIME_VALIDATION)) { return; } @@ -193,10 +197,11 @@ contract TestTokenWithPreHookOnlyPlugin is BasePlugin { /// @inheritdoc BasePlugin function preExecutionHook(uint8 functionId, address sender, uint256 value, bytes calldata data) external - view + pure override returns (bytes memory context) { + (value); console.logString("preExecutionHook data:"); console.logBytes(data); if (functionId == uint8(FunctionId.PRE_EXECUTION_HOOK)) { diff --git a/test/msca/6900/v0.7/TestUserOpAllPassValidator.sol b/test/msca/6900/v0.7/TestUserOpAllPassValidator.sol index 0b35504..8472404 100644 --- a/test/msca/6900/v0.7/TestUserOpAllPassValidator.sol +++ b/test/msca/6900/v0.7/TestUserOpAllPassValidator.sol @@ -31,11 +31,11 @@ contract TestUserOpAllPassValidator is BasePlugin { expectedValidationData = expectToPass; } - function onInstall(bytes calldata data) external override { + function onInstall(bytes calldata data) external pure override { (data); } - function onUninstall(bytes calldata data) external override { + function onUninstall(bytes calldata data) external pure override { (data); } @@ -53,10 +53,10 @@ contract TestUserOpAllPassValidator is BasePlugin { /// @inheritdoc BasePlugin function runtimeValidationFunction(uint8 functionId, address sender, uint256 value, bytes calldata data) external - view + pure override { - (value, data); + (functionId, sender, value, data); return; } diff --git a/test/msca/6900/v0.7/UpgradableMSCA.t.sol b/test/msca/6900/v0.7/UpgradableMSCA.t.sol index 1821766..c995b8f 100644 --- a/test/msca/6900/v0.7/UpgradableMSCA.t.sol +++ b/test/msca/6900/v0.7/UpgradableMSCA.t.sol @@ -23,7 +23,6 @@ import {EMPTY_FUNCTION_REFERENCE} from "../../../../src/common/Constants.sol"; import {ValidationData} from "../../../../src/msca/6900/shared/common/Structs.sol"; import {UpgradableMSCA} from "../../../../src/msca/6900/v0.7/account/UpgradableMSCA.sol"; -import {InvalidValidationFunctionId} from "../../../../src/msca/6900/shared/common/Errors.sol"; import { PRE_HOOK_ALWAYS_DENY_FUNCTION_REFERENCE, RUNTIME_VALIDATION_ALWAYS_ALLOW_FUNCTION_REFERENCE @@ -47,8 +46,6 @@ import {TestERC721} from "../../../util/TestERC721.sol"; import {TestLiquidityPool} from "../../../util/TestLiquidityPool.sol"; import {TestUtils} from "../../../util/TestUtils.sol"; -import {DefaultTokenCallbackPlugin} from - "../../../../src/msca/6900/v0.7/plugins/v1_0_0/utility/DefaultTokenCallbackPlugin.sol"; import {TestValidatorHook} from "../v0.7/TestUserOpValidatorHook.sol"; import {TestCircleMSCA} from "./TestCircleMSCA.sol"; import {TestCircleMSCAFactory} from "./TestCircleMSCAFactory.sol"; @@ -96,7 +93,6 @@ contract UpgradableMSCATest is TestUtils { TestCircleMSCAFactory private factory; address private factoryOwner; SingleOwnerPlugin private singleOwnerPlugin; - DefaultTokenCallbackPlugin private defaultTokenCallbackPlugin; function setUp() public { factoryOwner = makeAddr("factoryOwner"); @@ -106,13 +102,10 @@ contract UpgradableMSCATest is TestUtils { testLiquidityPool = new TestLiquidityPool("getrich", "$$$"); factory = new TestCircleMSCAFactory(factoryOwner, entryPoint, pluginManager); singleOwnerPlugin = new SingleOwnerPlugin(); - defaultTokenCallbackPlugin = new DefaultTokenCallbackPlugin(); - address[] memory _plugins = new address[](2); + address[] memory _plugins = new address[](1); _plugins[0] = address(singleOwnerPlugin); - _plugins[1] = address(defaultTokenCallbackPlugin); - bool[] memory _permissions = new bool[](2); + bool[] memory _permissions = new bool[](1); _permissions[0] = true; - _permissions[1] = true; vm.startPrank(factoryOwner); factory.setPlugins(_plugins, _permissions); vm.stopPrank(); @@ -701,7 +694,7 @@ contract UpgradableMSCATest is TestUtils { implMSCA.upgradeToAndCall(v2ImplAddr, ""); } - function testEncodeAndHashPluginManifest() public { + function testEncodeAndHashPluginManifest() public pure { PluginManifest memory manifest; manifest.permitAnyExternalAddress = true; bytes4[] memory dependencyInterfaceIds = new bytes4[](1); @@ -890,10 +883,10 @@ contract UpgradableMSCATest is TestUtils { assertEq(testLiquidityPool.balanceOf(senderAddr), 1000000); } - // should not be able to receive ERC1155 token w/o token callback plugin - function testSendAndReceiveERC1155TokenWithoutDefaultCallbackPlugin() public { + // should be able to receive ERC1155 token with token callback enshrined + function testSendAndReceiveERC1155TokenNatively() public { bytes32 salt = 0x0000000000000000000000000000000000000000000000000000000000000000; - (ownerAddr, eoaPrivateKey) = makeAddrAndKey("testSendAndReceiveERC1155TokenWithoutDefaultCallbackPlugin_sender"); + (ownerAddr, eoaPrivateKey) = makeAddrAndKey("testSendAndReceiveERC1155TokenNatively_sender"); address[] memory plugins = new address[](1); bytes32[] memory manifestHashes = new bytes32[](1); bytes[] memory pluginInstallData = new bytes[](1); @@ -904,15 +897,14 @@ contract UpgradableMSCATest is TestUtils { factory.createAccount(ownerAddr, salt, initializingData); (address senderAddr,) = factory.getAddress(ownerAddr, salt, initializingData); vm.deal(senderAddr, 1 ether); - // InvalidValidationFunctionId - vm.expectRevert(abi.encodeWithSelector(InvalidValidationFunctionId.selector, 0)); testERC1155.mint(senderAddr, 0, 2, ""); + assertEq(testERC1155.balanceOf(senderAddr, 0), 2); } - // should not be able to receive ERC721 token w/o token callback plugin - function testSendAndReceiveERC721TokenWithoutDefaultCallbackPlugin() public { + // should not be able to receive ERC721 token with token callback enshrined + function testSendAndReceiveERC721TokenNatively() public { bytes32 salt = 0x0000000000000000000000000000000000000000000000000000000000000000; - (ownerAddr, eoaPrivateKey) = makeAddrAndKey("testSendAndReceiveERC721TokenWithoutDefaultCallbackPlugin_sender"); + (ownerAddr, eoaPrivateKey) = makeAddrAndKey("testSendAndReceiveERC721TokenNatively_sender"); address[] memory plugins = new address[](1); bytes32[] memory manifestHashes = new bytes32[](1); bytes[] memory pluginInstallData = new bytes[](1); @@ -923,24 +915,20 @@ contract UpgradableMSCATest is TestUtils { factory.createAccount(ownerAddr, salt, initializingData); (address senderAddr,) = factory.getAddress(ownerAddr, salt, initializingData); vm.deal(senderAddr, 1 ether); - // we do the runtime validation now first, so it would not even pass that due to missing of onERC721Received - vm.expectRevert(abi.encodeWithSelector(bytes4(keccak256("InvalidValidationFunctionId(uint8)")), uint8(0))); testERC721.safeMint(senderAddr, 0); + assertEq(testERC721.balanceOf(senderAddr), 1); } - // should be able to send/receive ERC1155 token with token callback plugin - function testSendAndReceiveERC1155TokenWithDefaultCallbackPlugin() public { + // should be able to send/receive ERC1155 token with token callback handler + function testSendAndReceiveERC1155TokenWithDefaultCallbackHandler() public { bytes32 salt = 0x0000000000000000000000000000000000000000000000000000000000000000; - (ownerAddr, eoaPrivateKey) = makeAddrAndKey("testSendAndReceiveERC1155TokenWithDefaultCallbackPlugin_sender"); - address[] memory plugins = new address[](2); - bytes32[] memory manifestHashes = new bytes32[](2); - bytes[] memory pluginInstallData = new bytes[](2); - plugins[0] = address(defaultTokenCallbackPlugin); - manifestHashes[0] = keccak256(abi.encode(defaultTokenCallbackPlugin.pluginManifest())); - pluginInstallData[0] = ""; - plugins[1] = address(singleOwnerPlugin); - manifestHashes[1] = keccak256(abi.encode(singleOwnerPlugin.pluginManifest())); - pluginInstallData[1] = abi.encode(ownerAddr); + (ownerAddr, eoaPrivateKey) = makeAddrAndKey("testSendAndReceiveERC1155TokenWithDefaultCallbackHandler_sender"); + address[] memory plugins = new address[](1); + bytes32[] memory manifestHashes = new bytes32[](1); + bytes[] memory pluginInstallData = new bytes[](1); + plugins[0] = address(singleOwnerPlugin); + manifestHashes[0] = keccak256(abi.encode(singleOwnerPlugin.pluginManifest())); + pluginInstallData[0] = abi.encode(ownerAddr); bytes memory initializingData = abi.encode(plugins, manifestHashes, pluginInstallData); factory.createAccount(ownerAddr, salt, initializingData); (address senderAddr,) = factory.getAddress(ownerAddr, salt, initializingData); @@ -988,19 +976,16 @@ contract UpgradableMSCATest is TestUtils { assertEq(testERC1155.balanceOf(senderAddr, 0), 1); } - // should be able to send/receive ERC721 token with token callback plugin - function testSendAndReceiveERC721TokenWithDefaultCallbackPlugin() public { + // should be able to send/receive ERC721 token with token callback handler + function testSendAndReceiveERC721TokenWithDefaultCallbackHandler() public { bytes32 salt = 0x0000000000000000000000000000000000000000000000000000000000000000; - (ownerAddr, eoaPrivateKey) = makeAddrAndKey("testSendAndReceiveERC721TokenWithDefaultCallbackPlugin_sender"); - address[] memory plugins = new address[](2); - bytes32[] memory manifestHashes = new bytes32[](2); - bytes[] memory pluginInstallData = new bytes[](2); - plugins[0] = address(defaultTokenCallbackPlugin); - manifestHashes[0] = keccak256(abi.encode(defaultTokenCallbackPlugin.pluginManifest())); - pluginInstallData[0] = ""; - plugins[1] = address(singleOwnerPlugin); - manifestHashes[1] = keccak256(abi.encode(singleOwnerPlugin.pluginManifest())); - pluginInstallData[1] = abi.encode(ownerAddr); + (ownerAddr, eoaPrivateKey) = makeAddrAndKey("testSendAndReceiveERC721TokenWithDefaultCallbackHandler_sender"); + address[] memory plugins = new address[](1); + bytes32[] memory manifestHashes = new bytes32[](1); + bytes[] memory pluginInstallData = new bytes[](1); + plugins[0] = address(singleOwnerPlugin); + manifestHashes[0] = keccak256(abi.encode(singleOwnerPlugin.pluginManifest())); + pluginInstallData[0] = abi.encode(ownerAddr); bytes memory initializingData = abi.encode(plugins, manifestHashes, pluginInstallData); factory.createAccount(ownerAddr, salt, initializingData); (address senderAddr,) = factory.getAddress(ownerAddr, salt, initializingData); diff --git a/test/msca/6900/v0.7/WalletStorageV1Lib.t.sol b/test/msca/6900/v0.7/WalletStorageV1Lib.t.sol index 441f5b8..ab05f41 100644 --- a/test/msca/6900/v0.7/WalletStorageV1Lib.t.sol +++ b/test/msca/6900/v0.7/WalletStorageV1Lib.t.sol @@ -44,7 +44,7 @@ contract WalletStorageV1LibTest is TestUtils { beneficiary = payable(address(makeAddr("bundler"))); } - function testWalletStorageSlot() public { + function testWalletStorageSlot() public pure { bytes32 hash = keccak256(abi.encode(uint256(keccak256(abi.encode("circle.msca.v1.storage"))) - 1)); assertEq(hash, 0xc6a0cc20c824c4eecc4b0fbb7fb297d07492a7bd12c83d4fa4d27b4249f9bfc8); } @@ -188,28 +188,30 @@ contract WalletStorageV1LibTest is TestUtils { function testBulkGetPlugins() public { // try out different limits, even bigger than totalPlugins + // we can't set the limit too high for unoptimized runs such as forge coverage for (uint256 limit = 1; limit <= 10; limit++) { // 4 plugins bulkAddAndGetPlugins(new TestCircleMSCA(entryPoint, pluginManager), 4, limit); } - for (uint256 limit = 1; limit <= 55; limit++) { + for (uint256 limit = 1; limit <= 25; limit++) { bulkAddAndGetPlugins(new TestCircleMSCA(entryPoint, pluginManager), 50, limit); } - for (uint256 limit = 1; limit <= 56; limit++) { + for (uint256 limit = 1; limit <= 26; limit++) { bulkAddAndGetPlugins(new TestCircleMSCA(entryPoint, pluginManager), 50, limit); } } function testBulkGetPreUserOpValidationHooks() public { // try out different limits, even bigger than totalHooks + // we can't set the limit too high for unoptimized runs such as forge coverage for (uint256 limit = 1; limit <= 10; limit++) { // 4 plugins bulkAddAndGetPreUserOpValidationHooks(new TestCircleMSCA(entryPoint, pluginManager), 4, limit); } - for (uint256 limit = 1; limit <= 55; limit++) { + for (uint256 limit = 1; limit <= 25; limit++) { bulkAddAndGetPreUserOpValidationHooks(new TestCircleMSCA(entryPoint, pluginManager), 50, limit); } - for (uint256 limit = 1; limit <= 56; limit++) { + for (uint256 limit = 1; limit <= 26; limit++) { bulkAddAndGetPreUserOpValidationHooks(new TestCircleMSCA(entryPoint, pluginManager), 50, limit); } } @@ -221,7 +223,7 @@ contract WalletStorageV1LibTest is TestUtils { bulkGetPlugins(msca, totalPlugins, limit); } - function bulkGetPlugins(TestCircleMSCA msca, uint256 totalPlugins, uint256 limit) private { + function bulkGetPlugins(TestCircleMSCA msca, uint256 totalPlugins, uint256 limit) private view { address[] memory results = new address[](totalPlugins); address start = address(0x0); uint256 count = 0; @@ -254,6 +256,7 @@ contract WalletStorageV1LibTest is TestUtils { function bulkGetPreUserOpValidationHooks(TestCircleMSCA msca, bytes4 selector, uint256 totalHooks, uint256 limit) private + view { FunctionReference[] memory results = new FunctionReference[](totalHooks); FunctionReference memory start = SENTINEL_BYTES21.unpack(); diff --git a/test/msca/6900/v0.7/plugins/AddressBookPluginWithSemiMSCA.t.sol b/test/msca/6900/v0.7/plugins/AddressBookPluginWithSemiMSCA.t.sol index 84c0825..97aff92 100644 --- a/test/msca/6900/v0.7/plugins/AddressBookPluginWithSemiMSCA.t.sol +++ b/test/msca/6900/v0.7/plugins/AddressBookPluginWithSemiMSCA.t.sol @@ -90,7 +90,7 @@ contract AddressBookPluginWithSemiMSCATest is TestUtils { console.logAddress(address(testLiquidityPool)); } - function testPluginMetadataItself() public { + function testPluginMetadataItself() public view { PluginMetadata memory pluginMetadata = addressBookPlugin.pluginMetadata(); assertEq(pluginMetadata.name, "Address Book Plugin"); assertEq(pluginMetadata.version, PLUGIN_VERSION_1); diff --git a/test/msca/6900/v0.7/plugins/ColdStorageAddressBookPluginWithSemiMSCA.t.sol b/test/msca/6900/v0.7/plugins/ColdStorageAddressBookPluginWithSemiMSCA.t.sol index 656e5ed..48623a8 100644 --- a/test/msca/6900/v0.7/plugins/ColdStorageAddressBookPluginWithSemiMSCA.t.sol +++ b/test/msca/6900/v0.7/plugins/ColdStorageAddressBookPluginWithSemiMSCA.t.sol @@ -120,7 +120,7 @@ contract ColdStorageAddressBookPluginWithSemiMSCATest is TestUtils { testERC721 = new TestERC721("721", "$$$"); } - function testPluginMetadataItself() public { + function testPluginMetadataItself() public view { PluginMetadata memory pluginMetadata = addressBookPlugin.pluginMetadata(); assertEq(pluginMetadata.name, "Cold Storage Address Book Plugin"); assertEq(pluginMetadata.version, PLUGIN_VERSION_1); @@ -1315,7 +1315,7 @@ contract ColdStorageAddressBookPluginWithSemiMSCATest is TestUtils { addressBookPlugin.postExecutionHook(functionId, data); } - function testFuzz_getRecipientForSCCalls(uint256 rand) public { + function testFuzz_getRecipientForSCCalls(uint256 rand) public view { rand = bound(rand, 1, 3); address expectedRecipient = vm.addr(1); uint256 amount = 1; diff --git a/test/msca/6900/v0.7/plugins/WeightedWebauthnMultisigPlugin.t.sol b/test/msca/6900/v0.7/plugins/WeightedWebauthnMultisigPlugin.t.sol index a5c0d9f..8e06838 100644 --- a/test/msca/6900/v0.7/plugins/WeightedWebauthnMultisigPlugin.t.sol +++ b/test/msca/6900/v0.7/plugins/WeightedWebauthnMultisigPlugin.t.sol @@ -29,6 +29,8 @@ import { WebAuthnData, WebAuthnSigDynamicPart } from "../../../../../src/common/CommonStructs.sol"; +import "../../../../../src/msca/6900/v0.7/plugins/v1_0_0/multisig/IWeightedMultisigPlugin.sol"; + import { EIP1271_INVALID_SIGNATURE, EIP1271_VALID_SIGNATURE, @@ -38,31 +40,32 @@ import { SIG_VALIDATION_SUCCEEDED, ZERO_BYTES32 } from "../../../../../src/common/Constants.sol"; - import {AddressBytesLib} from "../../../../../src/libs/AddressBytesLib.sol"; -import {PublicKeyLib} from "../../../../../src/libs/PublicKeyLib.sol"; -import {WebAuthnLib} from "../../../../../src/libs/WebAuthnLib.sol"; -import {NotImplemented} from "../../../../../src/msca/6900/shared/common/Errors.sol"; -import {PluginManifest, PluginMetadata} from "../../../../../src/msca/6900/v0.7/common/PluginManifest.sol"; import {IPlugin} from "../../../../../src/msca/6900/v0.7/interfaces/IPlugin.sol"; import {BasePlugin} from "../../../../../src/msca/6900/v0.7/plugins/BasePlugin.sol"; import {BaseMultisigPlugin} from "../../../../../src/msca/6900/v0.7/plugins/v1_0_0/multisig/BaseMultisigPlugin.sol"; + import {IWeightedMultisigPlugin} from "../../../../../src/msca/6900/v0.7/plugins/v1_0_0/multisig/IWeightedMultisigPlugin.sol"; -import {WeightedWebauthnMultisigPlugin} from - "../../../../../src/msca/6900/v0.7/plugins/v1_0_0/multisig/WeightedWebauthnMultisigPlugin.sol"; - -import {MockContractOwner} from "../../../../util/MockContractOwner.sol"; -import {TestUtils} from "../../../../util/TestUtils.sol"; import {EntryPoint} from "@account-abstraction/contracts/core/EntryPoint.sol"; import {IEntryPoint} from "@account-abstraction/contracts/interfaces/IEntryPoint.sol"; -import {PackedUserOperation} from "@account-abstraction/contracts/interfaces/PackedUserOperation.sol"; - -import {FCL_Elliptic_ZZ} from "@fcl/FCL_elliptic.sol"; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {NotImplemented} from "../../../../../src/msca/6900/shared/common/Errors.sol"; + +import {PluginManifest, PluginMetadata} from "../../../../../src/msca/6900/v0.7/common/PluginManifest.sol"; +import {MockContractOwner} from "../../../../util/MockContractOwner.sol"; +import {PackedUserOperation} from "@account-abstraction/contracts/interfaces/PackedUserOperation.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; + +import {PublicKeyLib} from "../../../../../src/libs/PublicKeyLib.sol"; +import {TestUtils} from "../../../../util/TestUtils.sol"; + +import {WebAuthnLib} from "../../../../../src/libs/WebAuthnLib.sol"; +import {WeightedWebauthnMultisigPlugin} from + "../../../../../src/msca/6900/v0.7/plugins/v1_0_0/multisig/WeightedWebauthnMultisigPlugin.sol"; + import {stdJson} from "forge-std/src/StdJson.sol"; import {VmSafe} from "forge-std/src/Vm.sol"; import {console} from "forge-std/src/console.sol"; @@ -119,6 +122,7 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { uint256 private passkeyPrivateKey = uint256(0x03d99692017473e2d631945a812607b23269d85721e0f370b8d3e7d29a874fd2); uint256 private passkeyPublicKeyX = uint256(0x1c05286fe694493eae33312f2d2e0d0abeda8db76238b7a204be1fb87f54ce42); uint256 private passkeyPublicKeyY = uint256(0x28fef61ef4ac300f631657635c28e59bfb2fe71bce1634c81c65642042f6dc4d); + bytes32 private wrappedDigest; struct Owner { bytes30 owner; @@ -143,6 +147,88 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { uint256 privateKey; } + struct RemoveOwnersInput { + address owner1; + address owner2; + address owner3; + uint256 weight1; + uint256 weight2; + uint256 weight3; + PublicKey pubKey1; + PublicKey pubKey2; + PublicKey pubKey3; + uint256 pubKeyWeight1; + uint256 pubKeyWeight2; + uint256 pubKeyWeight3; + } + + struct UpdateMultisigWeightsPubKeyOnlyInput { + PublicKey pubKey1; + PublicKey pubKey2; + PublicKey pubKey3; + uint256 weight1; + uint256 weight2; + uint256 weight3; + uint256 weight4; + uint256 weight5; + uint256 weight6; + } + + struct UpdateMultisigWeightsInput { + address owner1; + address owner2; + address owner3; + PublicKey pubKey1; + PublicKey pubKey2; + PublicKey pubKey3; + uint256 weight1; + uint256 weight2; + uint256 weight3; + uint256 weight4; + uint256 weight5; + uint256 weight6; + } + + struct MultisigInput { + uint256 k; + uint256 n; + } + + struct AddOwnersInput { + address owner1; + address owner2; + address owner3; + uint256 weight1; + uint256 weight2; + uint256 weight3; + PublicKey pubKey1; + PublicKey pubKey2; + PublicKey pubKey3; + uint256 pubKeyWeight1; + uint256 pubKeyWeight2; + uint256 pubKeyWeight3; + } + + struct AddPubKeyOnlyOwnersThenK1OwnerInput { + PublicKey pubKey1; + PublicKey pubKey2; + PublicKey pubKey3; + uint256 pubKeyWeight1; + uint256 pubKeyWeight2; + uint256 pubKeyWeight3; + address owner1; + uint256 weight1; + } + + struct RemovePubKeyOnlyOwnersInput { + PublicKey pubKey1; + PublicKey pubKey2; + PublicKey pubKey3; + uint256 pubKeyWeight1; + uint256 pubKeyWeight2; + uint256 pubKeyWeight3; + } + uint256 internal constant _MAX_OWNERS = 1000; uint256 internal constant _MAX_WEIGHT = 1000000; string internal constant _NAME = "Weighted Multisig Webauthn Plugin"; @@ -272,7 +358,7 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { assertEq(res, 0); } - function test_pluginManifest() public { + function test_pluginManifest() public view { PluginManifest memory manifest = plugin.pluginManifest(); // 4 execution functions (addOwners, removeOwners, updateMultisigWeights, isValidSignature, // getReplaySafeMessageHash) @@ -285,7 +371,7 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { assertEq(10, manifest.runtimeValidationFunctions.length); } - function test_pluginMetadata() public { + function test_pluginMetadata() public view { PluginMetadata memory metadata = plugin.pluginMetadata(); string memory addOwnersPermission = "Add Owners"; @@ -321,68 +407,116 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { _addOwners(ownerTwoList, weightTwoList, pubKeyTwoList, pubKeyWeightTwoList, thresholdWeightTwo); } - function testFuzz_addOwners( - address owner1, - address owner2, - address owner3, - uint256 weight1, - uint256 weight2, - uint256 weight3, - PublicKey memory pubKey1, - PublicKey memory pubKey2, - PublicKey memory pubKey3, - uint256 pubKeyWeight1, - uint256 pubKeyWeight2, - uint256 pubKeyWeight3 - ) public { - vm.assume(owner1 != address(0)); - vm.assume(owner2 != address(0)); - vm.assume(owner3 != address(0)); - vm.assume(owner1 != owner2); - vm.assume(owner2 != owner3); - vm.assume(owner3 != owner1); - - vm.assume(!(pubKey1.x == 0 && pubKey1.y == 0)); - vm.assume(!(pubKey2.x == 0 && pubKey2.y == 0)); - vm.assume(!(pubKey3.x == 0 && pubKey3.y == 0)); - vm.assume(!_isSame(pubKey1, pubKey2)); - vm.assume(!_isSame(pubKey2, pubKey3)); - vm.assume(!_isSame(pubKey3, pubKey1)); - vm.assume(pubKey1.toBytes30() != owner1.toBytes30()); - vm.assume(pubKey1.toBytes30() != owner2.toBytes30()); - vm.assume(pubKey1.toBytes30() != owner3.toBytes30()); - vm.assume(pubKey2.toBytes30() != owner1.toBytes30()); - vm.assume(pubKey2.toBytes30() != owner2.toBytes30()); - vm.assume(pubKey2.toBytes30() != owner3.toBytes30()); - vm.assume(pubKey3.toBytes30() != owner1.toBytes30()); - vm.assume(pubKey3.toBytes30() != owner2.toBytes30()); - vm.assume(pubKey3.toBytes30() != owner3.toBytes30()); - - weight1 = bound(weight1, 1, _MAX_WEIGHT); - weight2 = bound(weight2, 1, _MAX_WEIGHT); - weight3 = bound(weight3, 1, _MAX_WEIGHT); - - pubKeyWeight1 = bound(pubKeyWeight1, 1, _MAX_WEIGHT); - pubKeyWeight2 = bound(pubKeyWeight2, 1, _MAX_WEIGHT); - pubKeyWeight3 = bound(pubKeyWeight3, 1, _MAX_WEIGHT); + function testFuzz_addOwners(AddOwnersInput memory input) public { + _installPluginForAddOwners(input); + uint256 initialThresholdWeight = input.weight1 + input.weight2 + input.pubKeyWeight1 + input.pubKeyWeight2; + address[] memory newOwners = new address[](1); + newOwners[0] = input.owner3; + PublicKey[] memory newPubKeys = new PublicKey[](1); + newPubKeys[0] = input.pubKey3; + + uint256[] memory newWeights = new uint256[](1); + newWeights[0] = input.weight3; + uint256[] memory newPubKeyWeights = new uint256[](1); + newPubKeyWeights[0] = input.pubKeyWeight3; + + uint256 newThresholdWeight = input.weight1 + input.weight2 + input.weight3 + input.pubKeyWeight1 + + input.pubKeyWeight2 + input.pubKeyWeight3; + (bytes30[] memory _tOwners, OwnerData[] memory _tWeights) = + _mergeOwnersData(newOwners, newWeights, newPubKeys, newPubKeyWeights); + vm.expectEmit(true, true, true, true); + emit ThresholdUpdated(account, initialThresholdWeight, newThresholdWeight); + vm.expectEmit(true, true, true, true); + emit OwnersAdded(account, _tOwners, _tWeights); + + vm.prank(account); + plugin.addOwners(newOwners, newWeights, newPubKeys, newPubKeyWeights, newThresholdWeight); + + ( + bytes30[] memory returnedOwnersAfterUpdate, + OwnerData[] memory returnedOwnersDataAfterUpdate, + OwnershipMetadata memory ownershipMetadataAfterUpdate + ) = plugin.ownershipInfoOf(account); + + uint256 returnedThresholdWeightAfterUpdate = ownershipMetadataAfterUpdate.thresholdWeight; + assertEq(returnedOwnersAfterUpdate.length, 6); + assertEq(returnedOwnersDataAfterUpdate.length, 6); + // new + assertEq(returnedOwnersAfterUpdate[0], input.pubKey3.toBytes30()); + assertEq(returnedOwnersDataAfterUpdate[0].weight, input.pubKeyWeight3); + assertEq(uint8(returnedOwnersDataAfterUpdate[0].credType), uint8(CredentialType.PUBLIC_KEY)); + assertEq(returnedOwnersDataAfterUpdate[0].addr, address(0)); + assertEq(returnedOwnersDataAfterUpdate[0].publicKeyX, input.pubKey3.x); + assertEq(returnedOwnersDataAfterUpdate[0].publicKeyY, input.pubKey3.y); + + assertEq(returnedOwnersAfterUpdate[1], input.owner3.toBytes30()); + assertEq(returnedOwnersDataAfterUpdate[1].weight, input.weight3); + assertEq(uint8(returnedOwnersDataAfterUpdate[1].credType), uint8(CredentialType.ADDRESS)); + assertEq(returnedOwnersDataAfterUpdate[1].addr, input.owner3); + assertEq(returnedOwnersDataAfterUpdate[1].publicKeyX, uint256(0)); + assertEq(returnedOwnersDataAfterUpdate[1].publicKeyY, uint256(0)); + + // old + assertEq(returnedOwnersAfterUpdate[2], input.pubKey2.toBytes30()); + assertEq(returnedOwnersDataAfterUpdate[2].weight, input.pubKeyWeight2); + assertEq(returnedOwnersAfterUpdate[3], input.pubKey1.toBytes30()); + assertEq(returnedOwnersDataAfterUpdate[3].weight, input.pubKeyWeight1); + assertEq(returnedOwnersAfterUpdate[4], input.owner2.toBytes30()); + assertEq(returnedOwnersDataAfterUpdate[4].weight, input.weight2); + assertEq(returnedOwnersAfterUpdate[5], input.owner1.toBytes30()); + assertEq(returnedOwnersDataAfterUpdate[5].weight, input.weight1); + assertEq(returnedThresholdWeightAfterUpdate, newThresholdWeight); + } + + function _installPluginForAddOwners(AddOwnersInput memory input) internal { + vm.assume(input.owner1 != address(0)); + vm.assume(input.owner2 != address(0)); + vm.assume(input.owner3 != address(0)); + vm.assume(input.owner1 != input.owner2); + vm.assume(input.owner2 != input.owner3); + vm.assume(input.owner3 != input.owner1); + + vm.assume(!(input.pubKey1.x == 0 && input.pubKey1.y == 0)); + vm.assume(!(input.pubKey2.x == 0 && input.pubKey2.y == 0)); + vm.assume(!(input.pubKey3.x == 0 && input.pubKey3.y == 0)); + vm.assume(!_isSame(input.pubKey1, input.pubKey2)); + vm.assume(!_isSame(input.pubKey2, input.pubKey3)); + vm.assume(!_isSame(input.pubKey3, input.pubKey1)); + vm.assume(input.pubKey1.toBytes30() != input.owner1.toBytes30()); + vm.assume(input.pubKey1.toBytes30() != input.owner2.toBytes30()); + vm.assume(input.pubKey1.toBytes30() != input.owner3.toBytes30()); + vm.assume(input.pubKey2.toBytes30() != input.owner1.toBytes30()); + vm.assume(input.pubKey2.toBytes30() != input.owner2.toBytes30()); + vm.assume(input.pubKey2.toBytes30() != input.owner3.toBytes30()); + vm.assume(input.pubKey3.toBytes30() != input.owner1.toBytes30()); + vm.assume(input.pubKey3.toBytes30() != input.owner2.toBytes30()); + vm.assume(input.pubKey3.toBytes30() != input.owner3.toBytes30()); + + input.weight1 = bound(input.weight1, 1, _MAX_WEIGHT); + input.weight2 = bound(input.weight2, 1, _MAX_WEIGHT); + input.weight3 = bound(input.weight3, 1, _MAX_WEIGHT); + + input.pubKeyWeight1 = bound(input.pubKeyWeight1, 1, _MAX_WEIGHT); + input.pubKeyWeight2 = bound(input.pubKeyWeight2, 1, _MAX_WEIGHT); + input.pubKeyWeight3 = bound(input.pubKeyWeight3, 1, _MAX_WEIGHT); address[] memory initialOwners = new address[](2); - initialOwners[0] = owner1; - initialOwners[1] = owner2; + initialOwners[0] = input.owner1; + initialOwners[1] = input.owner2; PublicKey[] memory initialPubKeys = new PublicKey[](2); - initialPubKeys[0] = pubKey1; - initialPubKeys[1] = pubKey2; + initialPubKeys[0] = input.pubKey1; + initialPubKeys[1] = input.pubKey2; uint256[] memory initialWeights = new uint256[](2); - initialWeights[0] = weight1; - initialWeights[1] = weight2; + initialWeights[0] = input.weight1; + initialWeights[1] = input.weight2; uint256[] memory initialPubKeyWeights = new uint256[](2); - initialPubKeyWeights[0] = pubKeyWeight1; - initialPubKeyWeights[1] = pubKeyWeight2; + initialPubKeyWeights[0] = input.pubKeyWeight1; + initialPubKeyWeights[1] = input.pubKeyWeight2; - uint256 initialThresholdWeight = weight1 + weight2 + pubKeyWeight1 + pubKeyWeight2; + uint256 initialThresholdWeight = input.weight1 + input.weight2 + input.pubKeyWeight1 + input.pubKeyWeight2; plugin.onInstall( abi.encode(initialOwners, initialWeights, initialPubKeys, initialPubKeyWeights, initialThresholdWeight) ); @@ -396,91 +530,34 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { assertEq(returnedOwners.length, 4); assertEq(returnedOwnersData.length, 4); // (reverse insertion order) - assertEq(returnedOwners[0], pubKey2.toBytes30()); - assertEq(returnedOwnersData[0].weight, pubKeyWeight2); + assertEq(returnedOwners[0], input.pubKey2.toBytes30()); + assertEq(returnedOwnersData[0].weight, input.pubKeyWeight2); assertEq(uint8(returnedOwnersData[0].credType), uint8(CredentialType.PUBLIC_KEY)); assertEq(returnedOwnersData[0].addr, address(0)); - assertEq(returnedOwnersData[0].publicKeyX, pubKey2.x); - assertEq(returnedOwnersData[0].publicKeyY, pubKey2.y); + assertEq(returnedOwnersData[0].publicKeyX, input.pubKey2.x); + assertEq(returnedOwnersData[0].publicKeyY, input.pubKey2.y); - assertEq(returnedOwners[1], pubKey1.toBytes30()); - assertEq(returnedOwnersData[1].weight, pubKeyWeight1); + assertEq(returnedOwners[1], input.pubKey1.toBytes30()); + assertEq(returnedOwnersData[1].weight, input.pubKeyWeight1); assertEq(uint8(returnedOwnersData[1].credType), uint8(CredentialType.PUBLIC_KEY)); assertEq(returnedOwnersData[1].addr, address(0)); - assertEq(returnedOwnersData[1].publicKeyX, pubKey1.x); - assertEq(returnedOwnersData[1].publicKeyY, pubKey1.y); + assertEq(returnedOwnersData[1].publicKeyX, input.pubKey1.x); + assertEq(returnedOwnersData[1].publicKeyY, input.pubKey1.y); - assertEq(returnedOwners[2], owner2.toBytes30()); - assertEq(returnedOwnersData[2].weight, weight2); + assertEq(returnedOwners[2], input.owner2.toBytes30()); + assertEq(returnedOwnersData[2].weight, input.weight2); assertEq(uint8(returnedOwnersData[2].credType), uint8(CredentialType.ADDRESS)); - assertEq(returnedOwnersData[2].addr, owner2); + assertEq(returnedOwnersData[2].addr, input.owner2); assertEq(returnedOwnersData[2].publicKeyX, uint256(0)); assertEq(returnedOwnersData[2].publicKeyY, uint256(0)); - assertEq(returnedOwners[3], owner1.toBytes30()); - assertEq(returnedOwnersData[3].weight, weight1); + assertEq(returnedOwners[3], input.owner1.toBytes30()); + assertEq(returnedOwnersData[3].weight, input.weight1); assertEq(uint8(returnedOwnersData[3].credType), uint8(CredentialType.ADDRESS)); - assertEq(returnedOwnersData[3].addr, owner1); + assertEq(returnedOwnersData[3].addr, input.owner1); assertEq(returnedOwnersData[3].publicKeyX, uint256(0)); assertEq(returnedOwnersData[3].publicKeyY, uint256(0)); - assertEq(returnedThresholdWeight, initialThresholdWeight); - - address[] memory newOwners = new address[](1); - newOwners[0] = owner3; - PublicKey[] memory newPubKeys = new PublicKey[](1); - newPubKeys[0] = pubKey3; - - uint256[] memory newWeights = new uint256[](1); - newWeights[0] = weight3; - uint256[] memory newPubKeyWeights = new uint256[](1); - newPubKeyWeights[0] = pubKeyWeight3; - - uint256 newThresholdWeight = weight1 + weight2 + weight3 + pubKeyWeight1 + pubKeyWeight2 + pubKeyWeight3; - (bytes30[] memory _tOwners, OwnerData[] memory _tWeights) = - _mergeOwnersData(newOwners, newWeights, newPubKeys, newPubKeyWeights); - vm.expectEmit(true, true, true, true); - emit ThresholdUpdated(account, initialThresholdWeight, newThresholdWeight); - vm.expectEmit(true, true, true, true); - emit OwnersAdded(account, _tOwners, _tWeights); - - vm.prank(account); - plugin.addOwners(newOwners, newWeights, newPubKeys, newPubKeyWeights, newThresholdWeight); - - ( - bytes30[] memory returnedOwnersAfterUpdate, - OwnerData[] memory returnedOwnersDataAfterUpdate, - OwnershipMetadata memory ownershipMetadataAfterUpdate - ) = plugin.ownershipInfoOf(account); - - uint256 returnedThresholdWeightAfterUpdate = ownershipMetadataAfterUpdate.thresholdWeight; - assertEq(returnedOwnersAfterUpdate.length, 6); - assertEq(returnedOwnersDataAfterUpdate.length, 6); - // new - assertEq(returnedOwnersAfterUpdate[0], pubKey3.toBytes30()); - assertEq(returnedOwnersDataAfterUpdate[0].weight, pubKeyWeight3); - assertEq(uint8(returnedOwnersDataAfterUpdate[0].credType), uint8(CredentialType.PUBLIC_KEY)); - assertEq(returnedOwnersDataAfterUpdate[0].addr, address(0)); - assertEq(returnedOwnersDataAfterUpdate[0].publicKeyX, pubKey3.x); - assertEq(returnedOwnersDataAfterUpdate[0].publicKeyY, pubKey3.y); - - assertEq(returnedOwnersAfterUpdate[1], owner3.toBytes30()); - assertEq(returnedOwnersDataAfterUpdate[1].weight, weight3); - assertEq(uint8(returnedOwnersDataAfterUpdate[1].credType), uint8(CredentialType.ADDRESS)); - assertEq(returnedOwnersDataAfterUpdate[1].addr, owner3); - assertEq(returnedOwnersDataAfterUpdate[1].publicKeyX, uint256(0)); - assertEq(returnedOwnersDataAfterUpdate[1].publicKeyY, uint256(0)); - - // old - assertEq(returnedOwnersAfterUpdate[2], pubKey2.toBytes30()); - assertEq(returnedOwnersDataAfterUpdate[2].weight, pubKeyWeight2); - assertEq(returnedOwnersAfterUpdate[3], pubKey1.toBytes30()); - assertEq(returnedOwnersDataAfterUpdate[3].weight, pubKeyWeight1); - assertEq(returnedOwnersAfterUpdate[4], owner2.toBytes30()); - assertEq(returnedOwnersDataAfterUpdate[4].weight, weight2); - assertEq(returnedOwnersAfterUpdate[5], owner1.toBytes30()); - assertEq(returnedOwnersDataAfterUpdate[5].weight, weight1); - assertEq(returnedThresholdWeightAfterUpdate, newThresholdWeight); } function test_addOwnersZeroThreshold() public { @@ -643,71 +720,58 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { assertEq(res, returnedWeights[0].weight); } - function testFuzz_removeOwners( - address owner1, - address owner2, - address owner3, - uint256 weight1, - uint256 weight2, - uint256 weight3, - PublicKey memory pubKey1, - PublicKey memory pubKey2, - PublicKey memory pubKey3, - uint256 pubKeyWeight1, - uint256 pubKeyWeight2, - uint256 pubKeyWeight3 - ) public { - vm.assume(owner1 != address(0)); - vm.assume(owner2 != address(0)); - vm.assume(owner3 != address(0)); - vm.assume(owner1 != owner2); - vm.assume(owner2 != owner3); - vm.assume(owner3 != owner1); - - vm.assume(!(pubKey1.x == 0 && pubKey1.y == 0)); - vm.assume(!(pubKey2.x == 0 && pubKey2.y == 0)); - vm.assume(!(pubKey3.x == 0 && pubKey3.y == 0)); - vm.assume(!_isSame(pubKey1, pubKey2)); - vm.assume(!_isSame(pubKey2, pubKey3)); - vm.assume(!_isSame(pubKey3, pubKey1)); - vm.assume(pubKey1.toBytes30() != owner1.toBytes30()); - vm.assume(pubKey1.toBytes30() != owner2.toBytes30()); - vm.assume(pubKey1.toBytes30() != owner3.toBytes30()); - vm.assume(pubKey2.toBytes30() != owner1.toBytes30()); - vm.assume(pubKey2.toBytes30() != owner2.toBytes30()); - vm.assume(pubKey2.toBytes30() != owner3.toBytes30()); - vm.assume(pubKey3.toBytes30() != owner1.toBytes30()); - vm.assume(pubKey3.toBytes30() != owner2.toBytes30()); - vm.assume(pubKey3.toBytes30() != owner3.toBytes30()); - - weight1 = bound(weight1, 1, _MAX_WEIGHT); - weight2 = bound(weight2, 1, _MAX_WEIGHT); - weight3 = bound(weight3, 1, _MAX_WEIGHT); - - pubKeyWeight1 = bound(pubKeyWeight1, 1, _MAX_WEIGHT); - pubKeyWeight2 = bound(pubKeyWeight2, 1, _MAX_WEIGHT); - pubKeyWeight3 = bound(pubKeyWeight3, 1, _MAX_WEIGHT); + function testFuzz_removeOwners(RemoveOwnersInput memory input) public { + vm.assume(input.owner1 != address(0)); + vm.assume(input.owner2 != address(0)); + vm.assume(input.owner3 != address(0)); + vm.assume(input.owner1 != input.owner2); + vm.assume(input.owner2 != input.owner3); + vm.assume(input.owner3 != input.owner1); + + vm.assume(!(input.pubKey1.x == 0 && input.pubKey1.y == 0)); + vm.assume(!(input.pubKey2.x == 0 && input.pubKey2.y == 0)); + vm.assume(!(input.pubKey3.x == 0 && input.pubKey3.y == 0)); + vm.assume(!_isSame(input.pubKey1, input.pubKey2)); + vm.assume(!_isSame(input.pubKey2, input.pubKey3)); + vm.assume(!_isSame(input.pubKey3, input.pubKey1)); + vm.assume(input.pubKey1.toBytes30() != input.owner1.toBytes30()); + vm.assume(input.pubKey1.toBytes30() != input.owner2.toBytes30()); + vm.assume(input.pubKey1.toBytes30() != input.owner3.toBytes30()); + vm.assume(input.pubKey2.toBytes30() != input.owner1.toBytes30()); + vm.assume(input.pubKey2.toBytes30() != input.owner2.toBytes30()); + vm.assume(input.pubKey2.toBytes30() != input.owner3.toBytes30()); + vm.assume(input.pubKey3.toBytes30() != input.owner1.toBytes30()); + vm.assume(input.pubKey3.toBytes30() != input.owner2.toBytes30()); + vm.assume(input.pubKey3.toBytes30() != input.owner3.toBytes30()); + + input.weight1 = bound(input.weight1, 1, _MAX_WEIGHT); + input.weight2 = bound(input.weight2, 1, _MAX_WEIGHT); + input.weight3 = bound(input.weight3, 1, _MAX_WEIGHT); + + input.pubKeyWeight1 = bound(input.pubKeyWeight1, 1, _MAX_WEIGHT); + input.pubKeyWeight2 = bound(input.pubKeyWeight2, 1, _MAX_WEIGHT); + input.pubKeyWeight3 = bound(input.pubKeyWeight3, 1, _MAX_WEIGHT); address[] memory initialOwners = new address[](3); - initialOwners[0] = owner1; - initialOwners[1] = owner2; - initialOwners[2] = owner3; + initialOwners[0] = input.owner1; + initialOwners[1] = input.owner2; + initialOwners[2] = input.owner3; PublicKey[] memory initialPubKeys = new PublicKey[](3); - initialPubKeys[0] = pubKey1; - initialPubKeys[1] = pubKey2; - initialPubKeys[2] = pubKey3; + initialPubKeys[0] = input.pubKey1; + initialPubKeys[1] = input.pubKey2; + initialPubKeys[2] = input.pubKey3; uint256[] memory initialWeights = new uint256[](3); - initialWeights[0] = weight1; - initialWeights[1] = weight2; - initialWeights[2] = weight3; + initialWeights[0] = input.weight1; + initialWeights[1] = input.weight2; + initialWeights[2] = input.weight3; uint256[] memory initialPubKeyWeights = new uint256[](3); - initialPubKeyWeights[0] = pubKeyWeight1; - initialPubKeyWeights[1] = pubKeyWeight2; - initialPubKeyWeights[2] = pubKeyWeight3; + initialPubKeyWeights[0] = input.pubKeyWeight1; + initialPubKeyWeights[1] = input.pubKeyWeight2; + initialPubKeyWeights[2] = input.pubKeyWeight3; - uint256 initialThresholdWeight = weight1 + weight2 + pubKeyWeight1 + pubKeyWeight2; + uint256 initialThresholdWeight = input.weight1 + input.weight2 + input.pubKeyWeight1 + input.pubKeyWeight2; plugin.onInstall( abi.encode(initialOwners, initialWeights, initialPubKeys, initialPubKeyWeights, initialThresholdWeight) @@ -718,63 +782,64 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { OwnershipMetadata memory ownershipMetadata ) = plugin.ownershipInfoOf(account); - uint256 returnedThresholdWeight = ownershipMetadata.thresholdWeight; assertEq(returnedOwners.length, 6); assertEq(returnedOwnersData.length, 6); // (reverse insertion order) - assertEq(returnedOwners[0], pubKey3.toBytes30()); - assertEq(returnedOwnersData[0].weight, pubKeyWeight3); + assertEq(returnedOwners[0], input.pubKey3.toBytes30()); + assertEq(returnedOwnersData[0].weight, input.pubKeyWeight3); assertEq(uint8(returnedOwnersData[0].credType), uint8(CredentialType.PUBLIC_KEY)); assertEq(returnedOwnersData[0].addr, address(0)); - assertEq(returnedOwnersData[0].publicKeyX, pubKey3.x); - assertEq(returnedOwnersData[0].publicKeyY, pubKey3.y); + assertEq(returnedOwnersData[0].publicKeyX, input.pubKey3.x); + assertEq(returnedOwnersData[0].publicKeyY, input.pubKey3.y); - assertEq(returnedOwners[1], pubKey2.toBytes30()); - assertEq(returnedOwnersData[1].weight, pubKeyWeight2); + assertEq(returnedOwners[1], input.pubKey2.toBytes30()); + assertEq(returnedOwnersData[1].weight, input.pubKeyWeight2); assertEq(uint8(returnedOwnersData[1].credType), uint8(CredentialType.PUBLIC_KEY)); assertEq(returnedOwnersData[1].addr, address(0)); - assertEq(returnedOwnersData[1].publicKeyX, pubKey2.x); - assertEq(returnedOwnersData[1].publicKeyY, pubKey2.y); + assertEq(returnedOwnersData[1].publicKeyX, input.pubKey2.x); + assertEq(returnedOwnersData[1].publicKeyY, input.pubKey2.y); - assertEq(returnedOwners[2], pubKey1.toBytes30()); - assertEq(returnedOwnersData[2].weight, pubKeyWeight1); + assertEq(returnedOwners[2], input.pubKey1.toBytes30()); + assertEq(returnedOwnersData[2].weight, input.pubKeyWeight1); assertEq(uint8(returnedOwnersData[2].credType), uint8(CredentialType.PUBLIC_KEY)); assertEq(returnedOwnersData[2].addr, address(0)); - assertEq(returnedOwnersData[2].publicKeyX, pubKey1.x); - assertEq(returnedOwnersData[2].publicKeyY, pubKey1.y); + assertEq(returnedOwnersData[2].publicKeyX, input.pubKey1.x); + assertEq(returnedOwnersData[2].publicKeyY, input.pubKey1.y); - assertEq(returnedOwners[3], owner3.toBytes30()); - assertEq(returnedOwnersData[3].weight, weight3); + assertEq(returnedOwners[3], input.owner3.toBytes30()); + assertEq(returnedOwnersData[3].weight, input.weight3); assertEq(uint8(returnedOwnersData[3].credType), uint8(CredentialType.ADDRESS)); - assertEq(returnedOwnersData[3].addr, owner3); + assertEq(returnedOwnersData[3].addr, input.owner3); assertEq(returnedOwnersData[3].publicKeyX, uint256(0)); assertEq(returnedOwnersData[3].publicKeyY, uint256(0)); - assertEq(returnedOwners[4], owner2.toBytes30()); - assertEq(returnedOwnersData[4].weight, weight2); + assertEq(returnedOwners[4], input.owner2.toBytes30()); + assertEq(returnedOwnersData[4].weight, input.weight2); assertEq(uint8(returnedOwnersData[4].credType), uint8(CredentialType.ADDRESS)); - assertEq(returnedOwnersData[4].addr, owner2); + assertEq(returnedOwnersData[4].addr, input.owner2); assertEq(returnedOwnersData[4].publicKeyX, uint256(0)); assertEq(returnedOwnersData[4].publicKeyY, uint256(0)); - assertEq(returnedOwners[5], owner1.toBytes30()); - assertEq(returnedOwnersData[5].weight, weight1); + assertEq(returnedOwners[5], input.owner1.toBytes30()); + assertEq(returnedOwnersData[5].weight, input.weight1); assertEq(uint8(returnedOwnersData[5].credType), uint8(CredentialType.ADDRESS)); - assertEq(returnedOwnersData[5].addr, owner1); + assertEq(returnedOwnersData[5].addr, input.owner1); assertEq(returnedOwnersData[5].publicKeyX, uint256(0)); assertEq(returnedOwnersData[5].publicKeyY, uint256(0)); - assertEq(returnedThresholdWeight, initialThresholdWeight); + assertEq(ownershipMetadata.thresholdWeight, initialThresholdWeight); + _removeOwnersAndAssert(input); + } + function _removeOwnersAndAssert(RemoveOwnersInput memory input) internal { // remove owners address[] memory ownersToRemove = new address[](2); - ownersToRemove[0] = owner1; - ownersToRemove[1] = owner2; + ownersToRemove[0] = input.owner1; + ownersToRemove[1] = input.owner2; PublicKey[] memory pubKeysToRemove = new PublicKey[](2); - pubKeysToRemove[0] = pubKey1; - pubKeysToRemove[1] = pubKey2; - - uint256 newThresholdWeight = weight3 + pubKeyWeight3; + pubKeysToRemove[0] = input.pubKey1; + pubKeysToRemove[1] = input.pubKey2; + uint256 newThresholdWeight = input.weight3 + input.pubKeyWeight3; vm.prank(account); plugin.removeOwners(ownersToRemove, pubKeysToRemove, newThresholdWeight); @@ -786,25 +851,24 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { OwnershipMetadata memory ownershipMetadataAfterUpdate ) = plugin.ownershipInfoOf(account); - uint256 returnedThresholdWeightAfterUpdate = ownershipMetadataAfterUpdate.thresholdWeight; assertEq(returnedOwnersAfterUpdate.length, 2); assertEq(returnedOwnersDataAfterUpdate.length, 2); - assertEq(returnedOwnersAfterUpdate[0], pubKey3.toBytes30()); - assertEq(returnedOwnersDataAfterUpdate[0].weight, pubKeyWeight3); + assertEq(returnedOwnersAfterUpdate[0], input.pubKey3.toBytes30()); + assertEq(returnedOwnersDataAfterUpdate[0].weight, input.pubKeyWeight3); assertEq(uint8(returnedOwnersDataAfterUpdate[0].credType), uint8(CredentialType.PUBLIC_KEY)); assertEq(returnedOwnersDataAfterUpdate[0].addr, address(0)); - assertEq(returnedOwnersDataAfterUpdate[0].publicKeyX, pubKey3.x); - assertEq(returnedOwnersDataAfterUpdate[0].publicKeyY, pubKey3.y); + assertEq(returnedOwnersDataAfterUpdate[0].publicKeyX, input.pubKey3.x); + assertEq(returnedOwnersDataAfterUpdate[0].publicKeyY, input.pubKey3.y); - assertEq(returnedOwnersAfterUpdate[1], owner3.toBytes30()); - assertEq(returnedOwnersDataAfterUpdate[1].weight, weight3); + assertEq(returnedOwnersAfterUpdate[1], input.owner3.toBytes30()); + assertEq(returnedOwnersDataAfterUpdate[1].weight, input.weight3); assertEq(uint8(returnedOwnersDataAfterUpdate[1].credType), uint8(CredentialType.ADDRESS)); - assertEq(returnedOwnersDataAfterUpdate[1].addr, owner3); + assertEq(returnedOwnersDataAfterUpdate[1].addr, input.owner3); assertEq(returnedOwnersDataAfterUpdate[1].publicKeyX, uint256(0)); assertEq(returnedOwnersDataAfterUpdate[1].publicKeyY, uint256(0)); - assertEq(returnedThresholdWeightAfterUpdate, newThresholdWeight); + assertEq(ownershipMetadataAfterUpdate.thresholdWeight, newThresholdWeight); } function test_removeOwners_zeroThreshold() public { @@ -1112,71 +1176,59 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { assertEq(returnedThresholdWeight, _newThresholdWeight); } - function testFuzz_updateMultisigWeights( - address owner1, - address owner2, - address owner3, - PublicKey memory pubKey1, - PublicKey memory pubKey2, - PublicKey memory pubKey3, - uint256 weight1, - uint256 weight2, - uint256 weight3, - uint256 weight4, - uint256 weight5, - uint256 weight6 - ) public { - vm.assume(owner1 != address(0)); - vm.assume(owner2 != address(0)); - vm.assume(owner3 != address(0)); - vm.assume(owner1 != owner2); - vm.assume(owner2 != owner3); - vm.assume(owner3 != owner1); - - vm.assume(!(pubKey1.x == 0 && pubKey1.y == 0)); - vm.assume(!(pubKey2.x == 0 && pubKey2.y == 0)); - vm.assume(!(pubKey3.x == 0 && pubKey3.y == 0)); - vm.assume(!_isSame(pubKey1, pubKey2)); - vm.assume(!_isSame(pubKey2, pubKey3)); - vm.assume(!_isSame(pubKey3, pubKey1)); - vm.assume(pubKey1.toBytes30() != owner1.toBytes30()); - vm.assume(pubKey1.toBytes30() != owner2.toBytes30()); - vm.assume(pubKey1.toBytes30() != owner3.toBytes30()); - vm.assume(pubKey2.toBytes30() != owner1.toBytes30()); - vm.assume(pubKey2.toBytes30() != owner2.toBytes30()); - vm.assume(pubKey2.toBytes30() != owner3.toBytes30()); - vm.assume(pubKey3.toBytes30() != owner1.toBytes30()); - vm.assume(pubKey3.toBytes30() != owner2.toBytes30()); - vm.assume(pubKey3.toBytes30() != owner3.toBytes30()); - - weight1 = bound(weight1, 1, _MAX_WEIGHT); - weight2 = bound(weight2, 1, _MAX_WEIGHT); - weight3 = bound(weight3, 1, _MAX_WEIGHT); - weight4 = bound(weight4, 1, _MAX_WEIGHT); - weight5 = bound(weight5, 1, _MAX_WEIGHT); - weight6 = bound(weight6, 1, _MAX_WEIGHT); + function testFuzz_updateMultisigWeights(UpdateMultisigWeightsInput memory input) public { + vm.assume(input.owner1 != address(0)); + vm.assume(input.owner2 != address(0)); + vm.assume(input.owner3 != address(0)); + vm.assume(input.owner1 != input.owner2); + vm.assume(input.owner2 != input.owner3); + vm.assume(input.owner3 != input.owner1); + + vm.assume(!(input.pubKey1.x == 0 && input.pubKey1.y == 0)); + vm.assume(!(input.pubKey2.x == 0 && input.pubKey2.y == 0)); + vm.assume(!(input.pubKey3.x == 0 && input.pubKey3.y == 0)); + vm.assume(!_isSame(input.pubKey1, input.pubKey2)); + vm.assume(!_isSame(input.pubKey2, input.pubKey3)); + vm.assume(!_isSame(input.pubKey3, input.pubKey1)); + vm.assume(input.pubKey1.toBytes30() != input.owner1.toBytes30()); + vm.assume(input.pubKey1.toBytes30() != input.owner2.toBytes30()); + vm.assume(input.pubKey1.toBytes30() != input.owner3.toBytes30()); + vm.assume(input.pubKey2.toBytes30() != input.owner1.toBytes30()); + vm.assume(input.pubKey2.toBytes30() != input.owner2.toBytes30()); + vm.assume(input.pubKey2.toBytes30() != input.owner3.toBytes30()); + vm.assume(input.pubKey3.toBytes30() != input.owner1.toBytes30()); + vm.assume(input.pubKey3.toBytes30() != input.owner2.toBytes30()); + vm.assume(input.pubKey3.toBytes30() != input.owner3.toBytes30()); + + input.weight1 = bound(input.weight1, 1, _MAX_WEIGHT); + input.weight2 = bound(input.weight2, 1, _MAX_WEIGHT); + input.weight3 = bound(input.weight3, 1, _MAX_WEIGHT); + input.weight4 = bound(input.weight4, 1, _MAX_WEIGHT); + input.weight5 = bound(input.weight5, 1, _MAX_WEIGHT); + input.weight6 = bound(input.weight6, 1, _MAX_WEIGHT); address[] memory initialOwners = new address[](3); - initialOwners[0] = owner1; - initialOwners[1] = owner2; - initialOwners[2] = owner3; + initialOwners[0] = input.owner1; + initialOwners[1] = input.owner2; + initialOwners[2] = input.owner3; PublicKey[] memory initialPubKeys = new PublicKey[](3); - initialPubKeys[0] = pubKey1; - initialPubKeys[1] = pubKey2; - initialPubKeys[2] = pubKey3; + initialPubKeys[0] = input.pubKey1; + initialPubKeys[1] = input.pubKey2; + initialPubKeys[2] = input.pubKey3; uint256[] memory initialWeights = new uint256[](3); - initialWeights[0] = weight1; - initialWeights[1] = weight2; - initialWeights[2] = weight3; + initialWeights[0] = input.weight1; + initialWeights[1] = input.weight2; + initialWeights[2] = input.weight3; uint256[] memory initialPubKeyWeights = new uint256[](3); - initialPubKeyWeights[0] = weight1; - initialPubKeyWeights[1] = weight2; - initialPubKeyWeights[2] = weight3; + initialPubKeyWeights[0] = input.weight1; + initialPubKeyWeights[1] = input.weight2; + initialPubKeyWeights[2] = input.weight3; - uint256 initialThresholdWeight = weight1 + weight2 + weight3 + weight1 + weight2 + weight3; + uint256 initialThresholdWeight = + input.weight1 + input.weight2 + input.weight3 + input.weight1 + input.weight2 + input.weight3; plugin.onInstall( abi.encode(initialOwners, initialWeights, initialPubKeys, initialPubKeyWeights, initialThresholdWeight) @@ -1191,30 +1243,39 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { assertEq(returnedOwners.length, 6); assertEq(returnedOwnersData.length, 6); // (reverse insertion order) - assertEq(returnedOwners[0], pubKey3.toBytes30()); - assertEq(returnedOwnersData[0].weight, weight3); - assertEq(returnedOwners[1], pubKey2.toBytes30()); - assertEq(returnedOwnersData[1].weight, weight2); - assertEq(returnedOwners[2], pubKey1.toBytes30()); - assertEq(returnedOwnersData[2].weight, weight1); - assertEq(returnedOwners[3], owner3.toBytes30()); - assertEq(returnedOwnersData[3].weight, weight3); - assertEq(returnedOwners[4], owner2.toBytes30()); - assertEq(returnedOwnersData[4].weight, weight2); - assertEq(returnedOwners[5], owner1.toBytes30()); - assertEq(returnedOwnersData[5].weight, weight1); + assertEq(returnedOwners[0], input.pubKey3.toBytes30()); + assertEq(returnedOwnersData[0].weight, input.weight3); + assertEq(returnedOwners[1], input.pubKey2.toBytes30()); + assertEq(returnedOwnersData[1].weight, input.weight2); + assertEq(returnedOwners[2], input.pubKey1.toBytes30()); + assertEq(returnedOwnersData[2].weight, input.weight1); + assertEq(returnedOwners[3], input.owner3.toBytes30()); + assertEq(returnedOwnersData[3].weight, input.weight3); + assertEq(returnedOwners[4], input.owner2.toBytes30()); + assertEq(returnedOwnersData[4].weight, input.weight2); + assertEq(returnedOwners[5], input.owner1.toBytes30()); + assertEq(returnedOwnersData[5].weight, input.weight1); assertEq(returnedThresholdWeight, initialThresholdWeight); + _updateWeightsAndAssert(input, initialOwners, initialPubKeys); + } + + function _updateWeightsAndAssert( + UpdateMultisigWeightsInput memory input, + address[] memory initialOwners, + PublicKey[] memory initialPubKeys + ) internal { uint256[] memory newWeights = new uint256[](3); - newWeights[0] = weight4; - newWeights[1] = weight5; - newWeights[2] = weight6; + newWeights[0] = input.weight4; + newWeights[1] = input.weight5; + newWeights[2] = input.weight6; uint256[] memory newPubKeyWeights = new uint256[](3); - newPubKeyWeights[0] = weight4; - newPubKeyWeights[1] = weight5; - newPubKeyWeights[2] = weight6; + newPubKeyWeights[0] = input.weight4; + newPubKeyWeights[1] = input.weight5; + newPubKeyWeights[2] = input.weight6; - uint256 newThresholdWeight = weight4 + weight5 + weight6 + weight4 + weight5 + weight6; + uint256 newThresholdWeight = + input.weight4 + input.weight5 + input.weight6 + input.weight4 + input.weight5 + input.weight6; vm.prank(account); plugin.updateMultisigWeights(initialOwners, newWeights, initialPubKeys, newPubKeyWeights, newThresholdWeight); @@ -1228,22 +1289,22 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { uint256 returnedThresholdWeightAfterUpdate = ownershipMetadataAfterUpdate.thresholdWeight; assertEq(returnedOwnersAfterUpdate.length, 6); assertEq(returnedOwnersDataAfterUpdate.length, 6); - assertEq(returnedOwnersAfterUpdate[0], pubKey3.toBytes30()); - assertEq(returnedOwnersDataAfterUpdate[0].weight, weight6); - assertEq(returnedOwnersAfterUpdate[1], pubKey2.toBytes30()); - assertEq(returnedOwnersDataAfterUpdate[1].weight, weight5); - assertEq(returnedOwnersAfterUpdate[2], pubKey1.toBytes30()); - assertEq(returnedOwnersDataAfterUpdate[2].weight, weight4); - assertEq(returnedOwnersAfterUpdate[3], owner3.toBytes30()); - assertEq(returnedOwnersDataAfterUpdate[3].weight, weight6); - assertEq(returnedOwnersAfterUpdate[4], owner2.toBytes30()); - assertEq(returnedOwnersDataAfterUpdate[4].weight, weight5); - assertEq(returnedOwnersAfterUpdate[5], owner1.toBytes30()); - assertEq(returnedOwnersDataAfterUpdate[5].weight, weight4); + assertEq(returnedOwnersAfterUpdate[0], input.pubKey3.toBytes30()); + assertEq(returnedOwnersDataAfterUpdate[0].weight, input.weight6); + assertEq(returnedOwnersAfterUpdate[1], input.pubKey2.toBytes30()); + assertEq(returnedOwnersDataAfterUpdate[1].weight, input.weight5); + assertEq(returnedOwnersAfterUpdate[2], input.pubKey1.toBytes30()); + assertEq(returnedOwnersDataAfterUpdate[2].weight, input.weight4); + assertEq(returnedOwnersAfterUpdate[3], input.owner3.toBytes30()); + assertEq(returnedOwnersDataAfterUpdate[3].weight, input.weight6); + assertEq(returnedOwnersAfterUpdate[4], input.owner2.toBytes30()); + assertEq(returnedOwnersDataAfterUpdate[4].weight, input.weight5); + assertEq(returnedOwnersAfterUpdate[5], input.owner1.toBytes30()); + assertEq(returnedOwnersDataAfterUpdate[5].weight, input.weight4); assertEq(returnedThresholdWeightAfterUpdate, newThresholdWeight); } - function test_supportsInterface() public { + function test_supportsInterface() public view { bool isSupported = plugin.supportsInterface(type(IWeightedMultisigPlugin).interfaceId); assertTrue(isSupported); isSupported = plugin.supportsInterface(type(IPlugin).interfaceId); @@ -1287,7 +1348,7 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { assertEq(ownerData.publicKeyY, uint256(0)); } - function testFuzz_getOwnerId(address addr, PublicKey memory pubKey) public { + function testFuzz_getOwnerId(address addr, PublicKey memory pubKey) public view { vm.assume(pubKey.x != 0 || pubKey.y != 0); console.logString("addrId:"); console.logBytes32(addr.toBytes30()); @@ -1318,77 +1379,19 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { assertEq(returnedThresholdWeight, thresholdWeightOne); } - function testFuzz_addPubKeyOnlyOwnersThenK1Owner( - PublicKey memory pubKey1, - PublicKey memory pubKey2, - PublicKey memory pubKey3, - uint256 pubKeyWeight1, - uint256 pubKeyWeight2, - uint256 pubKeyWeight3, - address owner1, - uint256 weight1 - ) public { - vm.assume(!(pubKey1.x == 0 && pubKey1.y == 0)); - vm.assume(!(pubKey2.x == 0 && pubKey2.y == 0)); - vm.assume(!(pubKey3.x == 0 && pubKey3.y == 0)); - vm.assume(!_isSame(pubKey1, pubKey2)); - vm.assume(!_isSame(pubKey2, pubKey3)); - vm.assume(!_isSame(pubKey3, pubKey1)); - - vm.assume(owner1 != address(0)); - - vm.assume(pubKey1.toBytes30() != owner1.toBytes30()); - vm.assume(pubKey2.toBytes30() != owner1.toBytes30()); - vm.assume(pubKey3.toBytes30() != owner1.toBytes30()); - - pubKeyWeight1 = bound(pubKeyWeight1, 1, _MAX_WEIGHT); - pubKeyWeight2 = bound(pubKeyWeight2, 1, _MAX_WEIGHT); - pubKeyWeight3 = bound(pubKeyWeight3, 1, _MAX_WEIGHT); - - weight1 = bound(weight1, 1, _MAX_WEIGHT); - - // no k1 owners - address[] memory initialOwners; - uint256[] memory initialWeights; - - PublicKey[] memory initialPubKeys = new PublicKey[](2); - initialPubKeys[0] = pubKey1; - initialPubKeys[1] = pubKey2; - - uint256[] memory initialPubKeyWeights = new uint256[](2); - initialPubKeyWeights[0] = pubKeyWeight1; - initialPubKeyWeights[1] = pubKeyWeight2; - - uint256 initialThresholdWeight = pubKeyWeight1 + pubKeyWeight2; - plugin.onInstall( - abi.encode(initialOwners, initialWeights, initialPubKeys, initialPubKeyWeights, initialThresholdWeight) - ); - ( - bytes30[] memory returnedOwners, - OwnerData[] memory returnedOwnersData, - OwnershipMetadata memory ownershipMetadata - ) = plugin.ownershipInfoOf(account); - uint256 returnedThresholdWeight = ownershipMetadata.thresholdWeight; - - assertEq(returnedOwners.length, 2); - assertEq(returnedOwnersData.length, 2); - // (reverse insertion order) - assertEq(returnedOwners[0], pubKey2.toBytes30()); - assertEq(returnedOwnersData[0].weight, pubKeyWeight2); - assertEq(returnedOwners[1], pubKey1.toBytes30()); - assertEq(returnedOwnersData[1].weight, pubKeyWeight1); - assertEq(returnedThresholdWeight, initialThresholdWeight); - + function testFuzz_addPubKeyOnlyOwnersThenK1Owner(AddPubKeyOnlyOwnersThenK1OwnerInput memory input) public { + _installPluginForAddPubKeyOnlyOwnersThenK1Owner(input); + uint256 initialThresholdWeight = input.pubKeyWeight1 + input.pubKeyWeight2; // still no k1 configs address[] memory newOwners; uint256[] memory newWeights; PublicKey[] memory newPubKeys = new PublicKey[](1); - newPubKeys[0] = pubKey3; + newPubKeys[0] = input.pubKey3; uint256[] memory newPubKeyWeights = new uint256[](1); - newPubKeyWeights[0] = pubKeyWeight3; + newPubKeyWeights[0] = input.pubKeyWeight3; - uint256 newThresholdWeight = pubKeyWeight1 + pubKeyWeight2 + pubKeyWeight3; + uint256 newThresholdWeight = input.pubKeyWeight1 + input.pubKeyWeight2 + input.pubKeyWeight3; (bytes30[] memory _tOwners, OwnerData[] memory _tWeights) = _mergeOwnersData(newOwners, newWeights, newPubKeys, newPubKeyWeights); vm.expectEmit(true, true, true, true); @@ -1405,31 +1408,30 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { OwnershipMetadata memory ownershipMetadataAfterUpdate ) = plugin.ownershipInfoOf(account); - uint256 returnedThresholdWeightAfterUpdate = ownershipMetadataAfterUpdate.thresholdWeight; assertEq(returnedOwnersAfterUpdate.length, 3); assertEq(returnedOwnersDataAfterUpdate.length, 3); // new - assertEq(returnedOwnersAfterUpdate[0], pubKey3.toBytes30()); - assertEq(returnedOwnersDataAfterUpdate[0].weight, pubKeyWeight3); + assertEq(returnedOwnersAfterUpdate[0], input.pubKey3.toBytes30()); + assertEq(returnedOwnersDataAfterUpdate[0].weight, input.pubKeyWeight3); // old - assertEq(returnedOwnersAfterUpdate[1], pubKey2.toBytes30()); - assertEq(returnedOwnersDataAfterUpdate[1].weight, pubKeyWeight2); - assertEq(returnedOwnersAfterUpdate[2], pubKey1.toBytes30()); - assertEq(returnedOwnersDataAfterUpdate[2].weight, pubKeyWeight1); - assertEq(returnedThresholdWeightAfterUpdate, newThresholdWeight); + assertEq(returnedOwnersAfterUpdate[1], input.pubKey2.toBytes30()); + assertEq(returnedOwnersDataAfterUpdate[1].weight, input.pubKeyWeight2); + assertEq(returnedOwnersAfterUpdate[2], input.pubKey1.toBytes30()); + assertEq(returnedOwnersDataAfterUpdate[2].weight, input.pubKeyWeight1); + assertEq(ownershipMetadataAfterUpdate.thresholdWeight, newThresholdWeight); // now we add k1 owners newOwners = new address[](1); - newOwners[0] = owner1; + newOwners[0] = input.owner1; newWeights = new uint256[](1); - newWeights[0] = weight1; + newWeights[0] = input.weight1; newPubKeys = new PublicKey[](0); newPubKeyWeights = new uint256[](0); - initialThresholdWeight = pubKeyWeight1 + pubKeyWeight2 + pubKeyWeight3; - newThresholdWeight = weight1 + initialThresholdWeight; + initialThresholdWeight = input.pubKeyWeight1 + input.pubKeyWeight2 + input.pubKeyWeight3; + newThresholdWeight = input.weight1 + initialThresholdWeight; (_tOwners, _tWeights) = _mergeOwnersData(newOwners, newWeights, newPubKeys, newPubKeyWeights); vm.expectEmit(true, true, true, true); emit ThresholdUpdated(account, initialThresholdWeight, newThresholdWeight); @@ -1442,56 +1444,57 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { (returnedOwnersAfterUpdate, returnedOwnersDataAfterUpdate, ownershipMetadataAfterUpdate) = plugin.ownershipInfoOf(account); - returnedThresholdWeightAfterUpdate = ownershipMetadataAfterUpdate.thresholdWeight; assertEq(returnedOwnersAfterUpdate.length, 4); assertEq(returnedOwnersDataAfterUpdate.length, 4); // new - assertEq(returnedOwnersAfterUpdate[0], owner1.toBytes30()); - assertEq(returnedOwnersDataAfterUpdate[0].weight, weight1); + assertEq(returnedOwnersAfterUpdate[0], input.owner1.toBytes30()); + assertEq(returnedOwnersDataAfterUpdate[0].weight, input.weight1); // old - assertEq(returnedOwnersAfterUpdate[1], pubKey3.toBytes30()); - assertEq(returnedOwnersDataAfterUpdate[1].weight, pubKeyWeight3); - assertEq(returnedOwnersAfterUpdate[2], pubKey2.toBytes30()); - assertEq(returnedOwnersDataAfterUpdate[2].weight, pubKeyWeight2); - assertEq(returnedOwnersAfterUpdate[3], pubKey1.toBytes30()); - assertEq(returnedOwnersDataAfterUpdate[3].weight, pubKeyWeight1); - assertEq(returnedThresholdWeightAfterUpdate, newThresholdWeight); + assertEq(returnedOwnersAfterUpdate[1], input.pubKey3.toBytes30()); + assertEq(returnedOwnersDataAfterUpdate[1].weight, input.pubKeyWeight3); + assertEq(returnedOwnersAfterUpdate[2], input.pubKey2.toBytes30()); + assertEq(returnedOwnersDataAfterUpdate[2].weight, input.pubKeyWeight2); + assertEq(returnedOwnersAfterUpdate[3], input.pubKey1.toBytes30()); + assertEq(returnedOwnersDataAfterUpdate[3].weight, input.pubKeyWeight1); + assertEq(ownershipMetadataAfterUpdate.thresholdWeight, newThresholdWeight); } - function testFuzz_removePubKeyOnlyOwners( - PublicKey memory pubKey1, - PublicKey memory pubKey2, - PublicKey memory pubKey3, - uint256 pubKeyWeight1, - uint256 pubKeyWeight2, - uint256 pubKeyWeight3 - ) public { - vm.assume(!(pubKey1.x == 0 && pubKey1.y == 0)); - vm.assume(!(pubKey2.x == 0 && pubKey2.y == 0)); - vm.assume(!(pubKey3.x == 0 && pubKey3.y == 0)); - vm.assume(!_isSame(pubKey1, pubKey2)); - vm.assume(!_isSame(pubKey2, pubKey3)); - vm.assume(!_isSame(pubKey3, pubKey1)); + function _installPluginForAddPubKeyOnlyOwnersThenK1Owner(AddPubKeyOnlyOwnersThenK1OwnerInput memory input) + internal + { + vm.assume(!(input.pubKey1.x == 0 && input.pubKey1.y == 0)); + vm.assume(!(input.pubKey2.x == 0 && input.pubKey2.y == 0)); + vm.assume(!(input.pubKey3.x == 0 && input.pubKey3.y == 0)); + vm.assume(!_isSame(input.pubKey1, input.pubKey2)); + vm.assume(!_isSame(input.pubKey2, input.pubKey3)); + vm.assume(!_isSame(input.pubKey3, input.pubKey1)); + + vm.assume(input.owner1 != address(0)); + + vm.assume(input.pubKey1.toBytes30() != input.owner1.toBytes30()); + vm.assume(input.pubKey2.toBytes30() != input.owner1.toBytes30()); + vm.assume(input.pubKey3.toBytes30() != input.owner1.toBytes30()); - pubKeyWeight1 = bound(pubKeyWeight1, 1, _MAX_WEIGHT); - pubKeyWeight2 = bound(pubKeyWeight2, 1, _MAX_WEIGHT); - pubKeyWeight3 = bound(pubKeyWeight3, 1, _MAX_WEIGHT); + input.pubKeyWeight1 = bound(input.pubKeyWeight1, 1, _MAX_WEIGHT); + input.pubKeyWeight2 = bound(input.pubKeyWeight2, 1, _MAX_WEIGHT); + input.pubKeyWeight3 = bound(input.pubKeyWeight3, 1, _MAX_WEIGHT); + input.weight1 = bound(input.weight1, 1, _MAX_WEIGHT); + + // no k1 owners address[] memory initialOwners; uint256[] memory initialWeights; - PublicKey[] memory initialPubKeys = new PublicKey[](3); - initialPubKeys[0] = pubKey1; - initialPubKeys[1] = pubKey2; - initialPubKeys[2] = pubKey3; + PublicKey[] memory initialPubKeys = new PublicKey[](2); + initialPubKeys[0] = input.pubKey1; + initialPubKeys[1] = input.pubKey2; - uint256[] memory initialPubKeyWeights = new uint256[](3); - initialPubKeyWeights[0] = pubKeyWeight1; - initialPubKeyWeights[1] = pubKeyWeight2; - initialPubKeyWeights[2] = pubKeyWeight3; + uint256[] memory initialPubKeyWeights = new uint256[](2); + initialPubKeyWeights[0] = input.pubKeyWeight1; + initialPubKeyWeights[1] = input.pubKeyWeight2; - uint256 initialThresholdWeight = pubKeyWeight1 + pubKeyWeight2 + pubKeyWeight3; + uint256 initialThresholdWeight = input.pubKeyWeight1 + input.pubKeyWeight2; plugin.onInstall( abi.encode(initialOwners, initialWeights, initialPubKeys, initialPubKeyWeights, initialThresholdWeight) ); @@ -1500,25 +1503,24 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { OwnerData[] memory returnedOwnersData, OwnershipMetadata memory ownershipMetadata ) = plugin.ownershipInfoOf(account); - uint256 returnedThresholdWeight = ownershipMetadata.thresholdWeight; - assertEq(returnedOwners.length, 3); - assertEq(returnedOwnersData.length, 3); + assertEq(returnedOwners.length, 2); + assertEq(returnedOwnersData.length, 2); // (reverse insertion order) - assertEq(returnedOwners[0], pubKey3.toBytes30()); - assertEq(returnedOwnersData[0].weight, pubKeyWeight3); - assertEq(returnedOwners[1], pubKey2.toBytes30()); - assertEq(returnedOwnersData[1].weight, pubKeyWeight2); - assertEq(returnedOwners[2], pubKey1.toBytes30()); - assertEq(returnedOwnersData[2].weight, pubKeyWeight1); - assertEq(returnedThresholdWeight, initialThresholdWeight); + assertEq(returnedOwners[0], input.pubKey2.toBytes30()); + assertEq(returnedOwnersData[0].weight, input.pubKeyWeight2); + assertEq(returnedOwners[1], input.pubKey1.toBytes30()); + assertEq(returnedOwnersData[1].weight, input.pubKeyWeight1); + assertEq(ownershipMetadata.thresholdWeight, initialThresholdWeight); + } + function testFuzz_removePubKeyOnlyOwners(RemovePubKeyOnlyOwnersInput memory input) public { + _installPluginForRemovePubKeyOnlyOwners(input); address[] memory ownersToRemove; PublicKey[] memory pubKeysToRemove = new PublicKey[](2); - pubKeysToRemove[0] = pubKey1; - pubKeysToRemove[1] = pubKey2; - - uint256 newThresholdWeight = pubKeyWeight3; + pubKeysToRemove[0] = input.pubKey1; + pubKeysToRemove[1] = input.pubKey2; + uint256 newThresholdWeight = input.pubKeyWeight3; vm.prank(account); plugin.removeOwners(ownersToRemove, pubKeysToRemove, newThresholdWeight); @@ -1531,52 +1533,37 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { uint256 returnedThresholdWeightAfterUpdate = ownershipMetadataAfterUpdate.thresholdWeight; assertEq(returnedOwnersAfterUpdate.length, 1); assertEq(returnedWeightsAfterUpdate.length, 1); - assertEq(returnedOwnersAfterUpdate[0], pubKey3.toBytes30()); - assertEq(returnedWeightsAfterUpdate[0].weight, pubKeyWeight3); + assertEq(returnedOwnersAfterUpdate[0], input.pubKey3.toBytes30()); + assertEq(returnedWeightsAfterUpdate[0].weight, input.pubKeyWeight3); assertEq(returnedThresholdWeightAfterUpdate, newThresholdWeight); } - function testFuzz_updateMultisigWeightsPubKeyOnly( - PublicKey memory pubKey1, - PublicKey memory pubKey2, - PublicKey memory pubKey3, - uint256 weight1, - uint256 weight2, - uint256 weight3, - uint256 weight4, - uint256 weight5, - uint256 weight6 - ) public { - vm.assume(!(pubKey1.x == 0 && pubKey1.y == 0)); - vm.assume(!(pubKey2.x == 0 && pubKey2.y == 0)); - vm.assume(!(pubKey3.x == 0 && pubKey3.y == 0)); - vm.assume(!_isSame(pubKey1, pubKey2)); - vm.assume(!_isSame(pubKey2, pubKey3)); - vm.assume(!_isSame(pubKey3, pubKey1)); - - weight1 = bound(weight1, 1, _MAX_WEIGHT); - weight2 = bound(weight2, 1, _MAX_WEIGHT); - weight3 = bound(weight3, 1, _MAX_WEIGHT); - weight4 = bound(weight4, 1, _MAX_WEIGHT); - weight5 = bound(weight5, 1, _MAX_WEIGHT); - weight6 = bound(weight6, 1, _MAX_WEIGHT); + function _installPluginForRemovePubKeyOnlyOwners(RemovePubKeyOnlyOwnersInput memory input) internal { + vm.assume(!(input.pubKey1.x == 0 && input.pubKey1.y == 0)); + vm.assume(!(input.pubKey2.x == 0 && input.pubKey2.y == 0)); + vm.assume(!(input.pubKey3.x == 0 && input.pubKey3.y == 0)); + vm.assume(!_isSame(input.pubKey1, input.pubKey2)); + vm.assume(!_isSame(input.pubKey2, input.pubKey3)); + vm.assume(!_isSame(input.pubKey3, input.pubKey1)); + + input.pubKeyWeight1 = bound(input.pubKeyWeight1, 1, _MAX_WEIGHT); + input.pubKeyWeight2 = bound(input.pubKeyWeight2, 1, _MAX_WEIGHT); + input.pubKeyWeight3 = bound(input.pubKeyWeight3, 1, _MAX_WEIGHT); address[] memory initialOwners; + uint256[] memory initialWeights; PublicKey[] memory initialPubKeys = new PublicKey[](3); - initialPubKeys[0] = pubKey1; - initialPubKeys[1] = pubKey2; - initialPubKeys[2] = pubKey3; - - uint256[] memory initialWeights; + initialPubKeys[0] = input.pubKey1; + initialPubKeys[1] = input.pubKey2; + initialPubKeys[2] = input.pubKey3; uint256[] memory initialPubKeyWeights = new uint256[](3); - initialPubKeyWeights[0] = weight1; - initialPubKeyWeights[1] = weight2; - initialPubKeyWeights[2] = weight3; - - uint256 initialThresholdWeight = weight1 + weight2 + weight3; + initialPubKeyWeights[0] = input.pubKeyWeight1; + initialPubKeyWeights[1] = input.pubKeyWeight2; + initialPubKeyWeights[2] = input.pubKeyWeight3; + uint256 initialThresholdWeight = input.pubKeyWeight1 + input.pubKeyWeight2 + input.pubKeyWeight3; plugin.onInstall( abi.encode(initialOwners, initialWeights, initialPubKeys, initialPubKeyWeights, initialThresholdWeight) ); @@ -1590,21 +1577,30 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { assertEq(returnedOwners.length, 3); assertEq(returnedOwnersData.length, 3); // (reverse insertion order) - assertEq(returnedOwners[0], pubKey3.toBytes30()); - assertEq(returnedOwnersData[0].weight, weight3); - assertEq(returnedOwners[1], pubKey2.toBytes30()); - assertEq(returnedOwnersData[1].weight, weight2); - assertEq(returnedOwners[2], pubKey1.toBytes30()); - assertEq(returnedOwnersData[2].weight, weight1); + assertEq(returnedOwners[0], input.pubKey3.toBytes30()); + assertEq(returnedOwnersData[0].weight, input.pubKeyWeight3); + assertEq(returnedOwners[1], input.pubKey2.toBytes30()); + assertEq(returnedOwnersData[1].weight, input.pubKeyWeight2); + assertEq(returnedOwners[2], input.pubKey1.toBytes30()); + assertEq(returnedOwnersData[2].weight, input.pubKeyWeight1); assertEq(returnedThresholdWeight, initialThresholdWeight); + } + function testFuzz_updateMultisigWeightsPubKeyOnly(UpdateMultisigWeightsPubKeyOnlyInput memory input) public { + address[] memory initialOwners; + PublicKey[] memory initialPubKeys = new PublicKey[](3); + initialPubKeys[0] = input.pubKey1; + initialPubKeys[1] = input.pubKey2; + initialPubKeys[2] = input.pubKey3; + + _installPluginForUpdateMultisigWeightsPubKeyOnly(input, initialOwners, initialPubKeys); uint256[] memory newWeights; uint256[] memory newPubKeyWeights = new uint256[](3); - newPubKeyWeights[0] = weight4; - newPubKeyWeights[1] = weight5; - newPubKeyWeights[2] = weight6; + newPubKeyWeights[0] = input.weight4; + newPubKeyWeights[1] = input.weight5; + newPubKeyWeights[2] = input.weight6; - uint256 newThresholdWeight = weight4 + weight5 + weight6; + uint256 newThresholdWeight = input.weight4 + input.weight5 + input.weight6; vm.prank(account); plugin.updateMultisigWeights(initialOwners, newWeights, initialPubKeys, newPubKeyWeights, newThresholdWeight); @@ -1617,15 +1613,63 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { uint256 returnedThresholdWeightAfterUpdate = ownershipMetadataAfterUpdate.thresholdWeight; assertEq(returnedOwnersAfterUpdate.length, 3); assertEq(returnedOwnersDataAfterUpdate.length, 3); - assertEq(returnedOwnersAfterUpdate[0], pubKey3.toBytes30()); - assertEq(returnedOwnersDataAfterUpdate[0].weight, weight6); - assertEq(returnedOwnersAfterUpdate[1], pubKey2.toBytes30()); - assertEq(returnedOwnersDataAfterUpdate[1].weight, weight5); - assertEq(returnedOwnersAfterUpdate[2], pubKey1.toBytes30()); - assertEq(returnedOwnersDataAfterUpdate[2].weight, weight4); + assertEq(returnedOwnersAfterUpdate[0], input.pubKey3.toBytes30()); + assertEq(returnedOwnersDataAfterUpdate[0].weight, input.weight6); + assertEq(returnedOwnersAfterUpdate[1], input.pubKey2.toBytes30()); + assertEq(returnedOwnersDataAfterUpdate[1].weight, input.weight5); + assertEq(returnedOwnersAfterUpdate[2], input.pubKey1.toBytes30()); + assertEq(returnedOwnersDataAfterUpdate[2].weight, input.weight4); assertEq(returnedThresholdWeightAfterUpdate, newThresholdWeight); } + function _installPluginForUpdateMultisigWeightsPubKeyOnly( + UpdateMultisigWeightsPubKeyOnlyInput memory input, + address[] memory initialOwners, + PublicKey[] memory initialPubKeys + ) internal { + vm.assume(!(input.pubKey1.x == 0 && input.pubKey1.y == 0)); + vm.assume(!(input.pubKey2.x == 0 && input.pubKey2.y == 0)); + vm.assume(!(input.pubKey3.x == 0 && input.pubKey3.y == 0)); + vm.assume(!_isSame(input.pubKey1, input.pubKey2)); + vm.assume(!_isSame(input.pubKey2, input.pubKey3)); + vm.assume(!_isSame(input.pubKey3, input.pubKey1)); + + input.weight1 = bound(input.weight1, 1, _MAX_WEIGHT); + input.weight2 = bound(input.weight2, 1, _MAX_WEIGHT); + input.weight3 = bound(input.weight3, 1, _MAX_WEIGHT); + input.weight4 = bound(input.weight4, 1, _MAX_WEIGHT); + input.weight5 = bound(input.weight5, 1, _MAX_WEIGHT); + input.weight6 = bound(input.weight6, 1, _MAX_WEIGHT); + + uint256[] memory initialWeights; + uint256[] memory initialPubKeyWeights = new uint256[](3); + initialPubKeyWeights[0] = input.weight1; + initialPubKeyWeights[1] = input.weight2; + initialPubKeyWeights[2] = input.weight3; + + uint256 initialThresholdWeight = input.weight1 + input.weight2 + input.weight3; + plugin.onInstall( + abi.encode(initialOwners, initialWeights, initialPubKeys, initialPubKeyWeights, initialThresholdWeight) + ); + ( + bytes30[] memory returnedOwners, + OwnerData[] memory returnedOwnersData, + OwnershipMetadata memory ownershipMetadata + ) = plugin.ownershipInfoOf(account); + uint256 returnedThresholdWeight = ownershipMetadata.thresholdWeight; + + assertEq(returnedOwners.length, 3); + assertEq(returnedOwnersData.length, 3); + // (reverse insertion order) + assertEq(returnedOwners[0], input.pubKey3.toBytes30()); + assertEq(returnedOwnersData[0].weight, input.weight3); + assertEq(returnedOwners[1], input.pubKey2.toBytes30()); + assertEq(returnedOwnersData[1].weight, input.weight2); + assertEq(returnedOwners[2], input.pubKey1.toBytes30()); + assertEq(returnedOwnersData[2].weight, input.weight1); + assertEq(returnedThresholdWeight, initialThresholdWeight); + } + function testFuzz_isValidSignature_eoaOwner(string memory salt, bytes memory message) public { _install(); @@ -1633,7 +1677,7 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { (address signer, uint256 signerPrivateKey) = makeAddrAndKey(salt); bytes32 digest = keccak256(message); // the caller should sign the wrapped digest - bytes32 wrappedDigest = plugin.getReplaySafeMessageHash(address(account), digest); + wrappedDigest = plugin.getReplaySafeMessageHash(address(account), digest); bytes memory signature = signMessage(vm, signerPrivateKey, wrappedDigest); address[] memory ownersToAdd1 = new address[](1); @@ -1705,7 +1749,7 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { Owner memory contractOwner = _createContractOwner(seed); bytes32 digest = keccak256(message); // the caller should sign the wrapped digest - bytes32 wrappedDigest = plugin.getReplaySafeMessageHash(address(account), digest); + wrappedDigest = plugin.getReplaySafeMessageHash(address(account), digest); bytes memory signerSig = signMessage(vm, contractOwner.signerWallet.privateKey, wrappedDigest); address[] memory ownersToAdd1 = new address[](1); @@ -1763,8 +1807,7 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { function testFuzz_isValidSignature_p256Owner(bytes memory message) public { _install(); - bytes32 digest = keccak256(message); - bytes32 wrappedDigest = plugin.getReplaySafeMessageHash(address(account), digest); + wrappedDigest = plugin.getReplaySafeMessageHash(address(account), keccak256(message)); WebAuthnSigDynamicPart memory webAuthnSigDynamicPart; webAuthnSigDynamicPart.webAuthnData = _getWebAuthnData(wrappedDigest); bytes32 digestToSign = _getWebAuthnMessageHash(webAuthnSigDynamicPart.webAuthnData); @@ -1772,24 +1815,22 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { webAuthnSigDynamicPart.r = r; webAuthnSigDynamicPart.s = s; // x || dynamic pos || sig type || length of bytes || sig data bytes - uint256 dynamicPos = 65; // 1 constant part bytes memory sigBytes = abi.encode(webAuthnSigDynamicPart); PublicKey[] memory pubKeyOwnersToAdd1 = new PublicKey[](1); pubKeyOwnersToAdd1[0] = PublicKey(passkeyPublicKeyX, passkeyPublicKeyY); bytes32 pubKeyId = bytes32(bytes.concat(bytes2(0), plugin.getOwnerId(pubKeyOwnersToAdd1[0]))); console.logString("pubKeyId:"); console.logBytes32(pubKeyId); - bytes memory p256Sig = abi.encodePacked(pubKeyId, dynamicPos, uint8(2), sigBytes.length, sigBytes); + bytes memory p256Sig = abi.encodePacked(pubKeyId, uint256(65), uint8(2), sigBytes.length, sigBytes); - uint256 weightToAdd1 = 9; uint256[] memory weightsToAdd1 = new uint256[](1); - weightsToAdd1[0] = weightToAdd1; + weightsToAdd1[0] = 9; (bool isOwner,) = plugin.isOwnerOf(account, pubKeyOwnersToAdd1[0]); if (!isOwner) { // sig check should fail vm.prank(account); - assertEq(EIP1271_INVALID_SIGNATURE, plugin.isValidSignature(digest, p256Sig)); + assertEq(EIP1271_INVALID_SIGNATURE, plugin.isValidSignature(keccak256(message), p256Sig)); vm.prank(account); plugin.addOwners(new address[](0), new uint256[](0), pubKeyOwnersToAdd1, weightsToAdd1, 0); @@ -1798,13 +1839,14 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { (isOwner,) = plugin.isOwnerOf(account, pubKeyOwnersToAdd1[0]); assertTrue(isOwner); vm.prank(account); - assertEq(EIP1271_VALID_SIGNATURE, plugin.isValidSignature(digest, p256Sig)); + assertEq(EIP1271_VALID_SIGNATURE, plugin.isValidSignature(keccak256(message), p256Sig)); } function testIsValidSignature_p256Owner_emptyDigest() public { _install(); - bytes32 digest = 0x0000000000000000000000000000000000000000000000000000000000000000; - bytes32 wrappedDigest = plugin.getReplaySafeMessageHash(address(account), digest); + wrappedDigest = plugin.getReplaySafeMessageHash( + address(account), 0x0000000000000000000000000000000000000000000000000000000000000000 + ); WebAuthnSigDynamicPart memory webAuthnSigDynamicPart; webAuthnSigDynamicPart.webAuthnData = _getWebAuthnData(wrappedDigest); bytes32 digestToSign = _getWebAuthnMessageHash(webAuthnSigDynamicPart.webAuthnData); @@ -1812,12 +1854,12 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { webAuthnSigDynamicPart.r = r; webAuthnSigDynamicPart.s = s; // x || dynamic pos || sig type || length of bytes || sig data bytes - uint256 dynamicPos = 65; // 1 constant part + // uint256 dynamicPos = 65; // 1 constant part bytes memory sigBytes = abi.encode(webAuthnSigDynamicPart); PublicKey[] memory pubKeyOwnersToAdd1 = new PublicKey[](1); pubKeyOwnersToAdd1[0] = PublicKey(passkeyPublicKeyX, passkeyPublicKeyY); bytes32 pubKeyId = bytes32(bytes.concat(bytes2(0), plugin.getOwnerId(pubKeyOwnersToAdd1[0]))); - bytes memory p256Sig = abi.encodePacked(pubKeyId, dynamicPos, uint8(2), sigBytes.length, sigBytes); + bytes memory p256Sig = abi.encodePacked(pubKeyId, uint256(65), uint8(2), sigBytes.length, sigBytes); uint256 weightToAdd1 = 9; uint256[] memory weightsToAdd1 = new uint256[](1); @@ -1825,116 +1867,52 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { vm.prank(account); plugin.addOwners(new address[](0), new uint256[](0), pubKeyOwnersToAdd1, weightsToAdd1, 0); vm.prank(account); - assertEq(EIP1271_VALID_SIGNATURE, plugin.isValidSignature(digest, p256Sig)); + assertEq( + EIP1271_VALID_SIGNATURE, + plugin.isValidSignature(0x0000000000000000000000000000000000000000000000000000000000000000, p256Sig) + ); } // mixed signature types - function testFuzz_isValidSignature_mixedSigTypes(uint256 k, uint256 n, bytes memory message) public { + function testFuzz_isValidSignature_mixedSigTypes(MultisigInput memory input, bytes memory message) public { // 1 < n < 10 - n %= 11; - vm.assume(n > 0); + input.n %= 11; + vm.assume(input.n > 0); // 1 < k < n - k %= 11; - k %= n; - vm.assume(k > 0); + input.k %= 11; + input.k %= input.n; + vm.assume(input.k > 0); - bytes32 digest = keccak256(message); // for k1 signer - bytes32 wrappedDigest = plugin.getReplaySafeMessageHash(address(account), digest); + wrappedDigest = plugin.getReplaySafeMessageHash(address(account), keccak256(message)); WebAuthnSigDynamicPart memory webAuthnSigDynamicPart; // for r1 signer webAuthnSigDynamicPart.webAuthnData = _getWebAuthnData(wrappedDigest); - bytes32 webauthnDigest = _getWebAuthnMessageHash(webAuthnSigDynamicPart.webAuthnData); // get all owners - Owner[] memory owners = new Owner[](n); - uint256 lenOfK1Signers; - uint256 lenOfR1Signers; - for (uint256 i = 0; i < n; i++) { - uint256 seed = k + n + i; - if (seed % 3 == 2) { - lenOfR1Signers++; - } else { - lenOfK1Signers++; - } - } - console.log("lenOfK1Signers: ", lenOfK1Signers); - console.log("lenOfR1Signers: ", lenOfR1Signers); - assertEq(lenOfK1Signers + lenOfR1Signers, n); - address[] memory initialOwners = new address[](lenOfK1Signers); - uint256[] memory initialWeights = new uint256[](lenOfK1Signers); - PublicKey[] memory initialPubKeyOwners = new PublicKey[](lenOfR1Signers); - uint256[] memory initialPubKeyWeights = new uint256[](lenOfR1Signers); - uint256 numOfK1Signers; - uint256 numOfR1Signers; + Owner[] memory owners = new Owner[](input.n); + uint256[] memory lenOfSigners = _calculateLenOfSigners(input.n, input.k); + address[] memory initialOwners = new address[](lenOfSigners[0]); + uint256[] memory initialWeights = new uint256[](lenOfSigners[0]); + PublicKey[] memory initialPubKeyOwners = new PublicKey[](lenOfSigners[1]); + uint256[] memory initialPubKeyWeights = new uint256[](lenOfSigners[1]); // load pre generated r1 keys list which has more keys than we need TestKey[] memory testR1Keys = _loadP256Keys(); - for (uint256 i = 0; i < n; i++) { - uint256 seed = k + n + i; - if (seed % 3 == 0) { - // contract owners - console.logString("we have a contract owner.."); - owners[i] = _createContractOwner(seed); - owners[i].sigType = 0; - initialOwners[numOfK1Signers] = toAddress(owners[i].owner); - initialWeights[numOfK1Signers] = 1; - numOfK1Signers++; - } else if (seed % 3 == 1) { - // eoa owners - console.logString("we have an eoa owner.."); - VmSafe.Wallet memory signerWallet; - (signerWallet.addr, signerWallet.privateKey) = makeAddrAndKey(string(abi.encodePacked(seed))); - owners[i] = Owner({owner: signerWallet.addr.toBytes30(), signerWallet: signerWallet, sigType: 27}); - initialOwners[numOfK1Signers] = signerWallet.addr; - initialWeights[numOfK1Signers] = 1; - numOfK1Signers++; - } else { - // r1 owners - console.logString("we have a r1 owner.."); - VmSafe.Wallet memory signerWallet; - signerWallet.privateKey = testR1Keys[numOfR1Signers].privateKey; - signerWallet.publicKeyX = testR1Keys[numOfR1Signers].publicKeyX; - signerWallet.publicKeyY = testR1Keys[numOfR1Signers].publicKeyY; - // for sorting - bytes30 ownerBytes = PublicKeyLib.toBytes30(signerWallet.publicKeyX, signerWallet.publicKeyY); - owners[i] = Owner({owner: ownerBytes, signerWallet: signerWallet, sigType: 2}); - initialPubKeyOwners[numOfR1Signers] = PublicKey(signerWallet.publicKeyX, signerWallet.publicKeyY); - initialPubKeyWeights[numOfR1Signers] = 1; - numOfR1Signers++; - } - } - - // sort owners using address - uint256 minIdx; - for (uint256 i = 0; i < n; i++) { - minIdx = i; - for (uint256 j = i; j < n; j++) { - if (owners[j].owner < owners[minIdx].owner) { - minIdx = j; - } - } - (owners[i], owners[minIdx]) = (owners[minIdx], owners[i]); - } + _loadOwners(input, testR1Keys, owners, initialOwners, initialWeights, initialPubKeyOwners, initialPubKeyWeights); - // initialThresholdWeight is equivalent to k because every signer has weight 1 - vm.prank(account); - plugin.onInstall(abi.encode(initialOwners, initialWeights, initialPubKeyOwners, initialPubKeyWeights, k)); bytes memory sigDynamicParts = bytes(""); - uint256 offset = k * 65; // start after constant part + uint256 offset = input.k * 65; // start after constant part bytes memory signature; // constant + dynamic - for (uint256 i = 0; i < k; i++) { - uint8 v; - bytes32 r; - bytes32 s; + for (uint256 i = 0; i < input.k; i++) { if (owners[i].sigType == 27) { console.logString("eoa owner signs.."); - (v, r, s) = vm.sign(owners[i].signerWallet.privateKey, wrappedDigest); // constant part only for EOA - signature = abi.encodePacked(signature, abi.encodePacked(r, s, v)); + signature = + abi.encodePacked(signature, signMessage(vm, owners[i].signerWallet.privateKey, wrappedDigest)); } else if (owners[i].sigType == 0) { console.logString("contract owner signs.."); - (v, r, s) = vm.sign(owners[i].signerWallet.privateKey, wrappedDigest); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(owners[i].signerWallet.privateKey, wrappedDigest); signature = abi.encodePacked(signature, abi.encode(toAddress(owners[i].owner)), uint256(offset), uint8(0)); // dynamic part because offset was set to the end of constant part initially @@ -1943,15 +1921,10 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { sigDynamicParts = abi.encodePacked(sigDynamicParts, uint256(65), r, s, v); } else { console.logString("r1 owner signs.."); - (r, s) = vm.signP256(owners[i].signerWallet.privateKey, webauthnDigest); - uint256 ur = uint256(r); - uint256 us = uint256(s); - if (us > _P256_N_DIV_2) { - us = FCL_Elliptic_ZZ.n - us; - } - v = 2; - webAuthnSigDynamicPart.r = ur; - webAuthnSigDynamicPart.s = us; + (webAuthnSigDynamicPart.r, webAuthnSigDynamicPart.s) = signP256Message( + vm, owners[i].signerWallet.privateKey, _getWebAuthnMessageHash(webAuthnSigDynamicPart.webAuthnData) + ); + uint8 v = 2; PublicKey memory pubKey = PublicKey({x: owners[i].signerWallet.publicKeyX, y: owners[i].signerWallet.publicKeyY}); bytes32 pubKeyId = bytes32(bytes.concat(bytes2(0), plugin.getOwnerId(pubKey))); @@ -1969,7 +1942,7 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { signature = abi.encodePacked(signature, sigDynamicParts); vm.prank(account); - assertEq(EIP1271_VALID_SIGNATURE, plugin.isValidSignature(digest, signature)); + assertEq(EIP1271_VALID_SIGNATURE, plugin.isValidSignature(keccak256(message), signature)); } function testIsValidSignature_invalidContractOwner() public { @@ -1979,7 +1952,7 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { Owner memory contractOwner = _createContractOwner(123); bytes32 digest = keccak256(message); // the caller should sign the wrapped digest - bytes32 wrappedDigest = plugin.getReplaySafeMessageHash(address(account), digest); + wrappedDigest = plugin.getReplaySafeMessageHash(address(account), digest); bytes memory signerSig = signMessage(vm, contractOwner.signerWallet.privateKey, wrappedDigest); address[] memory ownersToAdd1 = new address[](1); @@ -2008,7 +1981,7 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { (, uint256 signerPrivateKey) = makeAddrAndKey("testIsValidSignature_invalidEOAOwner"); bytes32 digest = keccak256(message); // the caller should sign the wrapped digest - bytes32 wrappedDigest = plugin.getReplaySafeMessageHash(address(account), digest); + wrappedDigest = plugin.getReplaySafeMessageHash(address(account), digest); address anotherOwner = vm.addr(1); address[] memory ownersToAdd1 = new address[](1); @@ -2044,15 +2017,11 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { } function testIsValidSignature_remainingSigTooShort() public { - uint256 seed = 0; - bytes32 digest = 0x0000000000000000000000000000000000000000000000000000000000000000; - (address eoaOwner, uint256 privateKey) = makeAddrAndKey(string(abi.encodePacked(seed))); - // 1. install with two owners, so contract owner has insufficient weight to pass threshold address[] memory listOfTwoOwners = new address[](2); - listOfTwoOwners[0] = eoaOwner; listOfTwoOwners[1] = ownerTwo; - + uint256 privateKey; + (listOfTwoOwners[0], privateKey) = makeAddrAndKey(string(abi.encodePacked(uint256(0)))); uint256[] memory listOfTwoWeights = new uint256[](2); listOfTwoWeights[0] = weightOne; // 100 listOfTwoWeights[1] = weightTwo; // 101 @@ -2062,7 +2031,7 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { plugin.onInstall(abi.encode(listOfTwoOwners, listOfTwoWeights, new PublicKey[](0), new uint256[](0), threshold)); // 2. create a valid signature for installed owner - bytes32 messageDigest = plugin.getReplaySafeMessageHash(address(account), digest); + bytes32 messageDigest = plugin.getReplaySafeMessageHash(address(account), bytes32(0)); (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, messageDigest); bytes memory sig = abi.encodePacked(r, s, v); @@ -2075,13 +2044,18 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { assertEq(sigWithFooAppended.length, 68); vm.prank(account); - (bool success, uint256 firstFailure) = - plugin.checkNSignatures(messageDigest, messageDigest, account, sigWithFooAppended); + IWeightedMultisigPlugin.CheckNSignatureInput memory input = IWeightedMultisigPlugin.CheckNSignatureInput({ + actualDigest: messageDigest, + minimalDigest: messageDigest, + account: account, + signatures: sigWithFooAppended + }); + (bool success, uint256 firstFailure) = plugin.checkNSignatures(input); assertEq(success, false); assertEq(firstFailure, 1); vm.prank(account); - assertEq(EIP1271_INVALID_SIGNATURE, plugin.isValidSignature(digest, sigWithFooAppended)); + assertEq(EIP1271_INVALID_SIGNATURE, plugin.isValidSignature(bytes32(0), sigWithFooAppended)); } function testIsValidSignature_revertsOnTooHighV() public { @@ -2112,7 +2086,13 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { bytes32 messageDigest = bytes32("foo"); vm.prank(account); vm.expectRevert(ECDSAInvalidSignature.selector); - plugin.checkNSignatures(messageDigest, messageDigest, account, INVALID_ECDSA_SIGNATURE); + IWeightedMultisigPlugin.CheckNSignatureInput memory input = IWeightedMultisigPlugin.CheckNSignatureInput({ + actualDigest: messageDigest, + minimalDigest: messageDigest, + account: account, + signatures: INVALID_ECDSA_SIGNATURE + }); + plugin.checkNSignatures(input); } function testCheckNSignatures_revertsECDSASignatureOnHighS() public { @@ -2124,7 +2104,13 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { bytes32 messageDigest = 0xb94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9; vm.prank(account); vm.expectRevert(ECDSAInvalidSignature.selector); - plugin.checkNSignatures(messageDigest, messageDigest, account, INVALID_ECDSA_SIGNATURE); + IWeightedMultisigPlugin.CheckNSignatureInput memory input = IWeightedMultisigPlugin.CheckNSignatureInput({ + actualDigest: messageDigest, + minimalDigest: messageDigest, + account: account, + signatures: INVALID_ECDSA_SIGNATURE + }); + plugin.checkNSignatures(input); } function testFuzz_checkNSignatures_failsOnIsValidERC1271SignatureNow(uint256 seed1, uint256 seed2, bytes32 digest) @@ -2144,7 +2130,13 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { abi.encodePacked(abi.encode(toAddress(newOwner1.owner)), uint256(65), uint8(0), uint256(65), r, s, v); vm.prank(account); - (bool success, uint256 firstFailure) = plugin.checkNSignatures(messageDigest, messageDigest, account, sig); + IWeightedMultisigPlugin.CheckNSignatureInput memory input = IWeightedMultisigPlugin.CheckNSignatureInput({ + actualDigest: messageDigest, + minimalDigest: messageDigest, + account: account, + signatures: sig + }); + (bool success, uint256 firstFailure) = plugin.checkNSignatures(input); assertEq(success, false); assertEq(firstFailure, 0); } @@ -2259,13 +2251,12 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { webAuthnSigDynamicPart.r = r; webAuthnSigDynamicPart.s = s; // x || dynamic pos || sig type || length of bytes || sig data bytes - uint256 dynamicPos = 65; // 1 constant part bytes memory sigBytes = abi.encode(webAuthnSigDynamicPart); PublicKey[] memory pubKeyOwnersToAdd1 = new PublicKey[](1); pubKeyOwnersToAdd1[0] = PublicKey(passkeyPublicKeyX, passkeyPublicKeyY); bytes32 pubKeyId = bytes32(bytes.concat(bytes2(0), plugin.getOwnerId(pubKeyOwnersToAdd1[0]))); // actual digest sigType is 34 (2 + 32) - userOp.signature = abi.encodePacked(pubKeyId, dynamicPos, uint8(34), sigBytes.length, sigBytes); + userOp.signature = abi.encodePacked(pubKeyId, uint256(65), uint8(34), sigBytes.length, sigBytes); uint256 weightToAdd1 = 9; uint256[] memory weightsToAdd1 = new uint256[](1); weightsToAdd1[0] = weightToAdd1; @@ -2297,15 +2288,17 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { } // mixed signature types in userOp.signature - function testFuzz_userOpValidation_mixedSigTypes(uint256 k, uint256 n, PackedUserOperation memory userOp) public { + function testFuzz_userOpValidation_mixedSigTypes(MultisigInput memory input, PackedUserOperation memory userOp) + public + { // 1 < n < 10 - n %= 11; - vm.assume(n > 0); + input.n %= 11; + vm.assume(input.n > 0); // 1 < k < n - k %= 11; - k %= n; - vm.assume(k > 0); + input.k %= 11; + input.k %= input.n; + vm.assume(input.k > 0); // full userOp hash // for k1 signer @@ -2321,54 +2314,217 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { userOp.gasFees = ZERO_BYTES32; userOp.paymasterAndData = ""; - // minimal userOp hash - // for k1 signer + // get all owners + Owner[] memory owners = new Owner[](input.n); + uint256[] memory lenOfSigners = _calculateLenOfSigners(input.n, input.k); + address[] memory initialOwners = new address[](lenOfSigners[0]); + uint256[] memory initialWeights = new uint256[](lenOfSigners[0]); + PublicKey[] memory initialPubKeyOwners = new PublicKey[](lenOfSigners[1]); + uint256[] memory initialPubKeyWeights = new uint256[](lenOfSigners[1]); + + // load pre generated r1 keys list which has more keys than we need + TestKey[] memory testR1Keys = _loadP256Keys(); + _loadOwners(input, testR1Keys, owners, initialOwners, initialWeights, initialPubKeyOwners, initialPubKeyWeights); + + _signSignatures( + input, userOp, owners, fullUserOpHash, webauthnDigestForFullUserOp, webAuthnSigDynamicPartForFullUserOp + ); + vm.prank(account); + // sig check should pass + assertEq( + plugin.userOpValidationFunction( + uint8(BaseMultisigPlugin.FunctionId.USER_OP_VALIDATION_OWNER), userOp, fullUserOpHash + ), + SIG_VALIDATION_SUCCEEDED + ); + } + + function _signSignatures( + MultisigInput memory input, + PackedUserOperation memory userOp, + Owner[] memory owners, + bytes32 fullUserOpHash, + bytes32 webauthnDigestForFullUserOp, + WebAuthnSigDynamicPart memory webAuthnSigDynamicPartForFullUserOp + ) internal view { + userOp.signature = bytes(""); // constant + dynamic bytes32 minimalUserOpHash = entryPoint.getUserOpHash(userOp); WebAuthnSigDynamicPart memory webAuthnSigDynamicPartForMinimalUserOp; - // for r1 signer webAuthnSigDynamicPartForMinimalUserOp.webAuthnData = _getWebAuthnData(minimalUserOpHash.toEthSignedMessageHash()); - bytes32 webauthnDigestForMinimalUserOp = - _getWebAuthnMessageHash(webAuthnSigDynamicPartForMinimalUserOp.webAuthnData); + bytes30 ownerSignFullUserOp; + if (fullUserOpHash != minimalUserOpHash) { + ownerSignFullUserOp = owners[input.n % input.k].owner; + } + bytes memory sigDynamicParts = bytes(""); + uint256[] memory offset = new uint256[](1); + offset[0] = input.k * 65; // start after constant part; + for (uint256 i = 0; i < input.k; i++) { + sigDynamicParts = abi.encodePacked( + sigDynamicParts, + _SignIndividualOwnerSignature( + offset, + userOp, + owners[i], + fullUserOpHash, + minimalUserOpHash, + webauthnDigestForFullUserOp, + webAuthnSigDynamicPartForFullUserOp, + webAuthnSigDynamicPartForMinimalUserOp, + ownerSignFullUserOp + ) + ); + } + userOp.signature = abi.encodePacked(userOp.signature, sigDynamicParts); + } - // get all owners - Owner[] memory owners = new Owner[](n); - uint256 lenOfK1Signers; - uint256 lenOfR1Signers; + function _SignIndividualOwnerSignature( + uint256[] memory offset, + PackedUserOperation memory userOp, + Owner memory owner, + bytes32 fullUserOpHash, + bytes32 minimalUserOpHash, + bytes32 webauthnDigestForFullUserOp, + WebAuthnSigDynamicPart memory webAuthnSigDynamicPartForFullUserOp, + WebAuthnSigDynamicPart memory webAuthnSigDynamicPartForMinimalUserOp, + bytes30 ownerSignFullUserOp + ) internal view returns (bytes memory sigDynamicParts) { + sigDynamicParts = bytes(""); + if (owner.sigType == 27) { + console.logString("eoa owner signs.."); + bytes memory signed = _signEOAOwner(owner, fullUserOpHash, minimalUserOpHash, ownerSignFullUserOp); + userOp.signature = abi.encodePacked(userOp.signature, signed); + } else if (owner.sigType == 0) { + console.logString("contract owner signs.."); + uint8 v; + (v, sigDynamicParts) = _signContractOwner(owner, fullUserOpHash, minimalUserOpHash, ownerSignFullUserOp); + userOp.signature = + abi.encodePacked(userOp.signature, abi.encode(toAddress(owner.owner)), uint256(offset[0]), v); + offset[0] += 97; // 65 (k1 sig length) + 32 (length of sig) + } else { + console.logString("r1 owner signs.."); + bytes memory sigBytes; + uint8 v; + (v, sigBytes, sigDynamicParts) = _signR1Owner( + owner, + webauthnDigestForFullUserOp, + webAuthnSigDynamicPartForFullUserOp, + webAuthnSigDynamicPartForMinimalUserOp, + ownerSignFullUserOp + ); + PublicKey memory pubKey = PublicKey({x: owner.signerWallet.publicKeyX, y: owner.signerWallet.publicKeyY}); + bytes32 pubKeyId = bytes32(bytes.concat(bytes2(0), plugin.getOwnerId(pubKey))); + userOp.signature = abi.encodePacked(userOp.signature, pubKeyId, uint256(offset[0]), v); + offset[0] += (32 + sigBytes.length); + } + return sigDynamicParts; + } + + function _signEOAOwner( + Owner memory owner, + bytes32 fullUserOpHash, + bytes32 minimalUserOpHash, + bytes30 ownerSignFullUserOp + ) internal pure returns (bytes memory signed) { + bytes32 r; + bytes32 s; + uint8 v; + if (owner.owner == ownerSignFullUserOp) { + (v, r, s) = vm.sign(owner.signerWallet.privateKey, fullUserOpHash.toEthSignedMessageHash()); + v += 32; + } else { + (v, r, s) = vm.sign(owner.signerWallet.privateKey, minimalUserOpHash.toEthSignedMessageHash()); + } + return abi.encodePacked(r, s, v); + } + + function _signContractOwner( + Owner memory owner, + bytes32 fullUserOpHash, + bytes32 minimalUserOpHash, + bytes30 ownerSignFullUserOp + ) internal pure returns (uint8 v, bytes memory sigDynamicParts) { + bytes32 r; + bytes32 s; + if (owner.owner == ownerSignFullUserOp) { + (v, r, s) = vm.sign(owner.signerWallet.privateKey, fullUserOpHash.toEthSignedMessageHash()); + sigDynamicParts = abi.encodePacked(uint256(65), r, s, v); + v = 32; // 0 + 32 + } else { + (v, r, s) = vm.sign(owner.signerWallet.privateKey, minimalUserOpHash.toEthSignedMessageHash()); + sigDynamicParts = abi.encodePacked(uint256(65), r, s, v); + v = 0; + } + return (v, sigDynamicParts); + } + + function _signR1Owner( + Owner memory owner, + bytes32 webauthnDigestForFullUserOp, + WebAuthnSigDynamicPart memory webAuthnSigDynamicPartForFullUserOp, + WebAuthnSigDynamicPart memory webAuthnSigDynamicPartForMinimalUserOp, + bytes30 ownerSignFullUserOp + ) internal pure returns (uint8 v, bytes memory sigBytes, bytes memory sigDynamicParts) { + if (owner.owner == ownerSignFullUserOp) { + (webAuthnSigDynamicPartForFullUserOp.r, webAuthnSigDynamicPartForFullUserOp.s) = + signP256Message(vm, owner.signerWallet.privateKey, webauthnDigestForFullUserOp); + v = 34; // 2 + 32 + sigBytes = abi.encode(webAuthnSigDynamicPartForFullUserOp); + } else { + (webAuthnSigDynamicPartForMinimalUserOp.r, webAuthnSigDynamicPartForMinimalUserOp.s) = signP256Message( + vm, + owner.signerWallet.privateKey, + _getWebAuthnMessageHash(webAuthnSigDynamicPartForMinimalUserOp.webAuthnData) + ); + v = 2; + sigBytes = abi.encode(webAuthnSigDynamicPartForMinimalUserOp); + } + // length of bytes || sig data bytes + sigDynamicParts = abi.encodePacked(uint256(sigBytes.length), sigBytes); + return (v, sigBytes, sigDynamicParts); + } + + function _calculateLenOfSigners(uint256 n, uint256 k) internal pure returns (uint256[] memory) { + uint256[] memory lenOfSigners = new uint256[](2); // 0: lenOfK1Signers 1: lenOfR1Signers for (uint256 i = 0; i < n; i++) { - uint256 seed = k + n + i; - if (seed % 3 == 2) { - lenOfR1Signers++; + if ((k + n + i) % 3 == 2) { + lenOfSigners[1]++; } else { - lenOfK1Signers++; + lenOfSigners[0]++; } } - console.log("lenOfK1Signers: ", lenOfK1Signers); - console.log("lenOfR1Signers: ", lenOfR1Signers); - assertEq(lenOfK1Signers + lenOfR1Signers, n); - address[] memory initialOwners = new address[](lenOfK1Signers); - uint256[] memory initialWeights = new uint256[](lenOfK1Signers); - PublicKey[] memory initialPubKeyOwners = new PublicKey[](lenOfR1Signers); - uint256[] memory initialPubKeyWeights = new uint256[](lenOfR1Signers); + console.log("lenOfK1Signers: ", lenOfSigners[0]); + console.log("lenOfR1Signers: ", lenOfSigners[1]); + assertEq(lenOfSigners[0] + lenOfSigners[1], n); + return lenOfSigners; + } + + function _loadOwners( + MultisigInput memory input, + TestKey[] memory testR1Keys, + Owner[] memory owners, + address[] memory initialOwners, + uint256[] memory initialWeights, + PublicKey[] memory initialPubKeyOwners, + uint256[] memory initialPubKeyWeights + ) internal { uint256 numOfK1Signers; uint256 numOfR1Signers; - // load pre generated r1 keys list which has more keys than we need - TestKey[] memory testR1Keys = _loadP256Keys(); - for (uint256 i = 0; i < n; i++) { - uint256 seed = k + n + i; - if (seed % 3 == 0) { + for (uint256 i = 0; i < input.n; i++) { + if ((input.k + input.n + i) % 3 == 0) { // contract owners console.logString("we have a contract owner.."); - owners[i] = _createContractOwner(seed); + owners[i] = _createContractOwner((input.k + input.n + i)); owners[i].sigType = 0; initialOwners[numOfK1Signers] = toAddress(owners[i].owner); initialWeights[numOfK1Signers] = 1; numOfK1Signers++; - } else if (seed % 3 == 1) { + } else if ((input.k + input.n + i) % 3 == 1) { // eoa owners console.logString("we have an eoa owner.."); VmSafe.Wallet memory signerWallet; - (signerWallet.addr, signerWallet.privateKey) = makeAddrAndKey(string(abi.encodePacked(seed))); + (signerWallet.addr, signerWallet.privateKey) = + makeAddrAndKey(string(abi.encodePacked((input.k + input.n + i)))); owners[i] = Owner({owner: signerWallet.addr.toBytes30(), signerWallet: signerWallet, sigType: 27}); initialOwners[numOfK1Signers] = signerWallet.addr; initialWeights[numOfK1Signers] = 1; @@ -2390,6 +2546,14 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { } // sort owners using address + _sortOwnersByAddress(owners, input.n); + + // initialThresholdWeight is equivalent to k because every signer has weight 1 + vm.prank(account); + plugin.onInstall(abi.encode(initialOwners, initialWeights, initialPubKeyOwners, initialPubKeyWeights, input.k)); + } + + function _sortOwnersByAddress(Owner[] memory owners, uint256 n) internal pure { uint256 minIdx; for (uint256 i = 0; i < n; i++) { minIdx = i; @@ -2400,109 +2564,6 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { } (owners[i], owners[minIdx]) = (owners[minIdx], owners[i]); } - - // grab a ~random owner in the first k sigs to be the last signer - // last signer must sign over full(actual) gas vals used - bytes30 ownerSignFullUserOp; - // only require one singer to sign full digest if fullUserOpHash != minimalUserOpHash - if (fullUserOpHash != minimalUserOpHash) { - ownerSignFullUserOp = owners[n % k].owner; - } - - // initialThresholdWeight is equivalent to k because every signer has weight 1 - vm.prank(account); - plugin.onInstall(abi.encode(initialOwners, initialWeights, initialPubKeyOwners, initialPubKeyWeights, k)); - bytes memory sigDynamicParts = bytes(""); - uint256 offset = k * 65; // start after constant part - userOp.signature = bytes(""); // constant + dynamic - bytes32 k1Digest; - bytes32 r1Digest; - uint256 numOfSignersOnFullUserOp; - for (uint256 i = 0; i < k; i++) { - if (owners[i].owner == ownerSignFullUserOp) { - k1Digest = fullUserOpHash.toEthSignedMessageHash(); - r1Digest = webauthnDigestForFullUserOp; - } else { - k1Digest = minimalUserOpHash.toEthSignedMessageHash(); - r1Digest = webauthnDigestForMinimalUserOp; - } - uint8 v; - bytes32 r; - bytes32 s; - if (owners[i].sigType == 27) { - console.logString("eoa owner signs.."); - (v, r, s) = vm.sign(owners[i].signerWallet.privateKey, k1Digest); - if (owners[i].owner == ownerSignFullUserOp) { - v += 32; - numOfSignersOnFullUserOp++; - } - // constant part only for EOA - userOp.signature = abi.encodePacked(userOp.signature, abi.encodePacked(r, s, v)); - } else if (owners[i].sigType == 0) { - console.logString("contract owner signs.."); - (v, r, s) = vm.sign(owners[i].signerWallet.privateKey, k1Digest); - // 65 is the length of k1 signature - // use the original v for signer ecrecover - sigDynamicParts = abi.encodePacked(sigDynamicParts, uint256(65), r, s, v); - if (owners[i].owner == ownerSignFullUserOp) { - v = 32; // 0 + 32 - numOfSignersOnFullUserOp++; - } else { - v = 0; - } - userOp.signature = - abi.encodePacked(userOp.signature, abi.encode(toAddress(owners[i].owner)), uint256(offset), v); - // dynamic part because offset was set to the end of constant part initially - offset += 97; // 65 (k1 sig length) + 32 (length of sig) - } else { - console.logString("r1 owner signs.."); - (r, s) = vm.signP256(owners[i].signerWallet.privateKey, r1Digest); - uint256 ur = uint256(r); - uint256 us = uint256(s); - if (us > _P256_N_DIV_2) { - us = FCL_Elliptic_ZZ.n - us; - } - if (owners[i].owner == ownerSignFullUserOp) { - v = 34; // 2 + 32 - numOfSignersOnFullUserOp++; - } else { - v = 2; - } - // x || dynamic pos || sig type - PublicKey memory pubKey = - PublicKey({x: owners[i].signerWallet.publicKeyX, y: owners[i].signerWallet.publicKeyY}); - bytes32 pubKeyId = bytes32(bytes.concat(bytes2(0), plugin.getOwnerId(pubKey))); - userOp.signature = abi.encodePacked(userOp.signature, pubKeyId, uint256(offset), v); - - bytes memory sigBytes; - if (owners[i].owner == ownerSignFullUserOp) { - webAuthnSigDynamicPartForFullUserOp.r = ur; - webAuthnSigDynamicPartForFullUserOp.s = us; - sigBytes = abi.encode(webAuthnSigDynamicPartForFullUserOp); - } else { - webAuthnSigDynamicPartForMinimalUserOp.r = ur; - webAuthnSigDynamicPartForMinimalUserOp.s = us; - sigBytes = abi.encode(webAuthnSigDynamicPartForMinimalUserOp); - } - - // dynamic part because offset was set to the end of constant part initially - offset += (32 + sigBytes.length); - // length of bytes || sig data bytes - sigDynamicParts = abi.encodePacked(sigDynamicParts, uint256(sigBytes.length), sigBytes); - } - } - // concatenate(constant, dynamic) - userOp.signature = abi.encodePacked(userOp.signature, sigDynamicParts); - console.log("numOfSignersOnFullUserOp: ", numOfSignersOnFullUserOp); - - vm.prank(account); - // sig check should pass - assertEq( - plugin.userOpValidationFunction( - uint8(BaseMultisigPlugin.FunctionId.USER_OP_VALIDATION_OWNER), userOp, fullUserOpHash - ), - SIG_VALIDATION_SUCCEEDED - ); } function test_failUserOpValidationFunction_noActualDigest() public { @@ -2591,16 +2652,18 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { // make salts different string memory salt2 = string.concat(salt1, "foo"); // range bound the possible set of priv keys - (address signer1, uint256 privateKey1) = makeAddrAndKey(salt1); - (address signer2, uint256 privateKey2) = makeAddrAndKey(salt2); + address[] memory signers = new address[](2); + uint256[] memory privateKeys = new uint256[](2); + (signers[0], privateKeys[0]) = makeAddrAndKey(salt1); + (signers[1], privateKeys[1]) = makeAddrAndKey(salt2); - vm.assume(signer1 != address(0)); - vm.assume(signer2 != address(0)); - vm.assume(signer2 > signer1); // enforce ascending order + vm.assume(signers[0] != address(0)); + vm.assume(signers[1] != address(0)); + vm.assume(signers[1] > signers[0]); // enforce ascending order // add owners address[] memory ownersToAdd1 = new address[](1); - ownersToAdd1[0] = signer1; + ownersToAdd1[0] = signers[0]; uint256[] memory weightsToAdd1 = new uint256[](1); weightsToAdd1[0] = weightOne; @@ -2608,10 +2671,7 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { plugin.onInstall(abi.encode(ownersToAdd1, weightsToAdd1, new PublicKey[](0), new uint256[](0), weightOne)); // sign minimal user op hash - bytes32 minimalUserOpHash = entryPoint.getUserOpHash(userOp); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey1, minimalUserOpHash.toEthSignedMessageHash()); - - userOp.signature = abi.encodePacked(r, s, v); + userOp.signature = signUserOpHash(entryPoint, vm, privateKeys[0], userOp); // set an actual gas field userOp.accountGasLimits = bytes32(0x0000000000000000000000000000000000000000000000000000000000000005); @@ -2626,7 +2686,7 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { // Should succeed with 1 sig over actual digest address[] memory ownersToAdd2 = new address[](1); - ownersToAdd2[0] = signer2; + ownersToAdd2[0] = signers[1]; uint256[] memory weightsToAdd2 = new uint256[](1); weightsToAdd2[0] = weightOne; @@ -2634,8 +2694,7 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { vm.prank(account); plugin.addOwners(ownersToAdd2, weightsToAdd2, new PublicKey[](0), new uint256[](0), weightOne * 2); - (uint8 v2, bytes32 r2, bytes32 s2) = vm.sign(privateKey2, actualUserOpHash.toEthSignedMessageHash()); - bytes memory sig2 = abi.encodePacked(r2, s2, v2 + 32); // add 32 to v for actual digest + bytes memory sig2 = _generateSig(privateKeys[1], actualUserOpHash); userOp.signature = abi.encodePacked(userOp.signature, sig2); @@ -2648,8 +2707,7 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { ); // Should fail with 2 sig over actual digest - (uint8 v3, bytes32 r3, bytes32 s3) = vm.sign(privateKey1, actualUserOpHash.toEthSignedMessageHash()); - bytes memory sig3 = abi.encodePacked(r3, s3, v3 + 32); + bytes memory sig3 = _generateSig(privateKeys[0], actualUserOpHash); userOp.signature = abi.encodePacked(sig3, sig2); @@ -2662,6 +2720,11 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { ); } + function _generateSig(uint256 privateKey, bytes32 actualUserOpHash) internal pure returns (bytes memory) { + (uint8 v2, bytes32 r2, bytes32 s2) = vm.sign(privateKey, actualUserOpHash.toEthSignedMessageHash()); + return abi.encodePacked(r2, s2, v2 + 32); // add 32 to v for actual digest + } + function test_failUserOpValidation_sigOffset() public { _install(); PackedUserOperation memory userOp; @@ -2789,107 +2852,96 @@ contract WeightedWebauthnMultisigPluginTest is TestUtils { } function testAddUpdateThenRemoveWeights() public { - // install with owner1 - address owner1 = address(0x1); - uint256 weight1 = 2; - uint256 thresholdWeight = 2; + // install with owner1 address(0x1) with weight 2 vm.expectEmit(true, true, true, true); address[] memory ownerList = new address[](1); uint256[] memory weightList = new uint256[](1); PublicKey[] memory emptyPubKeyList = new PublicKey[](0); uint256[] memory emptyPubKeyWeightList = new uint256[](0); // set the initial weight - ownerList[0] = owner1; - weightList[0] = weight1; + ownerList[0] = address(0x1); + weightList[0] = 2; (bytes30[] memory _tOwners, OwnerData[] memory _tWeights) = _mergeOwnersData(ownerList, weightList, emptyPubKeyList, emptyPubKeyWeightList); emit OwnersAdded(account, _tOwners, _tWeights); vm.expectEmit(true, true, true, true); - emit ThresholdUpdated(account, 0, thresholdWeight); - - plugin.onInstall(abi.encode(ownerList, weightList, emptyPubKeyList, emptyPubKeyWeightList, thresholdWeight)); + emit ThresholdUpdated(account, 0, 2); + // thresholdWeight = 2 + plugin.onInstall(abi.encode(ownerList, weightList, emptyPubKeyList, emptyPubKeyWeightList, 2)); ( bytes30[] memory returnedOwners, OwnerData[] memory returnedOwnersData, OwnershipMetadata memory ownershipMetadata ) = plugin.ownershipInfoOf(account); - uint256 returnedThresholdWeight = ownershipMetadata.thresholdWeight; - (uint256 res,,,,) = plugin.ownerDataPerAccount(owner1.toBytes30(), account); - assertEq(res, weight1); - - uint256 returnedTotalWeight = ownershipMetadata.totalWeight; - assertEq(_sum(returnedOwnersData), returnedTotalWeight); - console.log("after installation with owner1, current total weight: %d", returnedTotalWeight); - console.log("after installation with owner1, current total threshold weight: %d", returnedThresholdWeight); - assertEq(returnedTotalWeight, weight1); // == 2 - assertEq(returnedThresholdWeight, thresholdWeight); // == 2 + (uint256 res,,,,) = plugin.ownerDataPerAccount(address(0x1).toBytes30(), account); + assertEq(res, 2); + + assertEq(_sum(returnedOwnersData), ownershipMetadata.totalWeight); + console.log("after installation with owner1, current total weight: %d", ownershipMetadata.totalWeight); + console.log( + "after installation with owner1, current total threshold weight: %d", ownershipMetadata.thresholdWeight + ); + assertEq(ownershipMetadata.totalWeight, 2); // == 2 + assertEq(ownershipMetadata.thresholdWeight, 2); // == 2 assertEq(ownershipMetadata.numOwners, 1); - // add owner2 - address owner2 = address(0x2); - uint256 weight2 = 2; - ownerList[0] = owner2; - weightList[0] = weight2; - thresholdWeight = 4; + // add owner2 address(0x2) with weight 2 + ownerList[0] = address(0x2); + weightList[0] = 2; + // thresholdWeight = 4 vm.prank(account); - plugin.addOwners(ownerList, weightList, emptyPubKeyList, emptyPubKeyWeightList, thresholdWeight); + plugin.addOwners(ownerList, weightList, emptyPubKeyList, emptyPubKeyWeightList, 4); (returnedOwners, returnedOwnersData, ownershipMetadata) = plugin.ownershipInfoOf(account); - returnedTotalWeight = ownershipMetadata.totalWeight; - returnedThresholdWeight = ownershipMetadata.thresholdWeight; - assertEq(_sum(returnedOwnersData), returnedTotalWeight); - console.log("after adding owner2, new total weight: %d", returnedTotalWeight); - console.log("after adding owner2, total threshold weight: %d", returnedThresholdWeight); - assertEq(returnedTotalWeight, weight1 + weight2); // == 4 - assertEq(returnedThresholdWeight, thresholdWeight); // == 4 + assertEq(_sum(returnedOwnersData), ownershipMetadata.totalWeight); + console.log("after adding owner2, new total weight: %d", ownershipMetadata.totalWeight); + console.log("after adding owner2, total threshold weight: %d", ownershipMetadata.thresholdWeight); + assertEq(ownershipMetadata.totalWeight, 2 + 2); // == 4 + assertEq(ownershipMetadata.thresholdWeight, 4); // == 4 assertEq(ownershipMetadata.numOwners, 2); // update owner2's weight from 2 to 4 address[] memory updatedOwnerList = new address[](1); - updatedOwnerList[0] = owner2; - uint256 updatedWeight2 = 4; + updatedOwnerList[0] = address(0x2); + // updatedWeight2 = 4 uint256[] memory updatedWeights = new uint256[](1); - updatedWeights[0] = updatedWeight2; + updatedWeights[0] = 4; (_tOwners, _tWeights) = _mergeOwnersData(updatedOwnerList, updatedWeights, emptyPubKeyList, emptyPubKeyWeightList); vm.expectEmit(true, true, true, true); emit OwnersUpdated(account, _tOwners, _tWeights); vm.prank(account); - thresholdWeight = 4; - plugin.updateMultisigWeights( - updatedOwnerList, updatedWeights, emptyPubKeyList, emptyPubKeyWeightList, thresholdWeight - ); + // thresholdWeight = 4 + plugin.updateMultisigWeights(updatedOwnerList, updatedWeights, emptyPubKeyList, emptyPubKeyWeightList, 4); (returnedOwners, returnedOwnersData, ownershipMetadata) = plugin.ownershipInfoOf(account); - returnedTotalWeight = ownershipMetadata.totalWeight; - assertEq(_sum(returnedOwnersData), returnedTotalWeight); - uint256 expectedTotalWeight = weight1 + updatedWeight2; + assertEq(_sum(returnedOwnersData), ownershipMetadata.totalWeight); + uint256 expectedTotalWeight = 2 + 4; console.log("expected total weight: %d", expectedTotalWeight); - console.log("after updating owner2, new total weight: %d", returnedTotalWeight); - console.log("after updating owner2, new total threshold weight: %d", returnedThresholdWeight); - assertEq(returnedTotalWeight, expectedTotalWeight); // == 6 - assertEq(returnedThresholdWeight, thresholdWeight); // == 4 + console.log("after updating owner2, new total weight: %d", ownershipMetadata.totalWeight); + console.log("after updating owner2, new total threshold weight: %d", ownershipMetadata.thresholdWeight); + assertEq(ownershipMetadata.totalWeight, expectedTotalWeight); // == 6 + assertEq(ownershipMetadata.thresholdWeight, 4); // == 4 assertEq(ownershipMetadata.numOwners, 2); // remove owner1 - thresholdWeight = 4; + // thresholdWeight = 4 address[] memory ownersToRemove = new address[](1); - ownersToRemove[0] = owner1; + ownersToRemove[0] = address(0x1); vm.prank(account); - plugin.removeOwners(ownersToRemove, emptyPubKeyList, thresholdWeight); + plugin.removeOwners(ownersToRemove, emptyPubKeyList, 4); (returnedOwners, returnedOwnersData, ownershipMetadata) = plugin.ownershipInfoOf(account); - returnedTotalWeight = ownershipMetadata.totalWeight; - assertEq(_sum(returnedOwnersData), returnedTotalWeight); - expectedTotalWeight = updatedWeight2; + assertEq(_sum(returnedOwnersData), ownershipMetadata.totalWeight); + expectedTotalWeight = 4; console.log("expected total weight: %d", expectedTotalWeight); - console.log("after removing owner1, new total weight: %d", returnedTotalWeight); - console.log("after removing owner1, new total threshold weight: %d", returnedThresholdWeight); - assertEq(returnedTotalWeight, expectedTotalWeight); // == 4 - assertEq(returnedThresholdWeight, thresholdWeight); // == 4 + console.log("after removing owner1, new total weight: %d", ownershipMetadata.totalWeight); + console.log("after removing owner1, new total threshold weight: %d", ownershipMetadata.thresholdWeight); + assertEq(ownershipMetadata.totalWeight, expectedTotalWeight); // == 4 + assertEq(ownershipMetadata.thresholdWeight, 4); // == 4 assertEq(ownershipMetadata.numOwners, 1); } diff --git a/test/msca/6900/v0.8/DirectCallsFromModule.t.sol b/test/msca/6900/v0.8/DirectCallsFromModule.t.sol index 1e2df22..a29525a 100644 --- a/test/msca/6900/v0.8/DirectCallsFromModule.t.sol +++ b/test/msca/6900/v0.8/DirectCallsFromModule.t.sol @@ -21,23 +21,22 @@ pragma solidity 0.8.24; import {BaseMSCA} from "../../../../src/msca/6900/v0.8/account/BaseMSCA.sol"; import {UpgradableMSCA} from "../../../../src/msca/6900/v0.8/account/UpgradableMSCA.sol"; -import {DIRECT_CALL_VALIDATION_ENTITY_ID} from "../../../../src/msca/6900/v0.8/common/Constants.sol"; -import {Call} from "../../../../src/msca/6900/v0.8/common/Structs.sol"; -import {ModuleEntity, ValidationConfig} from "../../../../src/msca/6900/v0.8/common/Types.sol"; +import {ModuleEntity, ValidationConfig} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol"; -import {IModularAccount} from "../../../../src/msca/6900/v0.8/interfaces/IModularAccount.sol"; +import {Call, IModularAccount} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol"; -import {HookConfigLib} from "../../../../src/msca/6900/v0.8/libs/HookConfigLib.sol"; -import {ModuleEntityLib} from "../../../../src/msca/6900/v0.8/libs/thirdparty/ModuleEntityLib.sol"; -import {ValidationConfigLib} from "../../../../src/msca/6900/v0.8/libs/thirdparty/ValidationConfigLib.sol"; import {SingleSignerValidationModule} from "../../../../src/msca/6900/v0.8/modules/validation/SingleSignerValidationModule.sol"; +import {HookConfigLib} from "@erc6900/reference-implementation/libraries/HookConfigLib.sol"; +import {ModuleEntityLib} from "@erc6900/reference-implementation/libraries/ModuleEntityLib.sol"; +import {ValidationConfigLib} from "@erc6900/reference-implementation/libraries/ValidationConfigLib.sol"; import {UpgradableMSCAFactory} from "../../../../src/msca/6900/v0.8/factories/UpgradableMSCAFactory.sol"; import {DirectCallModule} from "./helpers/DirectCallModule.sol"; import {AccountTestUtils} from "./utils/AccountTestUtils.sol"; import {EntryPoint} from "@account-abstraction/contracts/core/EntryPoint.sol"; import {IEntryPoint} from "@account-abstraction/contracts/interfaces/IEntryPoint.sol"; +import {DIRECT_CALL_VALIDATION_ENTITY_ID} from "@erc6900/reference-implementation/helpers/Constants.sol"; /// @notice Inspired by 6900 reference implementation with some modifications contract DirectCallsFromModuleTest is AccountTestUtils { @@ -103,7 +102,7 @@ contract DirectCallsFromModuleTest is AccountTestUtils { function testFailDirectCallModuleNotInstalled() public { vm.startPrank(address(directCallModule)); vm.expectRevert( - abi.encodeWithSelector(BaseMSCA.ValidationFunctionMissing.selector, IModularAccount.execute.selector) + abi.encodeWithSelector(BaseMSCA.InvalidValidationFunction.selector, IModularAccount.execute.selector) ); msca.execute(address(0), 0, ""); vm.stopPrank(); @@ -117,7 +116,7 @@ contract DirectCallsFromModuleTest is AccountTestUtils { Call[] memory calls = new Call[](0); vm.startPrank(address(directCallModule)); vm.expectRevert( - abi.encodeWithSelector(BaseMSCA.ValidationFunctionMissing.selector, IModularAccount.executeBatch.selector) + abi.encodeWithSelector(BaseMSCA.InvalidValidationFunction.selector, IModularAccount.executeBatch.selector) ); msca.executeBatch(calls); vm.stopPrank(); @@ -131,7 +130,7 @@ contract DirectCallsFromModuleTest is AccountTestUtils { vm.startPrank(address(directCallModule)); vm.expectRevert( abi.encodeWithSelector( - BaseMSCA.ValidationFunctionMissing.selector, IModularAccount.execute.selector, directCallModuleEntity + BaseMSCA.InvalidValidationFunction.selector, IModularAccount.execute.selector, directCallModuleEntity ) ); msca.execute(address(0), 0, ""); @@ -183,7 +182,7 @@ contract DirectCallsFromModuleTest is AccountTestUtils { vm.startPrank(address(directCallModule)); vm.expectRevert( abi.encodeWithSelector( - BaseMSCA.ValidationFunctionMissing.selector, IModularAccount.execute.selector, directCallModuleEntity + BaseMSCA.InvalidValidationFunction.selector, IModularAccount.execute.selector, directCallModuleEntity ) ); msca.execute(address(0), 0, ""); @@ -233,7 +232,8 @@ contract DirectCallsFromModuleTest is AccountTestUtils { function _uninstallDirectCallValidation() internal { vm.startPrank(address(entryPoint)); vm.expectEmit(true, true, true, true); - emit ValidationUninstalled(directCallModuleEntity.module(), DIRECT_CALL_VALIDATION_ENTITY_ID, true); + (address moduleAddr,) = directCallModuleEntity.unpack(); + emit ValidationUninstalled(moduleAddr, DIRECT_CALL_VALIDATION_ENTITY_ID, true); msca.uninstallValidation(directCallModuleEntity, bytes(""), new bytes[](1)); } } diff --git a/test/msca/6900/v0.8/DynamicValidationHookData.t.sol b/test/msca/6900/v0.8/DynamicValidationHookData.t.sol index 511bb3f..7f6888e 100644 --- a/test/msca/6900/v0.8/DynamicValidationHookData.t.sol +++ b/test/msca/6900/v0.8/DynamicValidationHookData.t.sol @@ -22,23 +22,23 @@ pragma solidity 0.8.24; import {UpgradableMSCA} from "../../../../src/msca/6900/v0.8/account/UpgradableMSCA.sol"; -import {ModuleEntity} from "../../../../src/msca/6900/v0.8/common/Types.sol"; +import {ModuleEntity} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol"; -import {IModularAccount} from "../../../../src/msca/6900/v0.8/interfaces/IModularAccount.sol"; -import {ModuleEntityLib} from "../../../../src/msca/6900/v0.8/libs/thirdparty/ModuleEntityLib.sol"; +import {IModularAccount} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol"; +import {ModuleEntityLib} from "@erc6900/reference-implementation/libraries/ModuleEntityLib.sol"; -import {ValidationConfigLib} from "../../../../src/msca/6900/v0.8/libs/thirdparty/ValidationConfigLib.sol"; import {SingleSignerValidationModule} from "../../../../src/msca/6900/v0.8/modules/validation/SingleSignerValidationModule.sol"; +import {ValidationConfigLib} from "@erc6900/reference-implementation/libraries/ValidationConfigLib.sol"; import {UpgradableMSCAFactory} from "../../../../src/msca/6900/v0.8/factories/UpgradableMSCAFactory.sol"; -import {HookConfigLib} from "../../../../src/msca/6900/v0.8/libs/HookConfigLib.sol"; import {TestAddressBookModule} from "./helpers/TestAddressBookModule.sol"; import {AccountTestUtils} from "./utils/AccountTestUtils.sol"; import {EntryPoint} from "@account-abstraction/contracts/core/EntryPoint.sol"; import {IEntryPoint} from "@account-abstraction/contracts/interfaces/IEntryPoint.sol"; import {PackedUserOperation} from "@account-abstraction/contracts/interfaces/PackedUserOperation.sol"; +import {HookConfigLib} from "@erc6900/reference-implementation/libraries/HookConfigLib.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; // We use UpgradableMSCA (that inherits from UpgradableMSCA) because it has some convenience functions @@ -144,7 +144,6 @@ contract DynamicValidationHookDataTest is AccountTestUtils { PackedUserOperation memory userOp = buildPartialUserOp( address(msca), 0, "0x", vm.toString(executeCallData), 83353, 1028650, 45484, 516219199704, 1130000000, "0x" ); - bytes32 userOpHash = entryPoint.getUserOpHash(userOp); // signature is the data for ownerValidation bytes memory signature = signUserOpHash(entryPoint, vm, ownerPrivateKey, userOp); @@ -330,7 +329,6 @@ contract DynamicValidationHookDataTest is AccountTestUtils { PackedUserOperation memory userOp = buildPartialUserOp( address(msca), 0, "0x", vm.toString(executeCallData), 83353, 1028650, 45484, 516219199704, 1130000000, "0x" ); - bytes32 userOpHash = entryPoint.getUserOpHash(userOp); // signature is the data for ownerValidation bytes memory signature = signUserOpHash(entryPoint, vm, ownerPrivateKey, userOp); @@ -342,14 +340,7 @@ contract DynamicValidationHookDataTest is AccountTestUtils { PackedUserOperation[] memory ops = new PackedUserOperation[](1); ops[0] = userOp; vm.startPrank(address(entryPoint)); - vm.expectRevert( - abi.encodeWithSelector( - IEntryPoint.FailedOpWithRevert.selector, - 0, - "AA23 reverted", - abi.encodeWithSelector(NonCanonicalEncoding.selector) - ) - ); + vm.expectRevert(abi.encodeWithSelector(IEntryPoint.FailedOp.selector, 0, "AA24 signature error")); entryPoint.handleOps(ops, beneficiary); vm.stopPrank(); // verify recipient balance diff --git a/test/msca/6900/v0.8/FooBarModule.sol b/test/msca/6900/v0.8/FooBarModule.sol index 29b6c3e..c1e6624 100644 --- a/test/msca/6900/v0.8/FooBarModule.sol +++ b/test/msca/6900/v0.8/FooBarModule.sol @@ -21,12 +21,15 @@ pragma solidity 0.8.24; import {SIG_VALIDATION_SUCCEEDED} from "../../../../src/common/Constants.sol"; import {NotImplementedFunction} from "../../../../src/msca/6900/shared/common/Errors.sol"; -import {ExecutionManifest, ManifestExecutionFunction} from "../../../../src/msca/6900/v0.8/common/ModuleManifest.sol"; +import { + ExecutionManifest, + ManifestExecutionFunction +} from "@erc6900/reference-implementation/interfaces/IExecutionModule.sol"; -import {IExecutionModule} from "../../../../src/msca/6900/v0.8/interfaces/IExecutionModule.sol"; -import {IValidationModule} from "../../../../src/msca/6900/v0.8/interfaces/IValidationModule.sol"; import {BaseModule} from "../../../../src/msca/6900/v0.8/modules/BaseModule.sol"; import {PackedUserOperation} from "@account-abstraction/contracts/interfaces/PackedUserOperation.sol"; +import {IExecutionModule} from "@erc6900/reference-implementation/interfaces/IExecutionModule.sol"; +import {IValidationModule} from "@erc6900/reference-implementation/interfaces/IValidationModule.sol"; contract FooBarModule is IValidationModule, IExecutionModule, BaseModule { string public constant NAME = "Your Favourite Fruit Bar Module"; diff --git a/test/msca/6900/v0.8/MSCACallFlow.t.sol b/test/msca/6900/v0.8/MSCACallFlow.t.sol new file mode 100644 index 0000000..39983ca --- /dev/null +++ b/test/msca/6900/v0.8/MSCACallFlow.t.sol @@ -0,0 +1,722 @@ +/* + * Copyright 2024 Circle Internet Group, Inc. All rights reserved. + + * SPDX-License-Identifier: GPL-3.0-or-later + + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +pragma solidity 0.8.24; + +import {DIRECT_CALL_VALIDATION_ENTITY_ID} from "@erc6900/reference-implementation/helpers/Constants.sol"; +import { + ExecutionManifest, ManifestExecutionHook +} from "@erc6900/reference-implementation/interfaces/IExecutionModule.sol"; +import {ModuleEntity, ValidationConfig} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol"; +import {HookConfigLib} from "@erc6900/reference-implementation/libraries/HookConfigLib.sol"; +import {ModuleEntityLib} from "@erc6900/reference-implementation/libraries/ModuleEntityLib.sol"; +import {ValidationConfigLib} from "@erc6900/reference-implementation/libraries/ValidationConfigLib.sol"; +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; + +import {UpgradableMSCA} from "../../../../src/msca/6900/v0.8/account/UpgradableMSCA.sol"; +import {UpgradableMSCAFactory} from "../../../../src/msca/6900/v0.8/factories/UpgradableMSCAFactory.sol"; +import {SingleSignerValidationModule} from + "../../../../src/msca/6900/v0.8/modules/validation/SingleSignerValidationModule.sol"; +import {MSCACallFlowModule} from "./helpers/MSCACallFlowModule.sol"; +import {AccountTestUtils} from "./utils/AccountTestUtils.sol"; +import {EntryPoint} from "@account-abstraction/contracts/core/EntryPoint.sol"; +import {IEntryPoint} from "@account-abstraction/contracts/interfaces/IEntryPoint.sol"; + +// @notice Inspired by Alchemy's implementation with modifications. +// For MSCA call flow, please refer to +// https://github.com/erc6900/reference-implementation/blob/6cdcfa653eb019d27d23586a86ff8171201a4066/standard/assets/eip-6900/Modular_Account_Call_Flow.svg. +// This test asserts that all hooks, validation function, module functions and account functions are executed in the +// correct order: +// 1. All validation hooks (in forward order) +// 2. The validation function +// 3. All pre-exec hooks associated with validation (in forward order) +// 4. All pre-exec hooks associated with selector (in forward order) +// 5. The account / module exec function +// 6. All post-exec hooks associated with selector (in reverse order) +// 7. All post-exec hooks associated with validation (in reverse order) +// +// To do this, it installs a special module called HookOrderCheckerModule that is a module of every type, and each +// implementation reports it's order (from it's entityId) to a storage list. At the end of each execution flow, the +// list is asserted to be of the right length and contain the elements in the correct order. +// +// This test does not assert hook ordering after the removal of any hooks, or the addition of hooks after the first +// install. That case will need an invariant test + harness to handle the addition and removal of hooks. +contract MSCACallFlowTest is AccountTestUtils { + IEntryPoint private entryPoint = new EntryPoint(); + uint256 internal ownerPrivateKey; + address private ownerAddr; + address payable private beneficiary; // e.g. bundler + UpgradableMSCAFactory private factory; + address private factoryOwner; + SingleSignerValidationModule private singleSignerValidationModule; + ModuleEntity private ownerValidationEntity; + bytes32 private salt = 0x0000000000000000000000000000000000000000000000000000000000000000; + UpgradableMSCA private msca; + MSCACallFlowModule public mscaCallFlowModule; + ModuleEntity public mscaCallFlowValidationEntity; + + function setUp() public { + factoryOwner = makeAddr("factoryOwner"); + beneficiary = payable(address(makeAddr("bundler"))); + factory = new UpgradableMSCAFactory(factoryOwner, address(entryPoint)); + singleSignerValidationModule = new SingleSignerValidationModule(); + address[] memory modules = new address[](1); + modules[0] = address(singleSignerValidationModule); + bool[] memory _permissions = new bool[](1); + _permissions[0] = true; + vm.startPrank(factoryOwner); + factory.setModules(modules, _permissions); + vm.stopPrank(); + ownerValidationEntity = ModuleEntityLib.pack(address(singleSignerValidationModule), uint32(0)); + ValidationConfig validationConfig = ValidationConfigLib.pack(ownerValidationEntity, false, true, false); + bytes memory initializingData = + abi.encode(validationConfig, new bytes4[](0), abi.encode(uint32(0), ownerAddr), new bytes[](0)); + msca = factory.createAccountWithValidation(addressToBytes32(ownerAddr), salt, initializingData); + vm.deal(address(msca), 2 ether); + mscaCallFlowModule = new MSCACallFlowModule(); + } + + // userOp: module exec function with validation associated exec hooks and selector associated exec hooks + function testModuleFuncWithAllHooksViaUserOp() public { + _installOrderCheckerModule(4); + bytes memory executeCallData = + abi.encodePacked(msca.executeUserOp.selector, abi.encodeCall(MSCACallFlowModule.foo, (17))); + PackedUserOperation memory userOp = + buildPartialUserOp(address(msca), 0, "0x", vm.toString(executeCallData), 1000000, 1000000, 0, 1, 1, "0x"); + // PER_SELECTOR_VALIDATION_FLAG + userOp.signature = encodeSignature(new PreValidationHookData[](0), mscaCallFlowValidationEntity, "0x", false); + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); + userOps[0] = userOp; + entryPoint.handleOps(userOps, beneficiary); + + _checkInvokeOrderWithBothValidationAndSelectorAssocExecHooks(); + } + + // userOp: module exec function, with only selector associated exec hooks, without validation-associated exec hooks + function testModuleFuncWithOnlySelectorExecHooksViaUserOp() public { + _installOrderCheckerModuleWithOnlySelectorExecHooks(4); + bytes memory executeCallData = abi.encodeCall(MSCACallFlowModule.foo, (17)); + PackedUserOperation memory userOp = + buildPartialUserOp(address(msca), 0, "0x", vm.toString(executeCallData), 1000000, 1000000, 0, 1, 1, "0x"); + // PER_SELECTOR_VALIDATION_FLAG + userOp.signature = encodeSignature(new PreValidationHookData[](0), mscaCallFlowValidationEntity, "0x", false); + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); + userOps[0] = userOp; + entryPoint.handleOps(userOps, beneficiary); + + _checkInvokeOrderWithOnlySelectorAssocExecHooks(); + } + + // User op: module exec function, with only selector associated exec hooks, without validation-associated exec + // hooks, yes executeUserOp + // Call order is the same the test without executeUserOp + function testModuleFuncWithOnlySelectorExecHooksViaExecuteUserOp() public { + _installOrderCheckerModuleWithOnlySelectorExecHooks(4); + bytes memory executeCallData = + abi.encodePacked(msca.executeUserOp.selector, abi.encodeCall(MSCACallFlowModule.foo, (17))); + PackedUserOperation memory userOp = + buildPartialUserOp(address(msca), 0, "0x", vm.toString(executeCallData), 1000000, 1000000, 0, 1, 1, "0x"); + // PER_SELECTOR_VALIDATION_FLAG + userOp.signature = encodeSignature(new PreValidationHookData[](0), mscaCallFlowValidationEntity, "0x", false); + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); + userOps[0] = userOp; + entryPoint.handleOps(userOps, beneficiary); + + _checkInvokeOrderWithOnlySelectorAssocExecHooks(); + } + + // Runtime: module exec function with validation associated exec hooks and selector associated exec hooks + // Call order is the same as the test on userOp + function testModuleFuncWithAllHooksViaRuntime() public { + _installOrderCheckerModule(4); + msca.executeWithRuntimeValidation( + abi.encodeCall(MSCACallFlowModule.foo, (17)), + encodeSignature(new PreValidationHookData[](0), mscaCallFlowValidationEntity, "0x", false) + ); + + _checkInvokeOrderWithBothValidationAndSelectorAssocExecHooks(); + } + + // Runtime: module exec function, with only selector-associated exec hooks, without validation-associated exec hooks + // Call order is the same as the test on userOp + function testModuleFuncWithOnlySelectorExecHooksViaRuntime() public { + _installOrderCheckerModuleWithOnlySelectorExecHooks(4); + msca.executeWithRuntimeValidation( + abi.encodeCall(MSCACallFlowModule.foo, (17)), + encodeSignature(new PreValidationHookData[](0), mscaCallFlowValidationEntity, "0x", false) + ); + + _checkInvokeOrderWithOnlySelectorAssocExecHooks(); + } + + // Direct call: module exec function with validation-associated exec hooks and selector-associated exec hooks + function testModuleFuncWithAllHooksViaRuntimeDirectCall() public { + _installOrderCheckerModule(DIRECT_CALL_VALIDATION_ENTITY_ID); + + vm.prank(address(mscaCallFlowModule)); + MSCACallFlowModule(address(msca)).foo(17); + + _checkInvokeOrderDirectCallWithBothValidationAndSelectorAssocExecHooks(); + } + + // Direct call: with only selector-associated exec hooks, without validation-associated exec hooks + function testModuleFuncWithOnlySelectorExecHooksViaRuntimeDirectCall() public { + _installOrderCheckerModuleWithOnlySelectorExecHooks(DIRECT_CALL_VALIDATION_ENTITY_ID); + + vm.prank(address(mscaCallFlowModule)); + MSCACallFlowModule(address(msca)).foo(17); + + _checkInvokeOrderDirectCallWithOnlySelectorAssocExecHooks(); + } + + // User op: account native function with validation-associated exec hooks and selector-associated exec hooks + // Call order is the same as the test on module exec function + function testAccountNativeFuncWithAllHooksViaUserOp() public { + _installOrderCheckerModule(4); + + bytes memory executeCallData = abi.encodePacked( + msca.executeUserOp.selector, + abi.encodeCall( + msca.execute, (address(mscaCallFlowModule), 0 wei, abi.encodeCall(MSCACallFlowModule.foo, (17))) + ) + ); + PackedUserOperation memory userOp = + buildPartialUserOp(address(msca), 0, "0x", vm.toString(executeCallData), 1000000, 1000000, 0, 1, 1, "0x"); + // PER_SELECTOR_VALIDATION_FLAG + userOp.signature = encodeSignature(new PreValidationHookData[](0), mscaCallFlowValidationEntity, "0x", false); + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); + userOps[0] = userOp; + entryPoint.handleOps(userOps, beneficiary); + + _checkInvokeOrderWithBothValidationAndSelectorAssocExecHooks(); + } + + // User op: account native function, with only selector-associated exec hooks, without validation-associated exec + // hooks, no executeUserOp + // Call order is the same as the test on module exec function + function testAccountNativeFuncWithOnlySelectorExecHooksViaUserOp() public { + _installOrderCheckerModuleWithOnlySelectorExecHooks(4); + + bytes memory executeCallData = abi.encodeCall( + msca.execute, (address(mscaCallFlowModule), 0 wei, abi.encodeCall(MSCACallFlowModule.foo, (17))) + ); + PackedUserOperation memory userOp = + buildPartialUserOp(address(msca), 0, "0x", vm.toString(executeCallData), 1000000, 1000000, 0, 1, 1, "0x"); + // PER_SELECTOR_VALIDATION_FLAG + userOp.signature = encodeSignature(new PreValidationHookData[](0), mscaCallFlowValidationEntity, "0x", false); + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); + userOps[0] = userOp; + entryPoint.handleOps(userOps, beneficiary); + + _checkInvokeOrderWithOnlySelectorAssocExecHooks(); + } + + // User op: account native function, with only selector-associated exec hooks, without validation-associated exec + // hooks + // Call order is the same as the test on module exec function + function testAccountNativeFuncWithOnlySelectorExecHooksViaExecuteUserOp() public { + _installOrderCheckerModuleWithOnlySelectorExecHooks(4); + + bytes memory executeCallData = abi.encodePacked( + msca.executeUserOp.selector, + abi.encodeCall( + msca.execute, (address(mscaCallFlowModule), 0 wei, abi.encodeCall(MSCACallFlowModule.foo, (17))) + ) + ); + PackedUserOperation memory userOp = + buildPartialUserOp(address(msca), 0, "0x", vm.toString(executeCallData), 1000000, 1000000, 0, 1, 1, "0x"); + // PER_SELECTOR_VALIDATION_FLAG + userOp.signature = encodeSignature(new PreValidationHookData[](0), mscaCallFlowValidationEntity, "0x", false); + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); + userOps[0] = userOp; + entryPoint.handleOps(userOps, beneficiary); + + _checkInvokeOrderWithOnlySelectorAssocExecHooks(); + } + + // Runtime: account native function with validation-associated exec hooks and selector-associated exec hooks + // Call order is the same as the test on module exec function + function testAccountNativeFuncWithAllHooksViaRuntime() public { + _installOrderCheckerModule(4); + + msca.executeWithRuntimeValidation( + abi.encodeCall( + msca.execute, (address(mscaCallFlowModule), 0 wei, abi.encodeCall(MSCACallFlowModule.foo, (17))) + ), + encodeSignature(new PreValidationHookData[](0), mscaCallFlowValidationEntity, "0x", false) + ); + + _checkInvokeOrderWithBothValidationAndSelectorAssocExecHooks(); + } + + // Runtime: account native function, with only selector-associated exec hooks, without validation-associated exec + // hooks + // Call order is the same as the test on module exec function + function testAccountNativeFuncWithOnlySelectorExecHooksViaRuntime() public { + _installOrderCheckerModuleWithOnlySelectorExecHooks(4); + + msca.executeWithRuntimeValidation( + abi.encodeCall( + msca.execute, (address(mscaCallFlowModule), 0 wei, abi.encodeCall(MSCACallFlowModule.foo, (17))) + ), + encodeSignature(new PreValidationHookData[](0), mscaCallFlowValidationEntity, "0x", false) + ); + + _checkInvokeOrderWithOnlySelectorAssocExecHooks(); + } + + function testSignatureValidationWithAllHooks() public { + _installOrderCheckerModule(4); + // Technically, the hooks aren't supposed to make state changes during the signature validation flow + // because it will be invoked with `staticcall`, so we call `isValidSignature` directly with `call`. + bytes memory callData = abi.encodeCall( + msca.isValidSignature, + (bytes32(0), encode1271Signature(new PreValidationHookData[](0), mscaCallFlowValidationEntity, "0x")) + ); + + // solhint-disable-next-line avoid-low-level-calls + (bool success,) = address(msca).call(callData); + assertTrue(success); + _checkInvokeOrderSignatureValidation(); + } + + // - install validation and validation-associated exec hooks + // - install execution function and selector-associated exec hooks + function _installOrderCheckerModule(uint32 validationEntityId) internal { + (ValidationConfig validationConfig, bytes4[] memory selectors, bytes[] memory startingHooks) = + _getValidationWithHooksForInstallation(validationEntityId); + + bytes[] memory hooks = new bytes[](9); + + // Validation hooks + hooks[0] = startingHooks[0]; + hooks[1] = startingHooks[1]; + hooks[2] = startingHooks[2]; + + // Validation-associated exec hooks + hooks[3] = abi.encodePacked( + HookConfigLib.packExecHook({ + _module: address(mscaCallFlowModule), + _entityId: 5, + _hasPre: true, + _hasPost: false + }) + ); + hooks[4] = abi.encodePacked( + HookConfigLib.packExecHook({ + _module: address(mscaCallFlowModule), + _entityId: 6, + _hasPre: false, + _hasPost: true + }) + ); + hooks[5] = abi.encodePacked( + HookConfigLib.packExecHook({ + _module: address(mscaCallFlowModule), + _entityId: 7, + _hasPre: true, + _hasPost: true + }) + ); + hooks[6] = abi.encodePacked( + HookConfigLib.packExecHook({ + _module: address(mscaCallFlowModule), + _entityId: 8, + _hasPre: true, + _hasPost: true + }) + ); + hooks[7] = abi.encodePacked( + HookConfigLib.packExecHook({ + _module: address(mscaCallFlowModule), + _entityId: 9, + _hasPre: true, + _hasPost: false + }) + ); + hooks[8] = abi.encodePacked( + HookConfigLib.packExecHook({ + _module: address(mscaCallFlowModule), + _entityId: 10, + _hasPre: false, + _hasPost: true + }) + ); + + vm.prank(address(entryPoint)); + msca.installValidation(validationConfig, selectors, "", hooks); + + _installExecutionFunctionWithHooks(); + } + + function _installOrderCheckerModuleWithOnlySelectorExecHooks(uint32 validationEntityId) internal { + (ValidationConfig validationConfig, bytes4[] memory selectors, bytes[] memory hooks) = + _getValidationWithHooksForInstallation(validationEntityId); + + vm.prank(address(entryPoint)); + msca.installValidation(validationConfig, selectors, "", hooks); + _installExecutionFunctionWithHooks(); + } + + // Install the execution function and selector-associated hooks + // The executionManifest only contains the execution function, we need to insert the selector-associated + // hooks, which are more dynamic + function _installExecutionFunctionWithHooks() internal { + ExecutionManifest memory manifest = mscaCallFlowModule.executionManifest(); + ManifestExecutionHook[] memory execHooks = new ManifestExecutionHook[](12); + + // Apply hooks to the `foo` function + execHooks[0] = ManifestExecutionHook({ + executionSelector: MSCACallFlowModule.foo.selector, + entityId: 11, + isPreHook: true, + isPostHook: false + }); + execHooks[1] = ManifestExecutionHook({ + executionSelector: MSCACallFlowModule.foo.selector, + entityId: 12, + isPreHook: false, + isPostHook: true + }); + execHooks[2] = ManifestExecutionHook({ + executionSelector: MSCACallFlowModule.foo.selector, + entityId: 13, + isPreHook: true, + isPostHook: true + }); + execHooks[3] = ManifestExecutionHook({ + executionSelector: MSCACallFlowModule.foo.selector, + entityId: 14, + isPreHook: true, + isPostHook: true + }); + execHooks[4] = ManifestExecutionHook({ + executionSelector: MSCACallFlowModule.foo.selector, + entityId: 15, + isPreHook: true, + isPostHook: false + }); + execHooks[5] = ManifestExecutionHook({ + executionSelector: MSCACallFlowModule.foo.selector, + entityId: 16, + isPreHook: false, + isPostHook: true + }); + + // Apply hooks to the `execute` function + execHooks[6] = ManifestExecutionHook({ + executionSelector: msca.execute.selector, + entityId: 11, + isPreHook: true, + isPostHook: false + }); + execHooks[7] = ManifestExecutionHook({ + executionSelector: msca.execute.selector, + entityId: 12, + isPreHook: false, + isPostHook: true + }); + execHooks[8] = ManifestExecutionHook({ + executionSelector: msca.execute.selector, + entityId: 13, + isPreHook: true, + isPostHook: true + }); + execHooks[9] = ManifestExecutionHook({ + executionSelector: msca.execute.selector, + entityId: 14, + isPreHook: true, + isPostHook: true + }); + execHooks[10] = ManifestExecutionHook({ + executionSelector: msca.execute.selector, + entityId: 15, + isPreHook: true, + isPostHook: false + }); + execHooks[11] = ManifestExecutionHook({ + executionSelector: msca.execute.selector, + entityId: 16, + isPreHook: false, + isPostHook: true + }); + + manifest.executionHooks = execHooks; + + vm.prank(address(entryPoint)); + msca.installExecution(address(mscaCallFlowModule), manifest, ""); + } + + // Returns the validation config, selectors, and three validation hooks for the installValidation call, + // No validation-associated exec hooks are included + function _getValidationWithHooksForInstallation(uint32 validationEntityId) + internal + returns (ValidationConfig, bytes4[] memory, bytes[] memory) + { + ValidationConfig validationConfig = ValidationConfigLib.pack({ + _module: address(mscaCallFlowModule), + _entityId: validationEntityId, + _isGlobal: false, + _isSignatureValidation: true, + _isUserOpValidation: true + }); + + mscaCallFlowValidationEntity = ModuleEntityLib.pack(address(mscaCallFlowModule), validationEntityId); + + bytes4[] memory selectors = new bytes4[](2); + selectors[0] = MSCACallFlowModule.foo.selector; + selectors[1] = msca.execute.selector; + + bytes[] memory hooks = new bytes[](3); + + // Validation hooks + hooks[0] = + abi.encodePacked(HookConfigLib.packValidationHook({_module: address(mscaCallFlowModule), _entityId: 1})); + hooks[1] = + abi.encodePacked(HookConfigLib.packValidationHook({_module: address(mscaCallFlowModule), _entityId: 2})); + hooks[2] = + abi.encodePacked(HookConfigLib.packValidationHook({_module: address(mscaCallFlowModule), _entityId: 3})); + + return (validationConfig, selectors, hooks); + } + + // 1. validation hook 1 + // 2. validation hook 2 + // 3. validation hook 3 + // 4. validation + // 5. pre exec (validation-assoc) hook 1: pre only + // 6. pre exec (validation-assoc) hook 2: post only (skipped) + // 7. pre exec (validation-assoc) hook 3: pre and post + // 8. pre exec (validation-assoc) hook 4: pre and post + // 9. pre exec (validation-assoc) hook 5: pre only + // 10. pre exec (validation-assoc) hook 6: post only (skipped) + // 11. pre exec (selector-assoc) hook 1: pre only + // 12. pre exec (selector-assoc) hook 2: post only (skipped) + // 13. pre exec (selector-assoc) hook 3: pre and post + // 14. pre exec (selector-assoc) hook 4: pre and post + // 15. pre exec (selector-assoc) hook 5: pre only + // 16. pre exec (selector-assoc) hook 6: post only (skipped) + // 17. exec + // 16. post exec (selector-assoc) hook 6: post only + // 15. post exec (selector-assoc) hook 5: pre only (skipped) + // 14. post exec (selector-assoc) hook 4: pre and post + // 13. post exec (selector-assoc) hook 3: pre and post + // 12. post exec (selector-assoc) hook 2: post only) + // 11. post exec (selector-assoc) hook 1: pre only (skipped) + // 10. post exec (validation-assoc) hook 6: post only + // 9. post exec (validation-assoc) hook 5: pre only (skipped) + // 8. post exec (validation-assoc) hook 4: pre and post + // 7. post exec (validation-assoc) hook 3: pre and post + // 6. post exec (validation-assoc) hook 2: post only + // 5. post exec (validation-assoc) hook 1: pre only (skipped) + function _checkInvokeOrderWithBothValidationAndSelectorAssocExecHooks() internal view { + uint32[] memory expectedOrder = new uint32[](21); + uint32[21] memory expectedOrderValues = + [uint32(1), 2, 3, 4, 5, 7, 8, 9, 11, 13, 14, 15, 17, 16, 14, 13, 12, 10, 8, 7, 6]; + + for (uint256 i = 0; i < expectedOrder.length; i++) { + expectedOrder[i] = expectedOrderValues[i]; + } + + uint256[] memory actualOrder = mscaCallFlowModule.getRecordedFunctionCalls(); + _assertArrsEqual(expectedOrder, actualOrder); + } + + // only validation is skipped compared to non-direct flow because the call is from the module that + // provides the validation function + // 1. validation hook 1 + // 2. validation hook 2 + // 3. validation hook 3 + // 4. validation (skipped) + // 5. pre exec (validation-assoc) hook 1: pre only + // 6. pre exec (validation-assoc) hook 2: post only (skipped) + // 7. pre exec (validation-assoc) hook 3: pre and post + // 8. pre exec (validation-assoc) hook 4: pre and post + // 9. pre exec (validation-assoc) hook 5: pre only + // 10. pre exec (validation-assoc) hook 6: post only (skipped) + // 11. pre exec (selector-assoc) hook 1: pre only + // 12. pre exec (selector-assoc) hook 2: post only (skipped) + // 13. pre exec (selector-assoc) hook 3: pre and post + // 14. pre exec (selector-assoc) hook 4: pre and post + // 15. pre exec (selector-assoc) hook 5: pre only + // 16. pre exec (selector-assoc) hook 6: post only (skipped) + // 17. exec + // 16. post exec (selector-assoc) hook 6: post only + // 15. post exec (selector-assoc) hook 5: pre only (skipped) + // 14. post exec (selector-assoc) hook 4: pre and post + // 13. post exec (selector-assoc) hook 3: pre and post + // 12. post exec (selector-assoc) hook 2: post only + // 11. post exec (selector-assoc) hook 1: pre only (skipped) + // 10. post exec (validation-assoc) hook 6: post only + // 9. post exec (validation-assoc) hook 5: pre only (skipped) + // 8. post exec (validation-assoc) hook 4: pre and post + // 7. post exec (validation-assoc) hook 3: pre and post + // 6. post exec (validation-assoc) hook 2: post only + // 5. post exec (validation-assoc) hook 1: pre only (skipped) + function _checkInvokeOrderDirectCallWithBothValidationAndSelectorAssocExecHooks() internal view { + uint32[] memory expectedOrder = new uint32[](20); + uint32[20] memory expectedOrderValues = + [uint32(1), 2, 3, 5, 7, 8, 9, 11, 13, 14, 15, 17, 16, 14, 13, 12, 10, 8, 7, 6]; + + for (uint256 i = 0; i < expectedOrder.length; i++) { + expectedOrder[i] = expectedOrderValues[i]; + } + + uint256[] memory actualOrder = mscaCallFlowModule.getRecordedFunctionCalls(); + + _assertArrsEqual(expectedOrder, actualOrder); + } + + // 1. validation hook 1 + // 2. validation hook 2 + // 3. validation hook 3 + // 4. validation + // 5. pre exec (validation-assoc) hook 1: pre only (skipped) + // 6. pre exec (validation-assoc) hook 2: post only (skipped) + // 7. pre exec (validation-assoc) hook 3: pre and post (skipped) + // 8. pre exec (validation-assoc) hook 4: pre and post (skipped) + // 9. pre exec (validation-assoc) hook 5: pre only (skipped) + // 10. pre exec (validation-assoc) hook 6: post only (skipped) + // 11. pre exec (selector-assoc) hook 1: pre only + // 12. pre exec (selector-assoc) hook 2: post only (skipped) + // 13. pre exec (selector-assoc) hook 3: pre and post + // 14. pre exec (selector-assoc) hook 4: pre and post + // 15. pre exec (selector-assoc) hook 5: pre only + // 16. pre exec (selector-assoc) hook 6: post only (skipped) + // 17. exec + // 16. post exec (selector-assoc) hook 6: post only + // 15. post exec (selector-assoc) hook 5: pre only (skipped) + // 14. post exec (selector-assoc) hook 4: pre and post + // 13. post exec (selector-assoc) hook 3: pre and post + // 12. post exec (selector-assoc) hook 2: post only) + // 11. post exec (selector-assoc) hook 1: pre only (skipped) + // 10. post exec (validation-assoc) hook 6: post only (skipped) + // 9. post exec (validation-assoc) hook 5: pre only (skipped) + // 8. post exec (validation-assoc) hook 4: pre and post (skipped) + // 7. post exec (validation-assoc) hook 3: pre and post (skipped) + // 6. post exec (validation-assoc) hook 2: post only (skipped) + // 5. post exec (validation-assoc) hook 1: pre only (skipped) + function _checkInvokeOrderWithOnlySelectorAssocExecHooks() internal view { + uint32[] memory expectedOrder = new uint32[](13); + uint32[13] memory expectedOrderValues = [uint32(1), 2, 3, 4, 11, 13, 14, 15, 17, 16, 14, 13, 12]; + + for (uint256 i = 0; i < expectedOrder.length; i++) { + expectedOrder[i] = expectedOrderValues[i]; + } + + uint256[] memory actualOrder = mscaCallFlowModule.getRecordedFunctionCalls(); + + _assertArrsEqual(expectedOrder, actualOrder); + } + + // only validation is skipped compared to non-direct flow because the call is from the module that + // provides the validation function + // 1. validation hook 1 + // 2. validation hook 2 + // 3. validation hook 3 + // 4. validation (skipped) + // 5. pre exec (validation-assoc) hook 1: pre only (skipped) + // 6. pre exec (validation-assoc) hook 2: post only (skipped) + // 7. pre exec (validation-assoc) hook 3: pre and post (skipped) + // 8. pre exec (validation-assoc) hook 4: pre and post (skipped) + // 9. pre exec (validation-assoc) hook 5: pre only (skipped) + // 10. pre exec (validation-assoc) hook 6: post only (skipped) + // 11. pre exec (selector-assoc) hook 1: pre only + // 12. pre exec (selector-assoc) hook 2: post only (skipped) + // 13. pre exec (selector-assoc) hook 3: pre and post + // 14. pre exec (selector-assoc) hook 4: pre and post + // 15. pre exec (selector-assoc) hook 5: pre only + // 16. pre exec (selector-assoc) hook 6: post only (skipped) + // 17. exec + // 16. post exec (selector-assoc) hook 6: post only + // 15. post exec (selector-assoc) hook 5: pre only (skipped) + // 14. post exec (selector-assoc) hook 4: pre and post + // 13. post exec (selector-assoc) hook 3: pre and post + // 12. post exec (selector-assoc) hook 2: post only) + // 11. post exec (selector-assoc) hook 1: pre only (skipped) + // 10. post exec (validation-assoc) hook 6: post only (skipped) + // 9. post exec (validation-assoc) hook 5: pre only (skipped) + // 8. post exec (validation-assoc) hook 4: pre and post (skipped) + // 7. post exec (validation-assoc) hook 3: pre and post (skipped) + // 6. post exec (validation-assoc) hook 2: post only (skipped) + // 5. post exec (validation-assoc) hook 1: pre only (skipped) + function _checkInvokeOrderDirectCallWithOnlySelectorAssocExecHooks() internal view { + uint32[] memory expectedOrder = new uint32[](12); + uint32[12] memory expectedOrderValues = [uint32(1), 2, 3, 11, 13, 14, 15, 17, 16, 14, 13, 12]; + + for (uint256 i = 0; i < expectedOrder.length; i++) { + expectedOrder[i] = expectedOrderValues[i]; + } + + uint256[] memory actualOrder = mscaCallFlowModule.getRecordedFunctionCalls(); + + _assertArrsEqual(expectedOrder, actualOrder); + } + + // Signature validation is only going to trigger the validation hooks and validation function, not the exec hooks + // 1. validation hook 1 + // 2. validation hook 2 + // 3. validation hook 3 + // 4. validation + // 5. pre exec (validation-assoc) hook 1: pre only (skipped) + // 6. pre exec (validation-assoc) hook 2: post only (skipped) + // 7. pre exec (validation-assoc) hook 3: pre and post (skipped) + // 8. pre exec (validation-assoc) hook 4: pre and post (skipped) + // 9. pre exec (validation-assoc) hook 5: pre only (skipped) + // 10. pre exec (validation-assoc) hook 6: post only (skipped) + // 11. pre exec (selector-assoc) hook 1: pre only (skipped) + // 12. pre exec (selector-assoc) hook 2: post only (skipped) + // 13. pre exec (selector-assoc) hook 3: pre and post (skipped) + // 14. pre exec (selector-assoc) hook 4: pre and post (skipped) + // 15. pre exec (selector-assoc) hook 5: pre only (skipped) + // 16. pre exec (selector-assoc) hook 6: post only (skipped) + // 17. exec (skipped) + // 16. post exec (selector-assoc) hook 6: post only (skipped) + // 15. post exec (selector-assoc) hook 5: pre only (skipped) + // 14. post exec (selector-assoc) hook 4: pre and post (skipped) + // 13. post exec (selector-assoc) hook 3: pre and post (skipped) + // 12. post exec (selector-assoc) hook 2: post only) (skipped) + // 11. post exec (selector-assoc) hook 1: pre only (skipped) + // 10. post exec (validation-assoc) hook 6: post only (skipped) + // 9. post exec (validation-assoc) hook 5: pre only (skipped) + // 8. post exec (validation-assoc) hook 4: pre and post (skipped) + // 7. post exec (validation-assoc) hook 3: pre and post (skipped) + // 6. post exec (validation-assoc) hook 2: post only (skipped) + // 5. post exec (validation-assoc) hook 1: pre only (skipped) + function _checkInvokeOrderSignatureValidation() internal view { + uint32[] memory expectedOrder = new uint32[](4); + uint32[4] memory expectedOrderValues = [uint32(1), 2, 3, 4]; + + for (uint256 i = 0; i < expectedOrder.length; i++) { + expectedOrder[i] = expectedOrderValues[i]; + } + + uint256[] memory actualOrder = mscaCallFlowModule.getRecordedFunctionCalls(); + + _assertArrsEqual(expectedOrder, actualOrder); + } + + function _assertArrsEqual(uint32[] memory expected, uint256[] memory actual) internal pure { + assertEq(expected.length, actual.length); + + for (uint256 i = 0; i < expected.length; i++) { + assertEq(expected[i], actual[i]); + } + } +} diff --git a/test/msca/6900/v0.8/ModuleManagement.t.sol b/test/msca/6900/v0.8/ModuleManagement.t.sol index 32b6d87..3ee9a72 100644 --- a/test/msca/6900/v0.8/ModuleManagement.t.sol +++ b/test/msca/6900/v0.8/ModuleManagement.t.sol @@ -19,17 +19,17 @@ pragma solidity 0.8.24; import {BaseMSCA} from "../../../../src/msca/6900/v0.8/account/BaseMSCA.sol"; -import {DIRECT_CALL_VALIDATION_ENTITY_ID} from "../../../../src/msca/6900/v0.8/common/Constants.sol"; -import {ExecutionManifest} from "../../../../src/msca/6900/v0.8/common/ModuleManifest.sol"; -import {ModuleEntity, ValidationConfig} from "../../../../src/msca/6900/v0.8/common/Types.sol"; +import {DIRECT_CALL_VALIDATION_ENTITY_ID} from "@erc6900/reference-implementation/helpers/Constants.sol"; +import {ExecutionManifest} from "@erc6900/reference-implementation/interfaces/IExecutionModule.sol"; +import {ModuleEntity, ValidationConfig} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol"; -import {IModule} from "../../../../src/msca/6900/v0.8/interfaces/IModule.sol"; -import {ModuleEntityLib} from "../../../../src/msca/6900/v0.8/libs/thirdparty/ModuleEntityLib.sol"; +import {IModule} from "@erc6900/reference-implementation/interfaces/IModule.sol"; +import {ModuleEntityLib} from "@erc6900/reference-implementation/libraries/ModuleEntityLib.sol"; -import {ValidationConfigLib} from "../../../../src/msca/6900/v0.8/libs/thirdparty/ValidationConfigLib.sol"; import {SingleSignerValidationModule} from "../../../../src/msca/6900/v0.8/modules/validation/SingleSignerValidationModule.sol"; import {TestLiquidityPool} from "../../../util/TestLiquidityPool.sol"; +import {ValidationConfigLib} from "@erc6900/reference-implementation/libraries/ValidationConfigLib.sol"; import {TestTokenModule} from "./TestTokenModule.sol"; import {AccountTestUtils} from "./utils/AccountTestUtils.sol"; @@ -40,10 +40,13 @@ import {IEntryPoint} from "@account-abstraction/contracts/interfaces/IEntryPoint import {UpgradableMSCA} from "../../../../src/msca/6900/v0.8/account/UpgradableMSCA.sol"; import {UpgradableMSCAFactory} from "../../../../src/msca/6900/v0.8/factories/UpgradableMSCAFactory.sol"; -import {ExecutionDataView, ValidationDataView} from "../../../../src/msca/6900/v0.8/common/Structs.sol"; -import {IModularAccount} from "../../../../src/msca/6900/v0.8/interfaces/IModularAccount.sol"; -import {HookConfigLib} from "../../../../src/msca/6900/v0.8/libs/HookConfigLib.sol"; +import {IModularAccount} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol"; +import { + ExecutionDataView, ValidationDataView +} from "@erc6900/reference-implementation/interfaces/IModularAccountView.sol"; + import {PackedUserOperation} from "@account-abstraction/contracts/interfaces/PackedUserOperation.sol"; +import {HookConfigLib} from "@erc6900/reference-implementation/libraries/HookConfigLib.sol"; import {console} from "forge-std/src/console.sol"; /// Tests for install/uninstall @@ -127,7 +130,7 @@ contract ModuleManagementTest is AccountTestUtils { vm.startPrank(address(1)); vm.expectRevert( abi.encodeWithSelector( - BaseMSCA.ValidationFunctionMissing.selector, + BaseMSCA.InvalidValidationFunction.selector, IModularAccount.installExecution.selector, ModuleEntityLib.pack(address(1), DIRECT_CALL_VALIDATION_ENTITY_ID) ) @@ -148,16 +151,7 @@ contract ModuleManagementTest is AccountTestUtils { (address(testTokenModule), testTokenModule.executionManifest(), abi.encode(1000)) ); PackedUserOperation memory userOp = buildPartialUserOp( - address(msca), - 0, - "0x", - vm.toString(installModuleCallData), - 10053353, - 103353, - 45484, - 516219199704, - 1130000000, - "0x" + address(msca), 0, "0x", vm.toString(installModuleCallData), 1000000, 1000000, 0, 1, 1, "0x" ); // no paymaster bytes32 userOpHash = entryPoint.getUserOpHash(userOp); diff --git a/test/msca/6900/v0.8/PermittedCall.t.sol b/test/msca/6900/v0.8/PermittedCall.t.sol index 8837569..c77a343 100644 --- a/test/msca/6900/v0.8/PermittedCall.t.sol +++ b/test/msca/6900/v0.8/PermittedCall.t.sol @@ -20,19 +20,21 @@ pragma solidity 0.8.24; import {BaseMSCA} from "../../../../src/msca/6900/v0.8/account/BaseMSCA.sol"; import {UpgradableMSCA} from "../../../../src/msca/6900/v0.8/account/UpgradableMSCA.sol"; -import {DIRECT_CALL_VALIDATION_ENTITY_ID} from "../../../../src/msca/6900/v0.8/common/Constants.sol"; + import {UpgradableMSCAFactory} from "../../../../src/msca/6900/v0.8/factories/UpgradableMSCAFactory.sol"; -import {ModuleEntityLib} from "../../../../src/msca/6900/v0.8/libs/thirdparty/ModuleEntityLib.sol"; +import {DIRECT_CALL_VALIDATION_ENTITY_ID} from "@erc6900/reference-implementation/helpers/Constants.sol"; + import {TestUtils} from "../../../util/TestUtils.sol"; +import {ModuleEntityLib} from "@erc6900/reference-implementation/libraries/ModuleEntityLib.sol"; -import {ModuleEntity, ValidationConfig} from "../../../../src/msca/6900/v0.8/common/Types.sol"; -import {ValidationConfigLib} from "../../../../src/msca/6900/v0.8/libs/thirdparty/ValidationConfigLib.sol"; import {SingleSignerValidationModule} from "../../../../src/msca/6900/v0.8/modules/validation/SingleSignerValidationModule.sol"; import {FooBarModule} from "./FooBarModule.sol"; import {TestPermittedCallModule} from "./TestPermittedCallModule.sol"; import {EntryPoint} from "@account-abstraction/contracts/core/EntryPoint.sol"; import {IEntryPoint} from "@account-abstraction/contracts/interfaces/IEntryPoint.sol"; +import {ModuleEntity, ValidationConfig} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol"; +import {ValidationConfigLib} from "@erc6900/reference-implementation/libraries/ValidationConfigLib.sol"; contract PermittedCallTest is TestUtils { IEntryPoint private entryPoint = new EntryPoint(); @@ -94,7 +96,7 @@ contract PermittedCallTest is TestUtils { vm.expectRevert( abi.encodeWithSelector( - BaseMSCA.ValidationFunctionMissing.selector, + BaseMSCA.InvalidValidationFunction.selector, FooBarModule.bar.selector, ModuleEntityLib.pack(address(permittedCallModule), DIRECT_CALL_VALIDATION_ENTITY_ID) ) diff --git a/test/msca/6900/v0.8/SelfCallRule.t.sol b/test/msca/6900/v0.8/SelfCallRule.t.sol index 3609fc3..4d994d3 100644 --- a/test/msca/6900/v0.8/SelfCallRule.t.sol +++ b/test/msca/6900/v0.8/SelfCallRule.t.sol @@ -19,14 +19,15 @@ pragma solidity 0.8.24; import {BaseMSCA} from "../../../../src/msca/6900/v0.8/account/BaseMSCA.sol"; -import {Call} from "../../../../src/msca/6900/v0.8/common/Structs.sol"; -import {ModuleEntity, ValidationConfig} from "../../../../src/msca/6900/v0.8/common/Types.sol"; -import {IModularAccount} from "../../../../src/msca/6900/v0.8/interfaces/IModularAccount.sol"; -import {ModuleEntityLib} from "../../../../src/msca/6900/v0.8/libs/thirdparty/ModuleEntityLib.sol"; -import {ValidationConfigLib} from "../../../../src/msca/6900/v0.8/libs/thirdparty/ValidationConfigLib.sol"; + +import {Call, IModularAccount} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol"; + import {SingleSignerValidationModule} from "../../../../src/msca/6900/v0.8/modules/validation/SingleSignerValidationModule.sol"; import {FooBarModule} from "./FooBarModule.sol"; +import {ModuleEntity, ValidationConfig} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol"; +import {ModuleEntityLib} from "@erc6900/reference-implementation/libraries/ModuleEntityLib.sol"; +import {ValidationConfigLib} from "@erc6900/reference-implementation/libraries/ValidationConfigLib.sol"; import {UpgradableMSCA} from "../../../../src/msca/6900/v0.8/account/UpgradableMSCA.sol"; import {UpgradableMSCAFactory} from "../../../../src/msca/6900/v0.8/factories/UpgradableMSCAFactory.sol"; @@ -110,7 +111,7 @@ contract SelfCallRuleTest is AccountTestUtils { 0, "AA23 reverted", abi.encodeWithSelector( - BaseMSCA.ValidationFunctionMissing.selector, fooBarModule.bar.selector, ownerValidation + BaseMSCA.InvalidValidationFunction.selector, fooBarModule.bar.selector, ownerValidation ) ) ); @@ -136,7 +137,7 @@ contract SelfCallRuleTest is AccountTestUtils { 0, "AA23 reverted", abi.encodeWithSelector( - BaseMSCA.ValidationFunctionMissing.selector, IAccountExecute.executeUserOp.selector, ownerValidation + BaseMSCA.InvalidValidationFunction.selector, fooBarModule.bar.selector, ownerValidation ) ) ); @@ -162,7 +163,7 @@ contract SelfCallRuleTest is AccountTestUtils { 0, "AA23 reverted", abi.encodeWithSelector( - BaseMSCA.ValidationFunctionMissing.selector, IModularAccount.execute.selector, ownerValidation + BaseMSCA.InvalidValidationFunction.selector, IModularAccount.execute.selector, ownerValidation ) ) ); @@ -189,7 +190,7 @@ contract SelfCallRuleTest is AccountTestUtils { 0, "AA23 reverted", abi.encodeWithSelector( - BaseMSCA.ValidationFunctionMissing.selector, IModularAccount.executeBatch.selector, ownerValidation + BaseMSCA.InvalidValidationFunction.selector, IModularAccount.executeBatch.selector, ownerValidation ) ) ); @@ -433,7 +434,7 @@ contract SelfCallRuleTest is AccountTestUtils { vm.startPrank(ownerAddr); vm.expectRevert( abi.encodeWithSelector( - BaseMSCA.ValidationFunctionMissing.selector, FooBarModule.bar.selector, ownerValidation + BaseMSCA.InvalidValidationFunction.selector, FooBarModule.bar.selector, ownerValidation ) ); msca.executeWithRuntimeValidation(callData, authorizationData); @@ -446,7 +447,7 @@ contract SelfCallRuleTest is AccountTestUtils { vm.startPrank(ownerAddr); vm.expectRevert( abi.encodeWithSelector( - BaseMSCA.ValidationFunctionMissing.selector, IModularAccount.execute.selector, ownerValidation + BaseMSCA.InvalidValidationFunction.selector, IModularAccount.execute.selector, ownerValidation ) ); msca.executeWithRuntimeValidation(callData, authorizationData); @@ -461,7 +462,7 @@ contract SelfCallRuleTest is AccountTestUtils { vm.startPrank(ownerAddr); vm.expectRevert( abi.encodeWithSelector( - BaseMSCA.ValidationFunctionMissing.selector, IModularAccount.executeBatch.selector, ownerValidation + BaseMSCA.InvalidValidationFunction.selector, IModularAccount.executeBatch.selector, ownerValidation ) ); msca.executeWithRuntimeValidation(callData, authorizationData); diff --git a/test/msca/6900/v0.8/TestPermittedCallModule.sol b/test/msca/6900/v0.8/TestPermittedCallModule.sol index 0629246..ff807bc 100644 --- a/test/msca/6900/v0.8/TestPermittedCallModule.sol +++ b/test/msca/6900/v0.8/TestPermittedCallModule.sol @@ -18,11 +18,14 @@ */ pragma solidity 0.8.24; -import {ExecutionManifest, ManifestExecutionFunction} from "../../../../src/msca/6900/v0.8/common/ModuleManifest.sol"; +import { + ExecutionManifest, + ManifestExecutionFunction +} from "@erc6900/reference-implementation/interfaces/IExecutionModule.sol"; -import {IExecutionModule} from "../../../../src/msca/6900/v0.8/interfaces/IExecutionModule.sol"; import {BaseModule} from "../../../../src/msca/6900/v0.8/modules/BaseModule.sol"; import {FooBarModule} from "./FooBarModule.sol"; +import {IExecutionModule} from "@erc6900/reference-implementation/interfaces/IExecutionModule.sol"; /** * @dev Module for tests only. This module demos permitted call. diff --git a/test/msca/6900/v0.8/TestTokenModule.sol b/test/msca/6900/v0.8/TestTokenModule.sol index 8912373..15796fb 100644 --- a/test/msca/6900/v0.8/TestTokenModule.sol +++ b/test/msca/6900/v0.8/TestTokenModule.sol @@ -25,16 +25,16 @@ import { ExecutionManifest, ManifestExecutionFunction, ManifestExecutionHook -} from "../../../../src/msca/6900/v0.8/common/ModuleManifest.sol"; +} from "@erc6900/reference-implementation/interfaces/IExecutionModule.sol"; -import {IExecutionHookModule} from "../../../../src/msca/6900/v0.8/interfaces/IExecutionHookModule.sol"; +import {IExecutionHookModule} from "@erc6900/reference-implementation/interfaces/IExecutionHookModule.sol"; -import {IExecutionModule} from "../../../../src/msca/6900/v0.8/interfaces/IExecutionModule.sol"; -import {IModule} from "../../../../src/msca/6900/v0.8/interfaces/IModule.sol"; -import {IValidationHookModule} from "../../../../src/msca/6900/v0.8/interfaces/IValidationHookModule.sol"; -import {IValidationModule} from "../../../../src/msca/6900/v0.8/interfaces/IValidationModule.sol"; import {BaseModule} from "../../../../src/msca/6900/v0.8/modules/BaseModule.sol"; import {PackedUserOperation} from "@account-abstraction/contracts/interfaces/PackedUserOperation.sol"; +import {IExecutionModule} from "@erc6900/reference-implementation/interfaces/IExecutionModule.sol"; +import {IModule} from "@erc6900/reference-implementation/interfaces/IModule.sol"; +import {IValidationHookModule} from "@erc6900/reference-implementation/interfaces/IValidationHookModule.sol"; +import {IValidationModule} from "@erc6900/reference-implementation/interfaces/IValidationModule.sol"; import {console} from "forge-std/src/console.sol"; /** @@ -130,6 +130,7 @@ contract TestTokenModule is bytes calldata data, bytes calldata authorization ) external pure override { + (authorization); (sender, value, data); if (entityId == uint32(EntityId.PRE_VALIDATION_HOOK_PASS1)) { return; @@ -148,7 +149,7 @@ contract TestTokenModule is bytes calldata data, bytes calldata authorization ) external pure override { - (sender, value, data, authorization); + (account, sender, value, data, authorization); if (entityId == uint8(EntityId.VALIDATION)) { return; } @@ -161,6 +162,7 @@ contract TestTokenModule is pure returns (bytes4) { + (account); revert NotImplementedFunction(msg.sig, entityId); } @@ -200,7 +202,7 @@ contract TestTokenModule is function preSignatureValidationHook(uint32 entityId, address sender, bytes32 hash, bytes calldata signature) external - view + pure override { (entityId, sender, hash, signature); diff --git a/test/msca/6900/v0.8/UpgradableMSCA.t.sol b/test/msca/6900/v0.8/UpgradableMSCA.t.sol index 223f37f..4f7a12f 100644 --- a/test/msca/6900/v0.8/UpgradableMSCA.t.sol +++ b/test/msca/6900/v0.8/UpgradableMSCA.t.sol @@ -35,34 +35,35 @@ import { ExecutionManifest, ManifestExecutionFunction, ManifestExecutionHook -} from "../../../../src/msca/6900/v0.8/common/ModuleManifest.sol"; -import {ModuleEntity, ValidationConfig} from "../../../../src/msca/6900/v0.8/common/Types.sol"; +} from "@erc6900/reference-implementation/interfaces/IExecutionModule.sol"; + import {UpgradableMSCAFactory} from "../../../../src/msca/6900/v0.8/factories/UpgradableMSCAFactory.sol"; +import {ModuleEntity, ValidationConfig} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol"; -import {IExecutionHookModule} from "../../../../src/msca/6900/v0.8/interfaces/IExecutionHookModule.sol"; -import {IModularAccount} from "../../../../src/msca/6900/v0.8/interfaces/IModularAccount.sol"; +import {IExecutionHookModule} from "@erc6900/reference-implementation/interfaces/IExecutionHookModule.sol"; +import {Call, IModularAccount} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol"; -import {IValidationHookModule} from "../../../../src/msca/6900/v0.8/interfaces/IValidationHookModule.sol"; -import {IValidationModule} from "../../../../src/msca/6900/v0.8/interfaces/IValidationModule.sol"; -import {ModuleEntityLib} from "../../../../src/msca/6900/v0.8/libs/thirdparty/ModuleEntityLib.sol"; +import {IValidationHookModule} from "@erc6900/reference-implementation/interfaces/IValidationHookModule.sol"; +import {IValidationModule} from "@erc6900/reference-implementation/interfaces/IValidationModule.sol"; +import {ModuleEntityLib} from "@erc6900/reference-implementation/libraries/ModuleEntityLib.sol"; -import {ValidationConfigLib} from "../../../../src/msca/6900/v0.8/libs/thirdparty/ValidationConfigLib.sol"; import {SingleSignerValidationModule} from "../../../../src/msca/6900/v0.8/modules/validation/SingleSignerValidationModule.sol"; import {TestERC1155} from "../../../util/TestERC1155.sol"; import {TestERC721} from "../../../util/TestERC721.sol"; +import {ValidationConfigLib} from "@erc6900/reference-implementation/libraries/ValidationConfigLib.sol"; import {TestLiquidityPool} from "../../../util/TestLiquidityPool.sol"; import {AccountTestUtils} from "./utils/AccountTestUtils.sol"; import {EntryPoint} from "@account-abstraction/contracts/core/EntryPoint.sol"; -import {Call, ValidationDataView} from "../../../../src/msca/6900/v0.8/common/Structs.sol"; +import {ValidationDataView} from "@erc6900/reference-implementation/interfaces/IModularAccountView.sol"; -import {HookConfigLib} from "../../../../src/msca/6900/v0.8/libs/HookConfigLib.sol"; import {MockModule} from "./helpers/MockModule.sol"; import {IAccountExecute} from "@account-abstraction/contracts/interfaces/IAccountExecute.sol"; import {IEntryPoint} from "@account-abstraction/contracts/interfaces/IEntryPoint.sol"; import {PackedUserOperation} from "@account-abstraction/contracts/interfaces/PackedUserOperation.sol"; +import {HookConfigLib} from "@erc6900/reference-implementation/libraries/HookConfigLib.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; // We use UpgradableMSCA (that inherits from UpgradableMSCA) because it has some convenience functions @@ -298,7 +299,7 @@ contract UpgradableMSCATest is AccountTestUtils { vm.startPrank(address(entryPoint)); vm.expectRevert( abi.encodeWithSelector( - BaseMSCA.ValidationFunctionMissing.selector, + BaseMSCA.InvalidValidationFunction.selector, bytes4(keccak256("execute(address,uint256,bytes)")), ownerValidation ) @@ -613,6 +614,12 @@ contract UpgradableMSCATest is AccountTestUtils { MockModule mockModule = new MockModule( execManifest, SIG_VALIDATION_SUCCEEDED, true, true, bytes(""), true, SIG_VALIDATION_SUCCEEDED, true ); + bool[] memory permissions = new bool[](1); + address[] memory modulesAddr = new address[](1); + permissions[0] = true; + modulesAddr[0] = address(mockModule); + vm.prank(factoryOwner); + factory.setModules(modulesAddr, permissions); ValidationConfig validationConfig = ValidationConfigLib.pack(ownerValidation, true, true, true); bytes[] memory hooks = new bytes[](1); hooks[0] = abi.encodePacked(HookConfigLib.packValidationHook(address(mockModule), uint32(0)), ""); @@ -1018,47 +1025,15 @@ contract UpgradableMSCATest is AccountTestUtils { } function testReplaceValidationModule() public { - (ownerAddr, eoaPrivateKey) = makeAddrAndKey("test_replaceValidationModule"); - SingleSignerValidationModule validationV1 = singleSignerValidationModule; - SingleSignerValidationModule validationV2 = singleSignerValidationModule2; uint32 validationEntityIdV1 = 10; uint32 validationEntityIdV2 = 11; - ModuleEntity moduleEntityV1 = ModuleEntityLib.pack(address(validationV1), validationEntityIdV1); - ModuleEntity moduleEntityV2 = ModuleEntityLib.pack(address(validationV2), validationEntityIdV2); - - MockModule mockPreValAndExecutionHookModule = new MockModule( - ExecutionManifest({ - executionFunctions: new ManifestExecutionFunction[](0), - executionHooks: new ManifestExecutionHook[](0), - interfaceIds: new bytes4[](0) - }), - SIG_VALIDATION_SUCCEEDED, - true, - true, - bytes(""), - true, - SIG_VALIDATION_SUCCEEDED, - true - ); - // setup a validation with pre validation and execution hooks - bytes[] memory hooksForValidationV1 = new bytes[](2); - hooksForValidationV1[0] = abi.encodePacked( - HookConfigLib.packValidationHook(address(mockPreValAndExecutionHookModule), validationEntityIdV1) - ); - hooksForValidationV1[1] = abi.encodePacked( - HookConfigLib.packExecHook(address(mockPreValAndExecutionHookModule), validationEntityIdV1, true, true) - ); - ValidationConfig validationConfig = ValidationConfigLib.pack(moduleEntityV1, true, true, true); - bytes memory initializingData = abi.encode( - validationConfig, new bytes4[](0), abi.encode(validationEntityIdV1, ownerAddr), hooksForValidationV1 - ); - UpgradableMSCA msca = factory.createAccountWithValidation(addressToBytes32(ownerAddr), salt, initializingData); - vm.deal(address(msca), 10 ether); - address target = vm.addr(123); - uint256 amount = 1 ether; + ModuleEntity moduleEntityV1 = ModuleEntityLib.pack(address(singleSignerValidationModule), validationEntityIdV1); + ModuleEntity moduleEntityV2 = ModuleEntityLib.pack(address(singleSignerValidationModule2), validationEntityIdV2); + (UpgradableMSCA msca, address mockPreValAndExecutionHookModule) = + _createAccountForReplaceValidationModule(validationEntityIdV1, moduleEntityV1); vm.startPrank(address(msca)); - bytes memory callData = abi.encodeCall(IModularAccount.execute, (target, amount, bytes(""))); + bytes memory callData = abi.encodeCall(IModularAccount.execute, (vm.addr(123), 1 ether, bytes(""))); vm.expectEmit(true, true, true, true); emit ReceivedCall( abi.encodeCall( @@ -1079,15 +1054,14 @@ contract UpgradableMSCATest is AccountTestUtils { msca.executeWithRuntimeValidation( callData, encodeSignature(new PreValidationHookData[](0), moduleEntityV1, "", true) ); - assertEq(target.balance, amount); + assertEq(vm.addr(123).balance, 1 ether); // upgrade module by batching uninstall + install calls bytes[] memory hooksForValidationV2 = new bytes[](2); - hooksForValidationV2[0] = abi.encodePacked( - HookConfigLib.packValidationHook(address(mockPreValAndExecutionHookModule), validationEntityIdV2) - ); + hooksForValidationV2[0] = + abi.encodePacked(HookConfigLib.packValidationHook(mockPreValAndExecutionHookModule, validationEntityIdV2)); hooksForValidationV2[1] = abi.encodePacked( - HookConfigLib.packExecHook(address(mockPreValAndExecutionHookModule), validationEntityIdV2, true, true) + HookConfigLib.packExecHook(mockPreValAndExecutionHookModule, validationEntityIdV2, true, true) ); Call[] memory calls = new Call[](2); @@ -1120,12 +1094,12 @@ contract UpgradableMSCATest is AccountTestUtils { // old validation should fail vm.expectRevert( abi.encodePacked( - BaseMSCA.ValidationFunctionMissing.selector, + BaseMSCA.InvalidValidationFunction.selector, abi.encode(IModularAccount.execute.selector, moduleEntityV1) ) ); msca.executeWithRuntimeValidation( - abi.encodeCall(IModularAccount.execute, (target, amount, "")), + abi.encodeCall(IModularAccount.execute, (vm.addr(123), 1 ether, "")), encodeSignature(new PreValidationHookData[](0), moduleEntityV1, "", true) ); @@ -1150,10 +1124,51 @@ contract UpgradableMSCATest is AccountTestUtils { msca.executeWithRuntimeValidation( callData, encodeSignature(new PreValidationHookData[](0), moduleEntityV2, "", true) ); - assertEq(target.balance, 2 * amount); + assertEq(vm.addr(123).balance, 2 ether); vm.stopPrank(); } + function _createAccountForReplaceValidationModule(uint32 validationEntityIdV1, ModuleEntity moduleEntityV1) + internal + returns (UpgradableMSCA msca, address mockPreValAndExecutionHookModuleAddr) + { + (ownerAddr, eoaPrivateKey) = makeAddrAndKey("test_replaceValidationModule"); + MockModule mockPreValAndExecutionHookModule = new MockModule( + ExecutionManifest({ + executionFunctions: new ManifestExecutionFunction[](0), + executionHooks: new ManifestExecutionHook[](0), + interfaceIds: new bytes4[](0) + }), + SIG_VALIDATION_SUCCEEDED, + true, + true, + bytes(""), + true, + SIG_VALIDATION_SUCCEEDED, + true + ); + bool[] memory permissions = new bool[](1); + address[] memory modulesAddr = new address[](1); + permissions[0] = true; + modulesAddr[0] = address(mockPreValAndExecutionHookModule); + // setup a validation with pre validation and execution hooks + bytes[] memory hooksForValidationV1 = new bytes[](2); + hooksForValidationV1[0] = + abi.encodePacked(HookConfigLib.packValidationHook(modulesAddr[0], validationEntityIdV1)); + hooksForValidationV1[1] = + abi.encodePacked(HookConfigLib.packExecHook(modulesAddr[0], validationEntityIdV1, true, true)); + vm.prank(factoryOwner); + factory.setModules(modulesAddr, permissions); + + ValidationConfig validationConfig = ValidationConfigLib.pack(moduleEntityV1, true, true, true); + bytes memory initializingData = abi.encode( + validationConfig, new bytes4[](0), abi.encode(validationEntityIdV1, ownerAddr), hooksForValidationV1 + ); + msca = factory.createAccountWithValidation(addressToBytes32(ownerAddr), salt, initializingData); + vm.deal(address(msca), 10 ether); + mockPreValAndExecutionHookModuleAddr = modulesAddr[0]; + } + function testAccountId() public { (ownerAddr, eoaPrivateKey) = makeAddrAndKey("testAccountId"); ownerValidation = ModuleEntityLib.pack(address(singleSignerValidationModule), uint32(0)); @@ -1164,6 +1179,76 @@ contract UpgradableMSCATest is AccountTestUtils { assertEq(msca.accountId(), "circle.msca.2.0.0"); } + function testMaxLimitValidationHooks() public { + MockModule[] memory modules = new MockModule[](300); + bool[] memory permissions = new bool[](300); + address[] memory modulesAddr = new address[](300); + for (uint256 i = 0; i < 300; i++) { + modules[i] = new MockModule( + ExecutionManifest({ + executionFunctions: new ManifestExecutionFunction[](0), + executionHooks: new ManifestExecutionHook[](0), + interfaceIds: new bytes4[](0) + }), + SIG_VALIDATION_SUCCEEDED, + true, + true, + bytes(""), + true, + SIG_VALIDATION_SUCCEEDED, + true + ); + modulesAddr[i] = address(modules[i]); + permissions[i] = true; + } + vm.prank(factoryOwner); + factory.setModules(modulesAddr, permissions); + bytes[] memory hooks = new bytes[](300); + for (uint256 i = 0; i < 300; i++) { + hooks[i] = abi.encodePacked(HookConfigLib.packValidationHook(address(modules[i]), uint32(i))); + } + ValidationConfig validationConfig = ValidationConfigLib.pack(ownerValidation, true, true, true); + bytes memory initializingData = + abi.encode(validationConfig, new bytes4[](0), abi.encode(uint32(0), ownerAddr), hooks); + vm.expectRevert(abi.encodeWithSelector(BaseMSCA.MaxHooksExceeded.selector)); + factory.createAccountWithValidation(addressToBytes32(ownerAddr), salt, initializingData); + } + + function testMaxLimitExecutionHooks() public { + MockModule[] memory modules = new MockModule[](300); + bool[] memory permissions = new bool[](300); + address[] memory modulesAddr = new address[](300); + for (uint256 i = 0; i < 300; i++) { + modules[i] = new MockModule( + ExecutionManifest({ + executionFunctions: new ManifestExecutionFunction[](0), + executionHooks: new ManifestExecutionHook[](0), + interfaceIds: new bytes4[](0) + }), + SIG_VALIDATION_SUCCEEDED, + true, + true, + bytes(""), + true, + SIG_VALIDATION_SUCCEEDED, + true + ); + modulesAddr[i] = address(modules[i]); + permissions[i] = true; + } + vm.prank(factoryOwner); + factory.setModules(modulesAddr, permissions); + bytes[] memory hooks = new bytes[](300); + for (uint256 i = 0; i < 300; i++) { + hooks[i] = abi.encodePacked(HookConfigLib.packExecHook(address(modules[i]), uint32(i), false, false)); + } + ValidationConfig validationConfig = ValidationConfigLib.pack(ownerValidation, true, true, true); + bytes memory initializingData = + abi.encode(validationConfig, new bytes4[](0), abi.encode(uint32(0), ownerAddr), hooks); + vm.expectRevert(abi.encodeWithSelector(BaseMSCA.MaxHooksExceeded.selector)); + factory.createAccountWithValidation(addressToBytes32(ownerAddr), salt, initializingData); + } + function _installMultipleOwnerValidations() internal returns (UpgradableMSCA msca) { ownerValidation = ModuleEntityLib.pack(address(singleSignerValidationModule), uint32(0)); ValidationConfig validationConfig = ValidationConfigLib.pack(ownerValidation, true, true, true); diff --git a/test/msca/6900/v0.8/UpgradableMSCAFactory.t.sol b/test/msca/6900/v0.8/UpgradableMSCAFactory.t.sol index 2ecd409..4e1cef4 100644 --- a/test/msca/6900/v0.8/UpgradableMSCAFactory.t.sol +++ b/test/msca/6900/v0.8/UpgradableMSCAFactory.t.sol @@ -19,15 +19,16 @@ pragma solidity 0.8.24; import {UpgradableMSCA} from "../../../../src/msca/6900/v0.8/account/UpgradableMSCA.sol"; -import {ModuleEntity} from "../../../../src/msca/6900/v0.8/common/Types.sol"; -import {ValidationConfig} from "../../../../src/msca/6900/v0.8/common/Types.sol"; + import {UpgradableMSCAFactory} from "../../../../src/msca/6900/v0.8/factories/UpgradableMSCAFactory.sol"; -import {IModularAccount} from "../../../../src/msca/6900/v0.8/interfaces/IModularAccount.sol"; -import {ModuleEntityLib} from "../../../../src/msca/6900/v0.8/libs/thirdparty/ModuleEntityLib.sol"; -import {ValidationConfigLib} from "../../../../src/msca/6900/v0.8/libs/thirdparty/ValidationConfigLib.sol"; +import {IModularAccount} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol"; + import {SingleSignerValidationModule} from "../../../../src/msca/6900/v0.8/modules/validation/SingleSignerValidationModule.sol"; import {TestLiquidityPool} from "../../../util/TestLiquidityPool.sol"; +import {ModuleEntity, ValidationConfig} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol"; +import {ModuleEntityLib} from "@erc6900/reference-implementation/libraries/ModuleEntityLib.sol"; +import {ValidationConfigLib} from "@erc6900/reference-implementation/libraries/ValidationConfigLib.sol"; import {AccountTestUtils} from "./utils/AccountTestUtils.sol"; import {EntryPoint} from "@account-abstraction/contracts/core/EntryPoint.sol"; @@ -109,7 +110,8 @@ contract UpgradableMSCAFactoryTest is AccountTestUtils { emit SignerTransferred(counterfactualAddr, uint32(0), ownerAddr, address(0)); // emit ModuleInstalled first vm.expectEmit(true, false, false, true); - emit ValidationInstalled(ownerValidation.module(), uint32(0)); + (address moduleAddr,) = ownerValidation.unpack(); + emit ValidationInstalled(moduleAddr, uint32(0)); // emit UpgradableMSCAInitialized vm.expectEmit(true, true, false, false); emit UpgradableMSCAInitialized(counterfactualAddr, address(entryPoint)); diff --git a/test/msca/6900/v0.8/WalletStorageLib.t.sol b/test/msca/6900/v0.8/WalletStorageLib.t.sol index fad1537..6c68ad1 100644 --- a/test/msca/6900/v0.8/WalletStorageLib.t.sol +++ b/test/msca/6900/v0.8/WalletStorageLib.t.sol @@ -22,7 +22,7 @@ import {TestUtils} from "../../../util/TestUtils.sol"; import {console} from "forge-std/src/console.sol"; contract WalletStorageLibTest is TestUtils { - function testWalletStorageSlot() public { + function testWalletStorageSlot() public pure { bytes32 hash = keccak256(abi.encode(uint256(keccak256(abi.encode("circle.msca.v0_8.storage"))) - 1)); console.logString("hash: "); console.logBytes32(hash); diff --git a/test/msca/6900/v0.8/helpers/DirectCallModule.sol b/test/msca/6900/v0.8/helpers/DirectCallModule.sol index 57f6021..bcbf85e 100644 --- a/test/msca/6900/v0.8/helpers/DirectCallModule.sol +++ b/test/msca/6900/v0.8/helpers/DirectCallModule.sol @@ -18,9 +18,9 @@ */ pragma solidity 0.8.24; -import {IExecutionHookModule} from "../../../../../src/msca/6900/v0.8/interfaces/IExecutionHookModule.sol"; -import {IModularAccount} from "../../../../../src/msca/6900/v0.8/interfaces/IModularAccount.sol"; import {BaseModule} from "../../../../../src/msca/6900/v0.8/modules/BaseModule.sol"; +import {IExecutionHookModule} from "@erc6900/reference-implementation/interfaces/IExecutionHookModule.sol"; +import {IModularAccount} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol"; import {IERC165} from "@openzeppelin/contracts/interfaces/IERC165.sol"; /// @notice Inspired by 6900 reference implementation with some modifications diff --git a/test/msca/6900/v0.8/helpers/MSCACallFlowModule.sol b/test/msca/6900/v0.8/helpers/MSCACallFlowModule.sol new file mode 100644 index 0000000..73984c3 --- /dev/null +++ b/test/msca/6900/v0.8/helpers/MSCACallFlowModule.sol @@ -0,0 +1,155 @@ +/* + * Copyright 2024 Circle Internet Group, Inc. All rights reserved. + + * SPDX-License-Identifier: GPL-3.0-or-later + + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +pragma solidity 0.8.24; + +import {BaseModule} from "../../../../../src/msca/6900/v0.8/modules/BaseModule.sol"; +import {IExecutionHookModule} from "@erc6900/reference-implementation/interfaces/IExecutionHookModule.sol"; +import { + ExecutionManifest, + IExecutionModule, + ManifestExecutionFunction, + ManifestExecutionHook +} from "@erc6900/reference-implementation/interfaces/IExecutionModule.sol"; +import {IValidationHookModule} from "@erc6900/reference-implementation/interfaces/IValidationHookModule.sol"; +import {IValidationModule} from "@erc6900/reference-implementation/interfaces/IValidationModule.sol"; +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; +import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; +import {Test} from "forge-std/src/Test.sol"; + +/// @notice Inspired by Alchemy's implementation with modifications. +// Used within MSCACallFlowTest, see that file for details on usage. +contract MSCACallFlowModule is + IValidationModule, + IValidationHookModule, + IExecutionModule, + IExecutionHookModule, + BaseModule, + Test +{ + // Stored as a uint256 to make it easier to do the VM staticcall storage writes + uint256[] public recordedFunctionCalls; + + error VMStaticCallFailed(); + + function validateUserOp(uint32 entityId, PackedUserOperation calldata, bytes32) external returns (uint256) { + recordedFunctionCalls.push(uint256(entityId)); + return 0; + } + + function validateRuntime(address, uint32 entityId, address, uint256, bytes calldata, bytes calldata) external { + recordedFunctionCalls.push(uint256(entityId)); + } + + function validateSignature(address, uint32 entityId, address, bytes32, bytes calldata) + external + view + returns (bytes4) + { + recordCallInView(entityId); + return IERC1271.isValidSignature.selector; + } + + function preUserOpValidationHook(uint32 entityId, PackedUserOperation calldata, bytes32) + external + returns (uint256) + { + recordedFunctionCalls.push(uint256(entityId)); + return 0; + } + + function preRuntimeValidationHook(uint32 entityId, address, uint256, bytes calldata, bytes calldata) external { + recordedFunctionCalls.push(uint256(entityId)); + } + + function preSignatureValidationHook(uint32 entityId, address, bytes32, bytes calldata) external view { + recordCallInView(entityId); + } + + function preExecutionHook(uint32 entityId, address, uint256, bytes calldata) external returns (bytes memory) { + recordedFunctionCalls.push(uint256(entityId)); + return ""; + } + + function postExecutionHook(uint32 entityId, bytes calldata) external { + recordedFunctionCalls.push(uint256(entityId)); + } + + // solhint-disable-next-line no-empty-blocks + function onInstall(bytes calldata) external override {} + + // solhint-disable-next-line no-empty-blocks + function onUninstall(bytes calldata) external override {} + + function foo(uint32 index) external { + recordedFunctionCalls.push(index); + } + + function getRecordedFunctionCalls() external view returns (uint256[] memory) { + return recordedFunctionCalls; + } + + function moduleId() external pure returns (string memory) { + return "circle.msca-call-flow-module.2.0.0"; + } + + // Does not return any execution hooks, the caller should add any requested execution hooks prior to calling + // `installExecution` with the desired entity IDs. + function executionManifest() external pure returns (ExecutionManifest memory) { + ManifestExecutionFunction[] memory executionFunctions = new ManifestExecutionFunction[](1); + executionFunctions[0] = ManifestExecutionFunction({ + executionSelector: this.foo.selector, + skipRuntimeValidation: false, + allowGlobalValidation: false + }); + + return ExecutionManifest({ + executionFunctions: executionFunctions, + executionHooks: new ManifestExecutionHook[](0), + interfaceIds: new bytes4[](0) + }); + } + + // Normally we can't write to storage within a staticcall, so the signature validation and signature validation + // hooks would be unable to record their access order. However, we can use the VM cheat code to write to + // storage even in a view context, so we can record the order of function calls. + function recordCallInView(uint32 entityId) private view { + uint256 slotU; + bytes32 slot; + // solhint-disable-next-line no-inline-assembly + assembly ("memory-safe") { + slotU := recordedFunctionCalls.slot + mstore(0x0, slotU) + slot := mload(0x0) + } + // CommonBase.VM_ADDRESS + (bool success, bytes memory returnData) = VM_ADDRESS.staticcall(abi.encodeCall(vm.load, (address(this), slot))); + if (!success) revert VMStaticCallFailed(); + + uint256 length = uint256(bytes32(returnData)); + (success,) = VM_ADDRESS.staticcall(abi.encodeCall(vm.store, (address(this), slot, bytes32(length + 1)))); + if (!success) revert VMStaticCallFailed(); + + bytes32 dataSlot = keccak256(abi.encode(slot)); + (success,) = VM_ADDRESS.staticcall( + abi.encodeCall(vm.store, (address(this), bytes32(uint256(dataSlot) + length), bytes32(uint256(entityId)))) + ); + if (!success) revert VMStaticCallFailed(); + } +} diff --git a/test/msca/6900/v0.8/helpers/MockModule.sol b/test/msca/6900/v0.8/helpers/MockModule.sol index ebb7d53..9a7e723 100644 --- a/test/msca/6900/v0.8/helpers/MockModule.sol +++ b/test/msca/6900/v0.8/helpers/MockModule.sol @@ -19,13 +19,14 @@ pragma solidity 0.8.24; import {EIP1271_INVALID_SIGNATURE, EIP1271_VALID_SIGNATURE} from "../../../../../src/common/Constants.sol"; -import {ExecutionManifest} from "../../../../../src/msca/6900/v0.8/common/ModuleManifest.sol"; -import {IExecutionHookModule} from "../../../../../src/msca/6900/v0.8/interfaces/IExecutionHookModule.sol"; -import {IValidationHookModule} from "../../../../../src/msca/6900/v0.8/interfaces/IValidationHookModule.sol"; -import {IValidationModule} from "../../../../../src/msca/6900/v0.8/interfaces/IValidationModule.sol"; +import {IExecutionHookModule} from "@erc6900/reference-implementation/interfaces/IExecutionHookModule.sol"; +import {ExecutionManifest} from "@erc6900/reference-implementation/interfaces/IExecutionModule.sol"; + import {BaseModule} from "../../../../../src/msca/6900/v0.8/modules/BaseModule.sol"; import {PackedUserOperation} from "@account-abstraction/contracts/interfaces/PackedUserOperation.sol"; +import {IValidationHookModule} from "@erc6900/reference-implementation/interfaces/IValidationHookModule.sol"; +import {IValidationModule} from "@erc6900/reference-implementation/interfaces/IValidationModule.sol"; /// @author Inspired by 6900 reference implementation of MockModule. contract MockModule is BaseModule, IExecutionHookModule, IValidationModule, IValidationHookModule { diff --git a/test/msca/6900/v0.8/helpers/TestAddressBookModule.sol b/test/msca/6900/v0.8/helpers/TestAddressBookModule.sol index f3b891c..7814232 100644 --- a/test/msca/6900/v0.8/helpers/TestAddressBookModule.sol +++ b/test/msca/6900/v0.8/helpers/TestAddressBookModule.sol @@ -23,14 +23,16 @@ import {CastLib} from "../../../../../src/libs/CastLib.sol"; import {RecipientAddressLib} from "../../../../../src/libs/RecipientAddressLib.sol"; import {Unsupported} from "../../../../../src/msca/6900/shared/common/Errors.sol"; -import { - ExecutionManifest, ManifestExecutionFunction -} from "../../../../../src/msca/6900/v0.8/common/ModuleManifest.sol"; -import {IModule} from "../../../../../src/msca/6900/v0.8/interfaces/IModule.sol"; -import {IValidationHookModule} from "../../../../../src/msca/6900/v0.8/interfaces/IValidationHookModule.sol"; + import {BaseModule} from "../../../../../src/msca/6900/v0.8/modules/BaseModule.sol"; import {IAddressBookModule} from "../../../../../src/msca/6900/v0.8/modules/addressbook/IAddressBookModule.sol"; import {PackedUserOperation} from "@account-abstraction/contracts/interfaces/PackedUserOperation.sol"; +import { + ExecutionManifest, + ManifestExecutionFunction +} from "@erc6900/reference-implementation/interfaces/IExecutionModule.sol"; +import {IModule} from "@erc6900/reference-implementation/interfaces/IModule.sol"; +import {IValidationHookModule} from "@erc6900/reference-implementation/interfaces/IValidationHookModule.sol"; import { AssociatedLinkedListSet, AssociatedLinkedListSetLib @@ -124,8 +126,6 @@ contract TestAddressBookModule is IAddressBookModule, BaseModule { // It is incompatible with alternate execution functions, owing to the specific decoding logic employed // here. // calldata length has already been checked in caller - (address target, uint256 targetValue, bytes memory targetData) = - abi.decode(userOp.callData[4:], (address, uint256, bytes)); // the caller is expected to pack the hook function data in userOp.signature address recipient = address(bytes20(userOp.signature)); if (!_isRecipientAllowed(recipient)) { @@ -146,14 +146,12 @@ contract TestAddressBookModule is IAddressBookModule, BaseModule { bytes calldata data, bytes calldata authorization ) external view override { - (value); + (data, value); if (entityId == uint32(EntityId.PRE_VALIDATION_HOOK_EXECUTE_ADDRESS_BOOK)) { // This functionality is exclusively compatible with the IStandardExecutor.execute as delineated in the // moduleManifest. // It is incompatible with alternate execution functions, owing to the specific decoding logic employed // here. - (address target, uint256 targetValue, bytes memory targetData) = - abi.decode(data[4:], (address, uint256, bytes)); address recipient = address(bytes20(authorization)); if (!_isRecipientAllowed(recipient)) { revert UnauthorizedRecipient(sender, recipient); @@ -192,7 +190,7 @@ contract TestAddressBookModule is IAddressBookModule, BaseModule { function preSignatureValidationHook(uint32 entityId, address sender, bytes32 hash, bytes calldata signature) external - view + pure override { (entityId, sender, hash, signature); diff --git a/test/msca/6900/v0.8/libs/HookConfigLib.t.sol b/test/msca/6900/v0.8/libs/HookConfigLib.t.sol deleted file mode 100644 index 149927b..0000000 --- a/test/msca/6900/v0.8/libs/HookConfigLib.t.sol +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2024 Circle Internet Group, Inc. All rights reserved. - - * SPDX-License-Identifier: GPL-3.0-or-later - - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -pragma solidity 0.8.24; - -import {HookConfig, ModuleEntity} from "../../../../../src/msca/6900/v0.8/common/Types.sol"; - -import {HookConfigLib} from "../../../../../src/msca/6900/v0.8/libs/HookConfigLib.sol"; -import {ModuleEntityLib} from "../../../../../src/msca/6900/v0.8/libs/thirdparty/ModuleEntityLib.sol"; -import {TestUtils} from "../../../../util/TestUtils.sol"; - -// @notice Inspired by 6900 reference impl with some modifications. -contract HookConfigLibTest is TestUtils { - using ModuleEntityLib for ModuleEntity; - using HookConfigLib for HookConfig; - - // Tests the packing and unpacking of a hook config with a randomized state - function testFuzz_hookConfig_packingUnderlying( - address addr, - uint32 entityId, - bool isValidation, - bool hasPre, - bool hasPost - ) public { - HookConfig hookConfig; - if (isValidation) { - hookConfig = HookConfigLib.packValidationHook(addr, entityId); - } else { - hookConfig = HookConfigLib.packExecHook(addr, entityId, hasPre, hasPost); - } - - assertEq(hookConfig.getModule(), addr, "module mismatch"); - assertEq(hookConfig.getEntityId(), entityId, "entityId mismatch"); - assertEq(hookConfig.isValidationHook(), isValidation, "isValidation mismatch"); - - if (isValidation) { - // unpack validation hook - ModuleEntity unpackedModuleEntity = HookConfigLib.unpackValidationHook(hookConfig); - (address unpackedAddr, uint32 unpackedEntityId) = unpackedModuleEntity.unpack(); - assertEq(unpackedAddr, addr, "module mismatch"); - assertEq(unpackedEntityId, entityId, "entityId mismatch"); - } else { - assertEq(hookConfig.hasPreHook(), hasPre, "hasPre mismatch"); - assertEq(hookConfig.hasPostHook(), hasPost, "hasPost mismatch"); - // unpack exec hook - (ModuleEntity unpackedHookFunction, bool unpackedHasPre, bool unpackedHasPost) = - HookConfigLib.unpackExecHook(hookConfig); - (address unpackedAddr, uint32 unpackedEntityId) = unpackedHookFunction.unpack(); - assertEq(unpackedAddr, addr, "module mismatch"); - assertEq(unpackedEntityId, entityId, "entityId mismatch"); - assertEq(unpackedHasPre, hasPre, "hasPre mismatch"); - assertEq(unpackedHasPost, hasPost, "hasPost mismatch"); - } - } - - function testFuzz_hookConfig_packingModuleEntity( - ModuleEntity hookFunction, - bool isValidation, - bool hasPre, - bool hasPost - ) public { - HookConfig hookConfig; - if (isValidation) { - hookConfig = HookConfigLib.packValidationHook(hookFunction); - } else { - hookConfig = HookConfigLib.packExecHook(hookFunction, hasPre, hasPost); - } - - assertEq( - ModuleEntity.unwrap(hookConfig.getModuleEntity()), - ModuleEntity.unwrap(hookFunction), - "moduleEntity mismatch" - ); - assertEq(hookConfig.isValidationHook(), isValidation, "isValidation mismatch"); - - if (isValidation) { - // unpack validation hook - ModuleEntity unpackedModuleEntity = HookConfigLib.unpackValidationHook(hookConfig); - assertEq( - ModuleEntity.unwrap(unpackedModuleEntity), ModuleEntity.unwrap(hookFunction), "moduleEntity mismatch" - ); - } else { - assertEq(hookConfig.hasPreHook(), hasPre, "hasPre mismatch"); - assertEq(hookConfig.hasPostHook(), hasPost, "hasPost mismatch"); - // unpack exec hook - (ModuleEntity unpackedHookFunction, bool unpackedHasPre, bool unpackedHasPost) = - HookConfigLib.unpackExecHook(hookConfig); - assertEq( - ModuleEntity.unwrap(unpackedHookFunction), ModuleEntity.unwrap(hookFunction), "moduleEntity mismatch" - ); - assertEq(unpackedHasPre, hasPre, "hasPre mismatch"); - assertEq(unpackedHasPost, hasPost, "hasPost mismatch"); - } - } -} diff --git a/test/msca/6900/v0.8/libs/thirdparty/ModuleEntityLib.t.sol b/test/msca/6900/v0.8/libs/thirdparty/ModuleEntityLib.t.sol deleted file mode 100644 index c557dc9..0000000 --- a/test/msca/6900/v0.8/libs/thirdparty/ModuleEntityLib.t.sol +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2024 Circle Internet Group, Inc. All rights reserved. - - * SPDX-License-Identifier: GPL-3.0-or-later - - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -pragma solidity 0.8.24; - -import {ModuleEntity} from "../../../../../../src/msca/6900/v0.8/common/Types.sol"; -import {ModuleEntityLib} from "../../../../../../src/msca/6900/v0.8/libs/thirdparty/ModuleEntityLib.sol"; -import {TestUtils} from "../../../../../util/TestUtils.sol"; - -contract ModuleEntityLibTest is TestUtils { - using ModuleEntityLib for ModuleEntity; - - function testFuzz_moduleEntity_packing(address addr, uint32 entityId) public { - // console.log("addr: ", addr); - // console.log("entityId: ", vm.toString(entityId)); - ModuleEntity fr = ModuleEntityLib.pack(addr, entityId); - // console.log("packed: ", vm.toString(ModuleEntity.unwrap(fr))); - (address addr2, uint32 entityId2) = ModuleEntityLib.unpack(fr); - // console.log("addr2: ", addr2); - // console.log("entityId2: ", vm.toString(entityId2)); - assertEq(addr, addr2); - assertEq(entityId, entityId2); - } - - function testFuzz_moduleEntity_operators(ModuleEntity a, ModuleEntity b) public { - assertTrue(a.eq(a)); - assertTrue(b.eq(b)); - - if (ModuleEntity.unwrap(a) == ModuleEntity.unwrap(b)) { - assertTrue(a.eq(b)); - assertTrue(b.eq(a)); - assertFalse(a.notEq(b)); - assertFalse(b.notEq(a)); - } else { - assertTrue(a.notEq(b)); - assertTrue(b.notEq(a)); - assertFalse(a.eq(b)); - assertFalse(b.eq(a)); - } - } -} diff --git a/test/msca/6900/v0.8/libs/thirdparty/SparseCalldataSegmentLib.t.sol b/test/msca/6900/v0.8/libs/thirdparty/SparseCalldataSegmentLib.t.sol deleted file mode 100644 index 8a00202..0000000 --- a/test/msca/6900/v0.8/libs/thirdparty/SparseCalldataSegmentLib.t.sol +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2024 Circle Internet Group, Inc. All rights reserved. - - * SPDX-License-Identifier: GPL-3.0-or-later - - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -pragma solidity 0.8.24; - -import {SparseCalldataSegmentLib} from - "../../../../../../src/msca/6900/v0.8/libs/thirdparty/SparseCalldataSegmentLib.sol"; -import {AccountTestUtils} from "../../utils/AccountTestUtils.sol"; - -// TODO: remove this till SparseCalldataSegmentLib is moved to modular account lib -// https://github.com/erc6900/modular-account-libs/issues/2 -contract SparseCalldataSegmentLibTest is AccountTestUtils { - using SparseCalldataSegmentLib for bytes; - - function testFuzz_sparseCalldataSegmentLib_encodeDecode_simple(bytes[] memory segments) public { - bytes memory encoded = _encodeSimple(segments); - bytes[] memory decoded = this.decodeSimple(encoded, segments.length); - - assertEq(decoded.length, segments.length, "decoded.length != segments.length"); - - for (uint256 i = 0; i < segments.length; i++) { - assertEq(decoded[i], segments[i]); - } - } - - function testFuzz_sparseCalldataSegmentLib_encodeDecode_withIndex(bytes[] memory segments, uint256 indexSeed) - public - { - // Generate random indices - uint8[] memory indices = new uint8[](segments.length); - for (uint256 i = 0; i < segments.length; i++) { - uint8 nextIndex = uint8(uint256(keccak256(abi.encodePacked(indexSeed, i)))); - indices[i] = nextIndex; - } - - // Encode - bytes memory encoded = _encodeWithIndex(segments, indices); - - // Decode - (bytes[] memory decodedBodies, uint8[] memory decodedIndices) = this.decodeWithIndex(encoded, segments.length); - - assertEq(decodedBodies.length, segments.length, "decodedBodies.length != segments.length"); - assertEq(decodedIndices.length, segments.length, "decodedIndices.length != segments.length"); - - for (uint256 i = 0; i < segments.length; i++) { - assertEq(decodedBodies[i], segments[i]); - assertEq(decodedIndices[i], indices[i]); - } - } - - function _encodeSimple(bytes[] memory segments) internal pure returns (bytes memory) { - bytes memory result = ""; - - for (uint256 i = 0; i < segments.length; i++) { - result = abi.encodePacked(result, uint32(segments[i].length), segments[i]); - } - - return result; - } - - function _encodeWithIndex(bytes[] memory segments, uint8[] memory indices) internal pure returns (bytes memory) { - assertEq(segments.length, indices.length); - - bytes memory result = ""; - - for (uint256 i = 0; i < segments.length; i++) { - result = abi.encodePacked(result, uint32(segments[i].length + 1), indices[i], segments[i]); - } - - return result; - } - - function decodeSimple(bytes calldata encoded, uint256 capacityHint) external pure returns (bytes[] memory) { - bytes[] memory result = new bytes[](capacityHint); - - bytes calldata remainder = encoded; - - uint256 index = 0; - while (remainder.length > 0) { - bytes calldata segment; - (segment, remainder) = remainder.getNextSegment(); - result[index] = segment; - index++; - } - - return result; - } - - function decodeWithIndex(bytes calldata encoded, uint256 capacityHint) - external - pure - returns (bytes[] memory, uint8[] memory) - { - bytes[] memory bodies = new bytes[](capacityHint); - uint8[] memory indices = new uint8[](capacityHint); - - bytes calldata remainder = encoded; - - uint256 index = 0; - while (remainder.length > 0) { - bytes calldata segment; - (segment, remainder) = remainder.getNextSegment(); - bodies[index] = segment.getBody(); - indices[index] = segment.getIndex(); - index++; - } - - return (bodies, indices); - } -} diff --git a/test/msca/6900/v0.8/libs/thirdparty/ValidationConfigLib.t.sol b/test/msca/6900/v0.8/libs/thirdparty/ValidationConfigLib.t.sol deleted file mode 100644 index f2352de..0000000 --- a/test/msca/6900/v0.8/libs/thirdparty/ValidationConfigLib.t.sol +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2024 Circle Internet Group, Inc. All rights reserved. - - * SPDX-License-Identifier: GPL-3.0-or-later - - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -pragma solidity 0.8.24; - -import {ModuleEntity, ValidationConfig} from "../../../../../../src/msca/6900/v0.8/common/Types.sol"; -import {ModuleEntityLib} from "../../../../../../src/msca/6900/v0.8/libs/thirdparty/ModuleEntityLib.sol"; - -import {ValidationConfigLib} from "../../../../../../src/msca/6900/v0.8/libs/thirdparty/ValidationConfigLib.sol"; -import {TestUtils} from "../../../../../util/TestUtils.sol"; - -// @notice Inspired by 6900 reference impl with some modifications. -contract ValidationConfigLibTest is TestUtils { - using ModuleEntityLib for ModuleEntity; - using ValidationConfigLib for ValidationConfig; - using ValidationConfigLib for uint8; - - // Tests the packing and unpacking of a validation config with a randomized state - function testFuzz_packingUnderlying( - address module, - uint32 entityId, - bool isGlobal, - bool isSignatureValidation, - bool isUserOpValidation - ) public { - ValidationConfig validationConfig = - ValidationConfigLib.pack(module, entityId, isGlobal, isSignatureValidation, isUserOpValidation); - - // Test unpacking underlying - (address unpackedModule, uint32 unpackedEntityId, uint8 unpackedFlags) = validationConfig.unpackUnderlying(); - - assertEq(module, unpackedModule, "module mismatch"); - assertEq(entityId, unpackedEntityId, "entityId mismatch"); - assertEq(isGlobal, unpackedFlags.isGlobal(), "isGlobal mismatch"); - assertEq(isSignatureValidation, unpackedFlags.isSignatureValidation(), "isSignatureValidation mismatch"); - assertEq(isUserOpValidation, unpackedFlags.isUserOpValidation(), "isUserOpValidation mismatch"); - - // Test unpacking to ModuleEntity - ModuleEntity expectedModuleEntity = ModuleEntityLib.pack(module, entityId); - (ModuleEntity validationFunction, uint8 unpackedFlagsForMe) = validationConfig.unpack(); - - assertEq( - ModuleEntity.unwrap(validationFunction), - ModuleEntity.unwrap(expectedModuleEntity), - "validationFunction mismatch" - ); - assertEq(isGlobal, unpackedFlagsForMe.isGlobal(), "isGlobal mismatch"); - assertEq(isSignatureValidation, unpackedFlagsForMe.isSignatureValidation(), "isSignatureValidation mismatch"); - assertEq(isUserOpValidation, unpackedFlagsForMe.isUserOpValidation(), "isUserOpValidation mismatch"); - - // Test individual view functions - assertEq(validationConfig.module(), module, "module mismatch"); - assertEq(validationConfig.entityId(), entityId, "entityId mismatch"); - assertEq( - ModuleEntity.unwrap(validationConfig.moduleEntity()), - ModuleEntity.unwrap(expectedModuleEntity), - "moduleEntity mismatch" - ); - assertEq(validationConfig.isGlobal(), isGlobal, "isGlobal mismatch"); - assertEq(validationConfig.isSignatureValidation(), isSignatureValidation, "isSignatureValidation mismatch"); - assertEq(validationConfig.isUserOpValidation(), isUserOpValidation, "isUserOpValidation mismatch"); - } - - function testFuzz_packingModuleEntity( - ModuleEntity validationFunction, - bool isGlobal, - bool isSignatureValidation, - bool isUserOpValidation - ) public { - ValidationConfig validationConfig = - ValidationConfigLib.pack(validationFunction, isGlobal, isSignatureValidation, isUserOpValidation); - - // Test unpacking underlying - (address module, uint32 entityId) = validationFunction.unpack(); - (address unpackedModule, uint32 unpackedEntityId, uint8 unpackedFlags) = validationConfig.unpackUnderlying(); - - assertEq(module, unpackedModule, "module mismatch"); - assertEq(entityId, unpackedEntityId, "entityId mismatch"); - assertEq(isGlobal, unpackedFlags.isGlobal(), "isGlobal mismatch"); - assertEq(isSignatureValidation, unpackedFlags.isSignatureValidation(), "isSignatureValidation mismatch"); - assertEq(isUserOpValidation, unpackedFlags.isUserOpValidation(), "isUserOpValidation mismatch"); - - // Test unpacking to ModuleEntity - (ModuleEntity unpackedValidationFunction, uint8 unpackedFlagsForMe) = validationConfig.unpack(); - - assertEq( - ModuleEntity.unwrap(validationFunction), - ModuleEntity.unwrap(unpackedValidationFunction), - "validationFunction mismatch" - ); - assertEq(isGlobal, unpackedFlagsForMe.isGlobal(), "isGlobal mismatch"); - assertEq(isSignatureValidation, unpackedFlagsForMe.isSignatureValidation(), "isSignatureValidation mismatch"); - assertEq(isUserOpValidation, unpackedFlagsForMe.isUserOpValidation(), "isUserOpValidation mismatch"); - - // Test individual view functions - assertEq(validationConfig.module(), module, "module mismatch"); - assertEq(validationConfig.entityId(), entityId, "entityId mismatch"); - assertEq( - ModuleEntity.unwrap(validationConfig.moduleEntity()), - ModuleEntity.unwrap(validationFunction), - "validationFunction mismatch" - ); - assertEq(validationConfig.isGlobal(), isGlobal, "isGlobal mismatch"); - assertEq(validationConfig.isSignatureValidation(), isSignatureValidation, "isSignatureValidation mismatch"); - assertEq(validationConfig.isUserOpValidation(), isUserOpValidation, "isUserOpValidation mismatch"); - } -} diff --git a/test/msca/6900/v0.8/modules/SingleSignerValidationModule.t.sol b/test/msca/6900/v0.8/modules/SingleSignerValidationModule.t.sol index 1196c68..6d9ba9b 100644 --- a/test/msca/6900/v0.8/modules/SingleSignerValidationModule.t.sol +++ b/test/msca/6900/v0.8/modules/SingleSignerValidationModule.t.sol @@ -21,12 +21,15 @@ pragma solidity 0.8.24; /* solhint-disable max-states-count */ import {BaseMSCA} from "../../../../../src/msca/6900/v0.8/account/BaseMSCA.sol"; -import {ExecutionDataView, ValidationDataView} from "../../../../../src/msca/6900/v0.8/common/Structs.sol"; -import {ModuleEntity, ValidationConfig} from "../../../../../src/msca/6900/v0.8/common/Types.sol"; -import {IModularAccount} from "../../../../../src/msca/6900/v0.8/interfaces/IModularAccount.sol"; -import {ModuleEntityLib} from "../../../../../src/msca/6900/v0.8/libs/thirdparty/ModuleEntityLib.sol"; +import { + ExecutionDataView, ValidationDataView +} from "@erc6900/reference-implementation/interfaces/IModularAccountView.sol"; -import {ValidationConfigLib} from "../../../../../src/msca/6900/v0.8/libs/thirdparty/ValidationConfigLib.sol"; +import {IModularAccount} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol"; +import {ModuleEntity, ValidationConfig} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol"; +import {ModuleEntityLib} from "@erc6900/reference-implementation/libraries/ModuleEntityLib.sol"; + +import {ValidationConfigLib} from "@erc6900/reference-implementation/libraries/ValidationConfigLib.sol"; import {SingleSignerValidationModule} from "../../../../../src/msca/6900/v0.8/modules/validation/SingleSignerValidationModule.sol"; @@ -38,7 +41,6 @@ import {IEntryPoint} from "@account-abstraction/contracts/interfaces/IEntryPoint import {UpgradableMSCA} from "../../../../../src/msca/6900/v0.8/account/UpgradableMSCA.sol"; import {UpgradableMSCAFactory} from "../../../../../src/msca/6900/v0.8/factories/UpgradableMSCAFactory.sol"; -import {IModularAccount} from "../../../../../src/msca/6900/v0.8/interfaces/IModularAccount.sol"; import {PackedUserOperation} from "@account-abstraction/contracts/interfaces/PackedUserOperation.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import {console} from "forge-std/src/console.sol"; @@ -202,7 +204,6 @@ contract SingleSignerValidationModuleTest is AccountTestUtils { "0x" ); // no paymaster - bytes32 userOpHash = entryPoint.getUserOpHash(userOp); // eoaPrivateKey from singleSignerValidationModule bytes memory signature = signUserOpHash(entryPoint, vm, eoaPrivateKey1, userOp); userOp.signature = encodeSignature(new PreValidationHookData[](0), signerValidation, signature, false); @@ -215,7 +216,7 @@ contract SingleSignerValidationModuleTest is AccountTestUtils { 0, "AA23 reverted", abi.encodeWithSelector( - BaseMSCA.ValidationFunctionMissing.selector, + BaseMSCA.InvalidValidationFunction.selector, singleSignerValidationModule.transferSigner.selector, signerValidation ) diff --git a/test/msca/6900/v0.8/utils/AccountTestUtils.sol b/test/msca/6900/v0.8/utils/AccountTestUtils.sol index 7e1d665..6a40996 100644 --- a/test/msca/6900/v0.8/utils/AccountTestUtils.sol +++ b/test/msca/6900/v0.8/utils/AccountTestUtils.sol @@ -20,13 +20,15 @@ pragma solidity 0.8.24; import { GLOBAL_VALIDATION_FLAG, - PER_SELECTOR_VALIDATION_FLAG, - RESERVED_VALIDATION_DATA_INDEX + PER_SELECTOR_VALIDATION_FLAG } from "../../../../../src/msca/6900/v0.8/common/Constants.sol"; -import {ModuleEntity} from "../../../../../src/msca/6900/v0.8/common/Types.sol"; -import {ModuleEntityLib} from "../../../../../src/msca/6900/v0.8/libs/thirdparty/ModuleEntityLib.sol"; + import {TestUtils} from "../../../../util/TestUtils.sol"; +import {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"; + contract AccountTestUtils is TestUtils { using ModuleEntityLib for ModuleEntity; @@ -54,7 +56,7 @@ contract AccountTestUtils is TestUtils { signature, packSparseDataWithIndex(preValidationHookData[i].index, preValidationHookData[i].hookData) ); } - signature = abi.encodePacked(signature, packSparseDataWithIndex(RESERVED_VALIDATION_DATA_INDEX, validationData)); + signature = abi.encodePacked(signature, packFinalSignature(validationData)); return signature; } @@ -71,13 +73,19 @@ contract AccountTestUtils is TestUtils { signature, packSparseDataWithIndex(preValidationHookData[i].index, preValidationHookData[i].hookData) ); } - signature = abi.encodePacked(signature, packSparseDataWithIndex(RESERVED_VALIDATION_DATA_INDEX, validationData)); + signature = abi.encodePacked(signature, packFinalSignature(validationData)); return signature; } // @notice Helper function forked from 6900 to pack validation data with an index, according to the sparse calldata // segment spec. function packSparseDataWithIndex(uint8 index, bytes memory callData) internal pure returns (bytes memory) { - return abi.encodePacked(uint32(callData.length + 1), index, callData); + return abi.encodePacked(index, uint32(callData.length), callData); + } + + // @notice Helper function forked from 6900 to pack the final signature, according to the sparse calldata segment + // spec. + function packFinalSignature(bytes memory signature) internal pure returns (bytes memory) { + return abi.encodePacked(RESERVED_VALIDATION_DATA_INDEX, signature); } } diff --git a/test/util/Mock1820Registry.sol b/test/util/Mock1820Registry.sol index 17c237d..d1d2b06 100644 --- a/test/util/Mock1820Registry.sol +++ b/test/util/Mock1820Registry.sol @@ -151,7 +151,8 @@ contract MockERC1820Registry is IERC1820Registry { return false; } - function setManager(address account, address newManager) external { + function setManager(address account, address newManager) external pure { + (account, newManager); revert NotImplemented(); } @@ -160,7 +161,8 @@ contract MockERC1820Registry is IERC1820Registry { * * See {setManager}. */ - function getManager(address account) external view returns (address) { + function getManager(address account) external pure returns (address) { + (account); revert NotImplemented(); } diff --git a/test/util/TestERC777.sol b/test/util/TestERC777.sol index d97317a..508e265 100644 --- a/test/util/TestERC777.sol +++ b/test/util/TestERC777.sol @@ -63,23 +63,24 @@ contract TestERC777 is IERC777 { // solhint-disable-next-line no-empty-blocks function burn(uint256 amount, bytes calldata data) external {} - function granularity() external view returns (uint256) { + function granularity() external pure returns (uint256) { return 0; } - function isOperatorFor(address operator, address tokenHolder) external view returns (bool) { + function isOperatorFor(address operator, address tokenHolder) external pure returns (bool) { + (operator, tokenHolder); return false; } - function name() external view returns (string memory) { + function name() external pure returns (string memory) { return "777"; } - function symbol() external view returns (string memory) { + function symbol() external pure returns (string memory) { return "777"; } - function totalSupply() external view returns (uint256) { + function totalSupply() external pure returns (uint256) { return 1000; } @@ -89,7 +90,7 @@ contract TestERC777 is IERC777 { // solhint-disable-next-line no-empty-blocks function revokeOperator(address) external {} - function defaultOperators() external view returns (address[] memory a) { + function defaultOperators() external pure returns (address[] memory a) { return new address[](0); } diff --git a/yarn.lock b/yarn.lock index c61f83d..b69a1eb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -25,6 +25,10 @@ js-tokens "^4.0.0" picocolors "^1.0.0" +"@erc6900/reference-implementation@github:erc6900/reference-implementation#v0.8.0-rc.6": + version "0.8.0-rc.5" + resolved "https://codeload.github.com/erc6900/reference-implementation/tar.gz/6cdcfa653eb019d27d23586a86ff8171201a4066" + "@modular-account-libs@github:erc6900/modular-account-libs#v0.8.0-rc.0": version "0.8.0-rc.0" resolved "https://codeload.github.com/erc6900/modular-account-libs/tar.gz/d64adb5bd100d6ad64d0a83b5dc13b356423d852"