From d666f503de86977b07829a2d705e55bdbcfe7e6c Mon Sep 17 00:00:00 2001 From: adam-alchemy <127769144+adam-alchemy@users.noreply.github.com> Date: Tue, 5 Mar 2024 08:20:44 -0800 Subject: [PATCH 001/111] feat: Remove hook group (#39) --- foundry.toml | 1 - src/account/AccountLoupe.sol | 18 ++++++------- src/account/AccountStorage.sol | 16 ++++-------- src/account/PluginManagerInternals.sol | 31 +++++++---------------- src/account/UpgradeableModularAccount.sol | 18 ++++++------- 5 files changed, 32 insertions(+), 52 deletions(-) diff --git a/foundry.toml b/foundry.toml index 8d882d47..be310db9 100644 --- a/foundry.toml +++ b/foundry.toml @@ -7,7 +7,6 @@ libs = ['lib'] out = 'out' optimizer = true optimizer_runs = 200 -ignored_error_codes = [] fs_permissions = [ { access = "read", path = "./out-optimized" } ] diff --git a/src/account/AccountLoupe.sol b/src/account/AccountLoupe.sol index 7caf3e2b..8459c65a 100644 --- a/src/account/AccountLoupe.sol +++ b/src/account/AccountLoupe.sol @@ -8,7 +8,7 @@ import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet import {IAccountLoupe} from "../interfaces/IAccountLoupe.sol"; import {FunctionReference, IPluginManager} from "../interfaces/IPluginManager.sol"; import {IStandardExecutor} from "../interfaces/IStandardExecutor.sol"; -import {AccountStorage, getAccountStorage, HookGroup, toFunctionReferenceArray} from "./AccountStorage.sol"; +import {AccountStorage, getAccountStorage, SelectorData, toFunctionReferenceArray} from "./AccountStorage.sol"; abstract contract AccountLoupe is IAccountLoupe { using EnumerableMap for EnumerableMap.Bytes32ToUintMap; @@ -43,14 +43,14 @@ abstract contract AccountLoupe is IAccountLoupe { /// @inheritdoc IAccountLoupe function getExecutionHooks(bytes4 selector) external view returns (ExecutionHooks[] memory execHooks) { - HookGroup storage hooks = getAccountStorage().selectorData[selector].executionHooks; - uint256 preExecHooksLength = hooks.preHooks.length(); - uint256 postOnlyExecHooksLength = hooks.postOnlyHooks.length(); + SelectorData storage selectorData = getAccountStorage().selectorData[selector]; + uint256 preExecHooksLength = selectorData.preHooks.length(); + uint256 postOnlyExecHooksLength = selectorData.postOnlyHooks.length(); uint256 maxExecHooksLength = postOnlyExecHooksLength; // There can only be as many associated post hooks to run as there are pre hooks. for (uint256 i = 0; i < preExecHooksLength;) { - (, uint256 count) = hooks.preHooks.at(i); + (, uint256 count) = selectorData.preHooks.at(i); unchecked { maxExecHooksLength += (count + 1); ++i; @@ -62,14 +62,14 @@ abstract contract AccountLoupe is IAccountLoupe { uint256 actualExecHooksLength; for (uint256 i = 0; i < preExecHooksLength;) { - (bytes32 key,) = hooks.preHooks.at(i); + (bytes32 key,) = selectorData.preHooks.at(i); FunctionReference preExecHook = FunctionReference.wrap(bytes21(key)); - uint256 associatedPostExecHooksLength = hooks.associatedPostHooks[preExecHook].length(); + uint256 associatedPostExecHooksLength = selectorData.associatedPostHooks[preExecHook].length(); if (associatedPostExecHooksLength > 0) { for (uint256 j = 0; j < associatedPostExecHooksLength;) { execHooks[actualExecHooksLength].preExecHook = preExecHook; - (key,) = hooks.associatedPostHooks[preExecHook].at(j); + (key,) = selectorData.associatedPostHooks[preExecHook].at(j); execHooks[actualExecHooksLength].postExecHook = FunctionReference.wrap(bytes21(key)); unchecked { @@ -91,7 +91,7 @@ abstract contract AccountLoupe is IAccountLoupe { } for (uint256 i = 0; i < postOnlyExecHooksLength;) { - (bytes32 key,) = hooks.postOnlyHooks.at(i); + (bytes32 key,) = selectorData.postOnlyHooks.at(i); execHooks[actualExecHooksLength].postExecHook = FunctionReference.wrap(bytes21(key)); unchecked { diff --git a/src/account/AccountStorage.sol b/src/account/AccountStorage.sol index 86c7f0a7..0fea6771 100644 --- a/src/account/AccountStorage.sol +++ b/src/account/AccountStorage.sol @@ -12,8 +12,7 @@ bytes32 constant _ACCOUNT_STORAGE_SLOT = 0x9f09680beaa4e5c9f38841db2460c40149916 struct PluginData { bool anyExternalExecPermitted; - // boolean to indicate if the plugin can spend native tokens, if any of the execution function can spend - // native tokens, a plugin is considered to be able to spend native tokens of the accounts + // boolean to indicate if the plugin can spend native tokens from the account. bool canSpendNativeToken; bytes32 manifestHash; FunctionReference[] dependencies; @@ -31,14 +30,6 @@ struct PermittedExternalCallData { mapping(bytes4 => bool) permittedSelectors; } -// Represets a set of pre- and post- hooks. -struct HookGroup { - EnumerableMap.Bytes32ToUintMap preHooks; - // bytes21 key = pre hook function reference - mapping(FunctionReference => EnumerableMap.Bytes32ToUintMap) associatedPostHooks; - EnumerableMap.Bytes32ToUintMap postOnlyHooks; -} - // Represents data associated with a specifc function selector. struct SelectorData { // The plugin that implements this execution function. @@ -50,7 +41,10 @@ struct SelectorData { EnumerableMap.Bytes32ToUintMap preUserOpValidationHooks; EnumerableMap.Bytes32ToUintMap preRuntimeValidationHooks; // The execution hooks for this function selector. - HookGroup executionHooks; + EnumerableMap.Bytes32ToUintMap preHooks; + // bytes21 key = pre hook function reference + mapping(FunctionReference => EnumerableMap.Bytes32ToUintMap) associatedPostHooks; + EnumerableMap.Bytes32ToUintMap postOnlyHooks; } struct AccountStorage { diff --git a/src/account/PluginManagerInternals.sol b/src/account/PluginManagerInternals.sol index c77fb301..e3062661 100644 --- a/src/account/PluginManagerInternals.sol +++ b/src/account/PluginManagerInternals.sol @@ -21,7 +21,6 @@ import { getAccountStorage, SelectorData, getPermittedCallKey, - HookGroup, PermittedExternalCallData } from "./AccountStorage.sol"; @@ -126,25 +125,11 @@ abstract contract PluginManagerInternals is IPluginManager { { SelectorData storage _selectorData = getAccountStorage().selectorData[selector]; - _addHooks(_selectorData.executionHooks, preExecHook, postExecHook); - } - - function _removeExecHooks(bytes4 selector, FunctionReference preExecHook, FunctionReference postExecHook) - internal - { - SelectorData storage _selectorData = getAccountStorage().selectorData[selector]; - - _removeHooks(_selectorData.executionHooks, preExecHook, postExecHook); - } - - function _addHooks(HookGroup storage hooks, FunctionReference preExecHook, FunctionReference postExecHook) - internal - { if (!preExecHook.isEmpty()) { - _addOrIncrement(hooks.preHooks, _toSetValue(preExecHook)); + _addOrIncrement(_selectorData.preHooks, _toSetValue(preExecHook)); if (!postExecHook.isEmpty()) { - _addOrIncrement(hooks.associatedPostHooks[preExecHook], _toSetValue(postExecHook)); + _addOrIncrement(_selectorData.associatedPostHooks[preExecHook], _toSetValue(postExecHook)); } } else { if (postExecHook.isEmpty()) { @@ -152,24 +137,26 @@ abstract contract PluginManagerInternals is IPluginManager { revert NullFunctionReference(); } - _addOrIncrement(hooks.postOnlyHooks, _toSetValue(postExecHook)); + _addOrIncrement(_selectorData.postOnlyHooks, _toSetValue(postExecHook)); } } - function _removeHooks(HookGroup storage hooks, FunctionReference preExecHook, FunctionReference postExecHook) + function _removeExecHooks(bytes4 selector, FunctionReference preExecHook, FunctionReference postExecHook) internal { + SelectorData storage _selectorData = getAccountStorage().selectorData[selector]; + if (!preExecHook.isEmpty()) { - _removeOrDecrement(hooks.preHooks, _toSetValue(preExecHook)); + _removeOrDecrement(_selectorData.preHooks, _toSetValue(preExecHook)); if (!postExecHook.isEmpty()) { - _removeOrDecrement(hooks.associatedPostHooks[preExecHook], _toSetValue(postExecHook)); + _removeOrDecrement(_selectorData.associatedPostHooks[preExecHook], _toSetValue(postExecHook)); } } else { // The case where both pre and post hooks are null was checked during installation. // May ignore return value, as the manifest hash is validated to ensure that the hook exists. - _removeOrDecrement(hooks.postOnlyHooks, _toSetValue(postExecHook)); + _removeOrDecrement(_selectorData.postOnlyHooks, _toSetValue(postExecHook)); } } diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index e5c02acf..a449ae99 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -17,7 +17,7 @@ import {FunctionReference, IPluginManager} from "../interfaces/IPluginManager.so import {IStandardExecutor, Call} from "../interfaces/IStandardExecutor.sol"; import {AccountExecutor} from "./AccountExecutor.sol"; import {AccountLoupe} from "./AccountLoupe.sol"; -import {AccountStorage, HookGroup, getAccountStorage, getPermittedCallKey} from "./AccountStorage.sol"; +import {AccountStorage, getAccountStorage, getPermittedCallKey, SelectorData} from "./AccountStorage.sol"; import {AccountStorageInitializable} from "./AccountStorageInitializable.sol"; import {PluginManagerInternals} from "./PluginManagerInternals.sol"; @@ -459,14 +459,14 @@ contract UpgradeableModularAccount is internal returns (PostExecToRun[] memory postHooksToRun) { - HookGroup storage hooks = getAccountStorage().selectorData[selector].executionHooks; - uint256 preExecHooksLength = hooks.preHooks.length(); - uint256 postOnlyHooksLength = hooks.postOnlyHooks.length(); + SelectorData storage selectorData = getAccountStorage().selectorData[selector]; + uint256 preExecHooksLength = selectorData.preHooks.length(); + uint256 postOnlyHooksLength = selectorData.postOnlyHooks.length(); uint256 maxPostExecHooksLength = postOnlyHooksLength; // There can only be as many associated post hooks to run as there are pre hooks. for (uint256 i = 0; i < preExecHooksLength;) { - (, uint256 count) = hooks.preHooks.at(i); + (, uint256 count) = selectorData.preHooks.at(i); unchecked { maxPostExecHooksLength += (count + 1); ++i; @@ -479,7 +479,7 @@ contract UpgradeableModularAccount is // Copy post-only hooks to the array. for (uint256 i = 0; i < postOnlyHooksLength;) { - (bytes32 key,) = hooks.postOnlyHooks.at(i); + (bytes32 key,) = selectorData.postOnlyHooks.at(i); postHooksToRun[actualPostHooksToRunLength].postExecHook = _toFunctionReference(key); unchecked { ++actualPostHooksToRunLength; @@ -490,7 +490,7 @@ contract UpgradeableModularAccount is // Then run the pre hooks and copy the associated post hooks (along with their pre hook's return data) to // the array. for (uint256 i = 0; i < preExecHooksLength;) { - (bytes32 key,) = hooks.preHooks.at(i); + (bytes32 key,) = selectorData.preHooks.at(i); FunctionReference preExecHook = _toFunctionReference(key); if (preExecHook.isEmptyOrMagicValue()) { @@ -501,10 +501,10 @@ contract UpgradeableModularAccount is bytes memory preExecHookReturnData = _runPreExecHook(preExecHook, data); - uint256 associatedPostExecHooksLength = hooks.associatedPostHooks[preExecHook].length(); + uint256 associatedPostExecHooksLength = selectorData.associatedPostHooks[preExecHook].length(); if (associatedPostExecHooksLength > 0) { for (uint256 j = 0; j < associatedPostExecHooksLength;) { - (key,) = hooks.associatedPostHooks[preExecHook].at(j); + (key,) = selectorData.associatedPostHooks[preExecHook].at(j); postHooksToRun[actualPostHooksToRunLength].postExecHook = _toFunctionReference(key); postHooksToRun[actualPostHooksToRunLength].preExecHookReturnData = preExecHookReturnData; From f6e335ce67ef35293e675850c3114b30a2dfb3e4 Mon Sep 17 00:00:00 2001 From: adam-alchemy <127769144+adam-alchemy@users.noreply.github.com> Date: Mon, 15 Apr 2024 15:29:12 -0700 Subject: [PATCH 002/111] feat: [v0.8-develop, experimental] Merge validation function assignments (#40) --- src/account/AccountLoupe.sol | 4 +- src/account/AccountStorage.sol | 4 +- src/account/PluginManagerInternals.sol | 79 +++--------------- src/account/UpgradeableModularAccount.sol | 7 +- src/interfaces/IAccountLoupe.sol | 3 +- src/interfaces/IPlugin.sol | 3 +- src/plugins/TokenReceiverPlugin.sol | 20 ++--- src/plugins/owner/ISingleOwnerPlugin.sol | 3 +- src/plugins/owner/SingleOwnerPlugin.sol | 82 +++++-------------- .../plugins/ModularSessionKeyPlugin.sol | 51 ++++-------- src/samples/plugins/TokenSessionKeyPlugin.sol | 22 ++--- .../plugins/interfaces/ISessionKeyPlugin.sol | 3 +- standard/ERCs/erc-6900.md | 6 +- test/account/AccountExecHooks.t.sol | 2 +- test/account/AccountLoupe.t.sol | 57 ++++--------- test/account/ManifestValidity.t.sol | 17 ---- test/account/UpgradeableModularAccount.t.sol | 2 +- test/comparison/CompareSimpleAccount.t.sol | 2 +- .../plugins/BadTransferOwnershipPlugin.sol | 4 +- test/mocks/plugins/ComprehensivePlugin.sol | 28 ++----- .../ExecFromPluginPermissionsMocks.sol | 8 +- test/mocks/plugins/ManifestValidityMocks.sol | 46 ++--------- test/mocks/plugins/ReturnDataPluginMocks.sol | 10 +-- test/mocks/plugins/ValidationPluginMocks.sol | 12 +-- test/plugin/SingleOwnerPlugin.t.sol | 8 +- .../plugins/ModularSessionKeyPlugin.t.sol | 22 ++--- 26 files changed, 141 insertions(+), 364 deletions(-) diff --git a/src/account/AccountLoupe.sol b/src/account/AccountLoupe.sol index 8459c65a..8b6f42a2 100644 --- a/src/account/AccountLoupe.sol +++ b/src/account/AccountLoupe.sol @@ -36,9 +36,7 @@ abstract contract AccountLoupe is IAccountLoupe { config.plugin = _storage.selectorData[selector].plugin; } - config.userOpValidationFunction = _storage.selectorData[selector].userOpValidation; - - config.runtimeValidationFunction = _storage.selectorData[selector].runtimeValidation; + config.validationFunction = _storage.selectorData[selector].validation; } /// @inheritdoc IAccountLoupe diff --git a/src/account/AccountStorage.sol b/src/account/AccountStorage.sol index 0fea6771..deb31228 100644 --- a/src/account/AccountStorage.sol +++ b/src/account/AccountStorage.sol @@ -35,8 +35,8 @@ struct SelectorData { // The plugin that implements this execution function. // If this is a native function, the address must remain address(0). address plugin; - FunctionReference userOpValidation; - FunctionReference runtimeValidation; + // User operation validation and runtime validation share a function reference. + FunctionReference validation; // The pre validation hooks for this function selector. EnumerableMap.Bytes32ToUintMap preUserOpValidationHooks; EnumerableMap.Bytes32ToUintMap preRuntimeValidationHooks; diff --git a/src/account/PluginManagerInternals.sol b/src/account/PluginManagerInternals.sol index e3062661..dc5fbb8a 100644 --- a/src/account/PluginManagerInternals.sol +++ b/src/account/PluginManagerInternals.sol @@ -41,8 +41,7 @@ abstract contract PluginManagerInternals is IPluginManager { error PluginInstallCallbackFailed(address plugin, bytes revertReason); error PluginInterfaceNotSupported(address plugin); error PluginNotInstalled(address plugin); - error RuntimeValidationFunctionAlreadySet(bytes4 selector, FunctionReference validationFunction); - error UserOpValidationFunctionAlreadySet(bytes4 selector, FunctionReference validationFunction); + error ValidationFunctionAlreadySet(bytes4 selector, FunctionReference validationFunction); modifier notNullFunction(FunctionReference functionReference) { if (functionReference.isEmpty()) { @@ -76,48 +75,26 @@ abstract contract PluginManagerInternals is IPluginManager { _selectorData.plugin = address(0); } - function _addUserOpValidationFunction(bytes4 selector, FunctionReference validationFunction) + function _addValidationFunction(bytes4 selector, FunctionReference validationFunction) internal notNullFunction(validationFunction) { SelectorData storage _selectorData = getAccountStorage().selectorData[selector]; - if (!_selectorData.userOpValidation.isEmpty()) { - revert UserOpValidationFunctionAlreadySet(selector, validationFunction); + if (!_selectorData.validation.isEmpty()) { + revert ValidationFunctionAlreadySet(selector, validationFunction); } - _selectorData.userOpValidation = validationFunction; + _selectorData.validation = validationFunction; } - function _removeUserOpValidationFunction(bytes4 selector, FunctionReference validationFunction) + function _removeValidationFunction(bytes4 selector, FunctionReference validationFunction) internal notNullFunction(validationFunction) { SelectorData storage _selectorData = getAccountStorage().selectorData[selector]; - _selectorData.userOpValidation = FunctionReferenceLib._EMPTY_FUNCTION_REFERENCE; - } - - function _addRuntimeValidationFunction(bytes4 selector, FunctionReference validationFunction) - internal - notNullFunction(validationFunction) - { - SelectorData storage _selectorData = getAccountStorage().selectorData[selector]; - - if (!_selectorData.runtimeValidation.isEmpty()) { - revert RuntimeValidationFunctionAlreadySet(selector, validationFunction); - } - - _selectorData.runtimeValidation = validationFunction; - } - - function _removeRuntimeValidationFunction(bytes4 selector, FunctionReference validationFunction) - internal - notNullFunction(validationFunction) - { - SelectorData storage _selectorData = getAccountStorage().selectorData[selector]; - - _selectorData.runtimeValidation = FunctionReferenceLib._EMPTY_FUNCTION_REFERENCE; + _selectorData.validation = FunctionReferenceLib._EMPTY_FUNCTION_REFERENCE; } function _addExecHooks(bytes4 selector, FunctionReference preExecHook, FunctionReference postExecHook) @@ -319,25 +296,10 @@ abstract contract PluginManagerInternals is IPluginManager { } } - length = manifest.userOpValidationFunctions.length; - for (uint256 i = 0; i < length;) { - ManifestAssociatedFunction memory mv = manifest.userOpValidationFunctions[i]; - _addUserOpValidationFunction( - mv.executionSelector, - _resolveManifestFunction( - mv.associatedFunction, plugin, dependencies, ManifestAssociatedFunctionType.NONE - ) - ); - - unchecked { - ++i; - } - } - - length = manifest.runtimeValidationFunctions.length; + length = manifest.validationFunctions.length; for (uint256 i = 0; i < length;) { - ManifestAssociatedFunction memory mv = manifest.runtimeValidationFunctions[i]; - _addRuntimeValidationFunction( + ManifestAssociatedFunction memory mv = manifest.validationFunctions[i]; + _addValidationFunction( mv.executionSelector, _resolveManifestFunction( mv.associatedFunction, @@ -521,10 +483,10 @@ abstract contract PluginManagerInternals is IPluginManager { } } - length = manifest.runtimeValidationFunctions.length; + length = manifest.validationFunctions.length; for (uint256 i = 0; i < length;) { - ManifestAssociatedFunction memory mv = manifest.runtimeValidationFunctions[i]; - _removeRuntimeValidationFunction( + ManifestAssociatedFunction memory mv = manifest.validationFunctions[i]; + _removeValidationFunction( mv.executionSelector, _resolveManifestFunction( mv.associatedFunction, @@ -539,21 +501,6 @@ abstract contract PluginManagerInternals is IPluginManager { } } - length = manifest.userOpValidationFunctions.length; - for (uint256 i = 0; i < length;) { - ManifestAssociatedFunction memory mv = manifest.userOpValidationFunctions[i]; - _removeUserOpValidationFunction( - mv.executionSelector, - _resolveManifestFunction( - mv.associatedFunction, plugin, dependencies, ManifestAssociatedFunctionType.NONE - ) - ); - - unchecked { - ++i; - } - } - // remove external call permissions if (manifest.permitAnyExternalAddress) { diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index a449ae99..24a87ea1 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -336,7 +336,7 @@ contract UpgradeableModularAccount is } bytes4 selector = bytes4(userOp.callData); - FunctionReference userOpValidationFunction = getAccountStorage().selectorData[selector].userOpValidation; + FunctionReference userOpValidationFunction = getAccountStorage().selectorData[selector].validation; validationData = _doUserOpValidation(selector, userOpValidationFunction, userOp, userOpHash); } @@ -395,7 +395,8 @@ contract UpgradeableModularAccount is validationData = currentValidationData; } } else { - // _RUNTIME_VALIDATION_ALWAYS_ALLOW and _PRE_HOOK_ALWAYS_DENY is not permitted here. + // _PRE_HOOK_ALWAYS_DENY is not permitted here. + // If this is _RUNTIME_VALIDATION_ALWAYS_ALLOW, the call should revert. revert InvalidConfiguration(); } } @@ -405,7 +406,7 @@ contract UpgradeableModularAccount is if (msg.sender == address(_ENTRY_POINT)) return; AccountStorage storage _storage = getAccountStorage(); - FunctionReference runtimeValidationFunction = _storage.selectorData[msg.sig].runtimeValidation; + FunctionReference runtimeValidationFunction = _storage.selectorData[msg.sig].validation; // run all preRuntimeValidation hooks EnumerableMap.Bytes32ToUintMap storage preRuntimeValidationHooks = getAccountStorage().selectorData[msg.sig].preRuntimeValidationHooks; diff --git a/src/interfaces/IAccountLoupe.sol b/src/interfaces/IAccountLoupe.sol index 25d50487..300a81b1 100644 --- a/src/interfaces/IAccountLoupe.sol +++ b/src/interfaces/IAccountLoupe.sol @@ -7,8 +7,7 @@ interface IAccountLoupe { /// @notice Config for an execution function, given a selector. struct ExecutionFunctionConfig { address plugin; - FunctionReference userOpValidationFunction; - FunctionReference runtimeValidationFunction; + FunctionReference validationFunction; } /// @notice Pre and post hooks for a given selector. diff --git a/src/interfaces/IPlugin.sol b/src/interfaces/IPlugin.sol index cf74fd89..e8edaf4a 100644 --- a/src/interfaces/IPlugin.sol +++ b/src/interfaces/IPlugin.sol @@ -90,8 +90,7 @@ struct PluginManifest { // plugin MUST still be able to spend up to the balance that it sends to the account in the same call. bool canSpendNativeToken; ManifestExternalCallPermission[] permittedExternalCalls; - ManifestAssociatedFunction[] userOpValidationFunctions; - ManifestAssociatedFunction[] runtimeValidationFunctions; + ManifestAssociatedFunction[] validationFunctions; ManifestAssociatedFunction[] preUserOpValidationHooks; ManifestAssociatedFunction[] preRuntimeValidationHooks; ManifestExecutionHook[] executionHooks; diff --git a/src/plugins/TokenReceiverPlugin.sol b/src/plugins/TokenReceiverPlugin.sol index ac9335fd..e138f8f3 100644 --- a/src/plugins/TokenReceiverPlugin.sol +++ b/src/plugins/TokenReceiverPlugin.sol @@ -79,27 +79,27 @@ contract TokenReceiverPlugin is BasePlugin, IERC721Receiver, IERC777Recipient, I manifest.executionFunctions[3] = this.onERC1155BatchReceived.selector; // Only runtime validationFunction is needed since callbacks come from token contracts only - ManifestFunction memory alwaysAllowFunction = ManifestFunction({ + ManifestFunction memory alwaysAllowRuntime = ManifestFunction({ functionType: ManifestAssociatedFunctionType.RUNTIME_VALIDATION_ALWAYS_ALLOW, functionId: 0, // Unused. dependencyIndex: 0 // Unused. }); - manifest.runtimeValidationFunctions = new ManifestAssociatedFunction[](4); - manifest.runtimeValidationFunctions[0] = ManifestAssociatedFunction({ + manifest.validationFunctions = new ManifestAssociatedFunction[](4); + manifest.validationFunctions[0] = ManifestAssociatedFunction({ executionSelector: this.tokensReceived.selector, - associatedFunction: alwaysAllowFunction + associatedFunction: alwaysAllowRuntime }); - manifest.runtimeValidationFunctions[1] = ManifestAssociatedFunction({ + manifest.validationFunctions[1] = ManifestAssociatedFunction({ executionSelector: this.onERC721Received.selector, - associatedFunction: alwaysAllowFunction + associatedFunction: alwaysAllowRuntime }); - manifest.runtimeValidationFunctions[2] = ManifestAssociatedFunction({ + manifest.validationFunctions[2] = ManifestAssociatedFunction({ executionSelector: this.onERC1155Received.selector, - associatedFunction: alwaysAllowFunction + associatedFunction: alwaysAllowRuntime }); - manifest.runtimeValidationFunctions[3] = ManifestAssociatedFunction({ + manifest.validationFunctions[3] = ManifestAssociatedFunction({ executionSelector: this.onERC1155BatchReceived.selector, - associatedFunction: alwaysAllowFunction + associatedFunction: alwaysAllowRuntime }); manifest.interfaceIds = new bytes4[](3); diff --git a/src/plugins/owner/ISingleOwnerPlugin.sol b/src/plugins/owner/ISingleOwnerPlugin.sol index c75ab8a3..fc0622c8 100644 --- a/src/plugins/owner/ISingleOwnerPlugin.sol +++ b/src/plugins/owner/ISingleOwnerPlugin.sol @@ -5,8 +5,7 @@ import {UserOperation} from "@eth-infinitism/account-abstraction/interfaces/User interface ISingleOwnerPlugin { enum FunctionId { - RUNTIME_VALIDATION_OWNER_OR_SELF, - USER_OP_VALIDATION_OWNER + VALIDATION_OWNER_OR_SELF } /// @notice This event is emitted when ownership of the account changes. diff --git a/src/plugins/owner/SingleOwnerPlugin.sol b/src/plugins/owner/SingleOwnerPlugin.sol index fa8ed177..29103945 100644 --- a/src/plugins/owner/SingleOwnerPlugin.sol +++ b/src/plugins/owner/SingleOwnerPlugin.sol @@ -108,7 +108,7 @@ contract SingleOwnerPlugin is BasePlugin, ISingleOwnerPlugin, IERC1271 { view override { - if (functionId == uint8(FunctionId.RUNTIME_VALIDATION_OWNER_OR_SELF)) { + if (functionId == uint8(FunctionId.VALIDATION_OWNER_OR_SELF)) { // Validate that the sender is the owner of the account or self. if (sender != _owners[msg.sender] && sender != msg.sender) { revert NotAuthorized(); @@ -125,7 +125,7 @@ contract SingleOwnerPlugin is BasePlugin, ISingleOwnerPlugin, IERC1271 { override returns (uint256) { - if (functionId == uint8(FunctionId.USER_OP_VALIDATION_OWNER)) { + if (functionId == uint8(FunctionId.VALIDATION_OWNER_OR_SELF)) { // Validate the user op signature against the owner. (address signer,) = (userOpHash.toEthSignedMessageHash()).tryRecover(userOp.signature); if (signer == address(0) || signer != _owners[msg.sender]) { @@ -145,87 +145,49 @@ contract SingleOwnerPlugin is BasePlugin, ISingleOwnerPlugin, IERC1271 { manifest.executionFunctions[1] = this.isValidSignature.selector; manifest.executionFunctions[2] = this.owner.selector; - ManifestFunction memory ownerUserOpValidationFunction = ManifestFunction({ + ManifestFunction memory ownerValidationFunction = ManifestFunction({ functionType: ManifestAssociatedFunctionType.SELF, - functionId: uint8(FunctionId.USER_OP_VALIDATION_OWNER), + functionId: uint8(FunctionId.VALIDATION_OWNER_OR_SELF), dependencyIndex: 0 // Unused. }); - manifest.userOpValidationFunctions = new ManifestAssociatedFunction[](7); - manifest.userOpValidationFunctions[0] = ManifestAssociatedFunction({ + manifest.validationFunctions = new ManifestAssociatedFunction[](8); + manifest.validationFunctions[0] = ManifestAssociatedFunction({ executionSelector: this.transferOwnership.selector, - associatedFunction: ownerUserOpValidationFunction + associatedFunction: ownerValidationFunction }); - manifest.userOpValidationFunctions[1] = ManifestAssociatedFunction({ + manifest.validationFunctions[1] = ManifestAssociatedFunction({ executionSelector: IStandardExecutor.execute.selector, - associatedFunction: ownerUserOpValidationFunction + associatedFunction: ownerValidationFunction }); - manifest.userOpValidationFunctions[2] = ManifestAssociatedFunction({ + manifest.validationFunctions[2] = ManifestAssociatedFunction({ executionSelector: IStandardExecutor.executeBatch.selector, - associatedFunction: ownerUserOpValidationFunction + associatedFunction: ownerValidationFunction }); - manifest.userOpValidationFunctions[3] = ManifestAssociatedFunction({ + manifest.validationFunctions[3] = ManifestAssociatedFunction({ executionSelector: IPluginManager.installPlugin.selector, - associatedFunction: ownerUserOpValidationFunction + associatedFunction: ownerValidationFunction }); - manifest.userOpValidationFunctions[4] = ManifestAssociatedFunction({ + manifest.validationFunctions[4] = ManifestAssociatedFunction({ executionSelector: IPluginManager.uninstallPlugin.selector, - associatedFunction: ownerUserOpValidationFunction + associatedFunction: ownerValidationFunction }); - manifest.userOpValidationFunctions[5] = ManifestAssociatedFunction({ + manifest.validationFunctions[5] = ManifestAssociatedFunction({ executionSelector: UUPSUpgradeable.upgradeTo.selector, - associatedFunction: ownerUserOpValidationFunction + associatedFunction: ownerValidationFunction }); - manifest.userOpValidationFunctions[6] = ManifestAssociatedFunction({ + manifest.validationFunctions[6] = ManifestAssociatedFunction({ executionSelector: UUPSUpgradeable.upgradeToAndCall.selector, - associatedFunction: ownerUserOpValidationFunction + associatedFunction: ownerValidationFunction }); - ManifestFunction memory ownerOrSelfRuntimeValidationFunction = ManifestFunction({ - functionType: ManifestAssociatedFunctionType.SELF, - functionId: uint8(FunctionId.RUNTIME_VALIDATION_OWNER_OR_SELF), - dependencyIndex: 0 // Unused. - }); - ManifestFunction memory alwaysAllowFunction = ManifestFunction({ + ManifestFunction memory alwaysAllowRuntime = ManifestFunction({ functionType: ManifestAssociatedFunctionType.RUNTIME_VALIDATION_ALWAYS_ALLOW, functionId: 0, // Unused. dependencyIndex: 0 // Unused. }); - manifest.runtimeValidationFunctions = new ManifestAssociatedFunction[](9); - manifest.runtimeValidationFunctions[0] = ManifestAssociatedFunction({ - executionSelector: this.transferOwnership.selector, - associatedFunction: ownerOrSelfRuntimeValidationFunction - }); - manifest.runtimeValidationFunctions[1] = ManifestAssociatedFunction({ - executionSelector: this.owner.selector, - associatedFunction: alwaysAllowFunction - }); - manifest.runtimeValidationFunctions[2] = ManifestAssociatedFunction({ - executionSelector: IStandardExecutor.execute.selector, - associatedFunction: ownerOrSelfRuntimeValidationFunction - }); - manifest.runtimeValidationFunctions[3] = ManifestAssociatedFunction({ - executionSelector: IStandardExecutor.executeBatch.selector, - associatedFunction: ownerOrSelfRuntimeValidationFunction - }); - manifest.runtimeValidationFunctions[4] = ManifestAssociatedFunction({ - executionSelector: IPluginManager.installPlugin.selector, - associatedFunction: ownerOrSelfRuntimeValidationFunction - }); - manifest.runtimeValidationFunctions[5] = ManifestAssociatedFunction({ - executionSelector: IPluginManager.uninstallPlugin.selector, - associatedFunction: ownerOrSelfRuntimeValidationFunction - }); - manifest.runtimeValidationFunctions[6] = ManifestAssociatedFunction({ - executionSelector: UUPSUpgradeable.upgradeTo.selector, - associatedFunction: ownerOrSelfRuntimeValidationFunction - }); - manifest.runtimeValidationFunctions[7] = ManifestAssociatedFunction({ - executionSelector: UUPSUpgradeable.upgradeToAndCall.selector, - associatedFunction: ownerOrSelfRuntimeValidationFunction - }); - manifest.runtimeValidationFunctions[8] = ManifestAssociatedFunction({ + manifest.validationFunctions[7] = ManifestAssociatedFunction({ executionSelector: this.isValidSignature.selector, - associatedFunction: alwaysAllowFunction + associatedFunction: alwaysAllowRuntime }); return manifest; diff --git a/src/samples/plugins/ModularSessionKeyPlugin.sol b/src/samples/plugins/ModularSessionKeyPlugin.sol index e4a4d02b..7af6bb1c 100644 --- a/src/samples/plugins/ModularSessionKeyPlugin.sol +++ b/src/samples/plugins/ModularSessionKeyPlugin.sol @@ -192,7 +192,7 @@ contract ModularSessionKeyPlugin is BasePlugin, IModularSessionKeyPlugin { override returns (uint256) { - if (functionId == uint8(FunctionId.USER_OP_VALIDATION_TEMPORARY_OWNER)) { + if (functionId == uint8(FunctionId.VALIDATION_TEMPORARY_OWNER)) { (address signer, ECDSA.RecoverError err) = userOpHash.toEthSignedMessageHash().tryRecover(userOp.signature); if (err != ECDSA.RecoverError.NoError) { @@ -216,7 +216,7 @@ contract ModularSessionKeyPlugin is BasePlugin, IModularSessionKeyPlugin { view override { - if (functionId == uint8(FunctionId.RUNTIME_VALIDATION_TEMPORARY_OWNER)) { + if (functionId == uint8(FunctionId.VALIDATION_TEMPORARY_OWNER)) { bytes4 selector = bytes4(data[0:4]); bytes memory key = msg.sender.allocateAssociatedStorageKey(0, 1); StoragePointer ptr = key.associatedStorageLookup(keccak256(abi.encodePacked(sender, selector))); @@ -245,65 +245,42 @@ contract ModularSessionKeyPlugin is BasePlugin, IModularSessionKeyPlugin { manifest.executionFunctions[2] = this.addSessionKeyBatch.selector; manifest.executionFunctions[3] = this.removeSessionKeyBatch.selector; - ManifestFunction memory ownerUserOpValidationFunction = ManifestFunction({ + ManifestFunction memory ownerValidationFunction = ManifestFunction({ functionType: ManifestAssociatedFunctionType.DEPENDENCY, functionId: 0, // Unused. dependencyIndex: 0 // Used as first index. }); - manifest.userOpValidationFunctions = new ManifestAssociatedFunction[](4); - manifest.userOpValidationFunctions[0] = ManifestAssociatedFunction({ + manifest.validationFunctions = new ManifestAssociatedFunction[](5); + manifest.validationFunctions[0] = ManifestAssociatedFunction({ executionSelector: this.addSessionKey.selector, - associatedFunction: ownerUserOpValidationFunction + associatedFunction: ownerValidationFunction }); - manifest.userOpValidationFunctions[1] = ManifestAssociatedFunction({ + manifest.validationFunctions[1] = ManifestAssociatedFunction({ executionSelector: this.removeSessionKey.selector, - associatedFunction: ownerUserOpValidationFunction + associatedFunction: ownerValidationFunction }); - manifest.userOpValidationFunctions[2] = ManifestAssociatedFunction({ + manifest.validationFunctions[2] = ManifestAssociatedFunction({ executionSelector: this.addSessionKeyBatch.selector, - associatedFunction: ownerUserOpValidationFunction + associatedFunction: ownerValidationFunction }); - manifest.userOpValidationFunctions[3] = ManifestAssociatedFunction({ + manifest.validationFunctions[3] = ManifestAssociatedFunction({ executionSelector: this.removeSessionKeyBatch.selector, - associatedFunction: ownerUserOpValidationFunction + associatedFunction: ownerValidationFunction }); - ManifestFunction memory ownerOrSelfRuntimeValidationFunction = ManifestFunction({ - functionType: ManifestAssociatedFunctionType.DEPENDENCY, - functionId: 0, // Unused. - dependencyIndex: 1 - }); ManifestFunction memory alwaysAllowFunction = ManifestFunction({ functionType: ManifestAssociatedFunctionType.RUNTIME_VALIDATION_ALWAYS_ALLOW, functionId: 0, // Unused. dependencyIndex: 0 // Unused. }); - manifest.runtimeValidationFunctions = new ManifestAssociatedFunction[](5); - manifest.runtimeValidationFunctions[0] = ManifestAssociatedFunction({ - executionSelector: this.addSessionKey.selector, - associatedFunction: ownerOrSelfRuntimeValidationFunction - }); - manifest.runtimeValidationFunctions[1] = ManifestAssociatedFunction({ - executionSelector: this.removeSessionKey.selector, - associatedFunction: ownerOrSelfRuntimeValidationFunction - }); - manifest.runtimeValidationFunctions[2] = ManifestAssociatedFunction({ - executionSelector: this.addSessionKeyBatch.selector, - associatedFunction: ownerOrSelfRuntimeValidationFunction - }); - manifest.runtimeValidationFunctions[3] = ManifestAssociatedFunction({ - executionSelector: this.removeSessionKeyBatch.selector, - associatedFunction: ownerOrSelfRuntimeValidationFunction - }); - manifest.runtimeValidationFunctions[4] = ManifestAssociatedFunction({ + manifest.validationFunctions[4] = ManifestAssociatedFunction({ executionSelector: this.getSessionDuration.selector, associatedFunction: alwaysAllowFunction }); - manifest.dependencyInterfaceIds = new bytes4[](2); + manifest.dependencyInterfaceIds = new bytes4[](1); manifest.dependencyInterfaceIds[0] = type(ISingleOwnerPlugin).interfaceId; - manifest.dependencyInterfaceIds[1] = type(ISingleOwnerPlugin).interfaceId; return manifest; } diff --git a/src/samples/plugins/TokenSessionKeyPlugin.sol b/src/samples/plugins/TokenSessionKeyPlugin.sol index 9e2e8abd..ebc58eda 100644 --- a/src/samples/plugins/TokenSessionKeyPlugin.sol +++ b/src/samples/plugins/TokenSessionKeyPlugin.sol @@ -68,32 +68,20 @@ contract TokenSessionKeyPlugin is BasePlugin, ITokenSessionKeyPlugin { manifest.executionFunctions = new bytes4[](1); manifest.executionFunctions[0] = this.transferFromSessionKey.selector; - ManifestFunction memory tempOwnerUserOpValidationFunction = ManifestFunction({ + ManifestFunction memory tempOwnerValidationFunction = ManifestFunction({ functionType: ManifestAssociatedFunctionType.DEPENDENCY, functionId: 0, // Unused dependencyIndex: 0 // Used as first index }); - ManifestFunction memory tempOwnerRuntimeValidationFunction = ManifestFunction({ - functionType: ManifestAssociatedFunctionType.DEPENDENCY, - functionId: 0, // Unused - dependencyIndex: 1 // Used as second index - }); - - manifest.userOpValidationFunctions = new ManifestAssociatedFunction[](1); - manifest.userOpValidationFunctions[0] = ManifestAssociatedFunction({ - executionSelector: this.transferFromSessionKey.selector, - associatedFunction: tempOwnerUserOpValidationFunction - }); - manifest.runtimeValidationFunctions = new ManifestAssociatedFunction[](1); - manifest.runtimeValidationFunctions[0] = ManifestAssociatedFunction({ + manifest.validationFunctions = new ManifestAssociatedFunction[](1); + manifest.validationFunctions[0] = ManifestAssociatedFunction({ executionSelector: this.transferFromSessionKey.selector, - associatedFunction: tempOwnerRuntimeValidationFunction + associatedFunction: tempOwnerValidationFunction }); - manifest.dependencyInterfaceIds = new bytes4[](2); + manifest.dependencyInterfaceIds = new bytes4[](1); manifest.dependencyInterfaceIds[0] = type(IModularSessionKeyPlugin).interfaceId; - manifest.dependencyInterfaceIds[1] = type(IModularSessionKeyPlugin).interfaceId; bytes4[] memory permittedExternalSelectors = new bytes4[](1); permittedExternalSelectors[0] = TRANSFERFROM_SELECTOR; diff --git a/src/samples/plugins/interfaces/ISessionKeyPlugin.sol b/src/samples/plugins/interfaces/ISessionKeyPlugin.sol index 88d31f1a..18a2204c 100644 --- a/src/samples/plugins/interfaces/ISessionKeyPlugin.sol +++ b/src/samples/plugins/interfaces/ISessionKeyPlugin.sol @@ -5,8 +5,7 @@ import {UserOperation} from "@eth-infinitism/account-abstraction/interfaces/User interface IModularSessionKeyPlugin { enum FunctionId { - RUNTIME_VALIDATION_TEMPORARY_OWNER, - USER_OP_VALIDATION_TEMPORARY_OWNER + VALIDATION_TEMPORARY_OWNER } /// @notice This event is emitted when a session key is added to the account. diff --git a/standard/ERCs/erc-6900.md b/standard/ERCs/erc-6900.md index 9f9de2ce..ce775c15 100644 --- a/standard/ERCs/erc-6900.md +++ b/standard/ERCs/erc-6900.md @@ -215,8 +215,7 @@ interface IAccountLoupe { /// @notice Config for an execution function, given a selector. struct ExecutionFunctionConfig { address plugin; - FunctionReference userOpValidationFunction; - FunctionReference runtimeValidationFunction; + FunctionReference validationFunction; } /// @notice Pre and post hooks for a given selector. @@ -423,8 +422,7 @@ struct PluginManifest { // plugin MUST still be able to spend up to the balance that it sends to the account in the same call. bool canSpendNativeToken; ManifestExternalCallPermission[] permittedExternalCalls; - ManifestAssociatedFunction[] userOpValidationFunctions; - ManifestAssociatedFunction[] runtimeValidationFunctions; + ManifestAssociatedFunction[] validationFunctions; ManifestAssociatedFunction[] preUserOpValidationHooks; ManifestAssociatedFunction[] preRuntimeValidationHooks; ManifestExecutionHook[] executionHooks; diff --git a/test/account/AccountExecHooks.t.sol b/test/account/AccountExecHooks.t.sol index a9b23875..bf432e03 100644 --- a/test/account/AccountExecHooks.t.sol +++ b/test/account/AccountExecHooks.t.sol @@ -60,7 +60,7 @@ contract AccountExecHooksTest is OptimizedTest { m1.executionFunctions.push(_EXEC_SELECTOR); - m1.runtimeValidationFunctions.push( + m1.validationFunctions.push( ManifestAssociatedFunction({ executionSelector: _EXEC_SELECTOR, associatedFunction: ManifestFunction({ diff --git a/test/account/AccountLoupe.t.sol b/test/account/AccountLoupe.t.sol index 17f66788..35c7b6e2 100644 --- a/test/account/AccountLoupe.t.sol +++ b/test/account/AccountLoupe.t.sol @@ -31,8 +31,7 @@ contract AccountLoupeTest is OptimizedTest { UpgradeableModularAccount public account1; - FunctionReference public ownerUserOpValidation; - FunctionReference public ownerRuntimeValidation; + FunctionReference public ownerValidation; event ReceivedCall(bytes msgData, uint256 msgValue); @@ -48,11 +47,8 @@ contract AccountLoupeTest is OptimizedTest { bytes32 manifestHash = keccak256(abi.encode(comprehensivePlugin.pluginManifest())); account1.installPlugin(address(comprehensivePlugin), manifestHash, "", new FunctionReference[](0)); - ownerUserOpValidation = FunctionReferenceLib.pack( - address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.USER_OP_VALIDATION_OWNER) - ); - ownerRuntimeValidation = FunctionReferenceLib.pack( - address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.RUNTIME_VALIDATION_OWNER_OR_SELF) + ownerValidation = FunctionReferenceLib.pack( + address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER_OR_SELF) ); } @@ -67,28 +63,22 @@ contract AccountLoupeTest is OptimizedTest { function test_pluginLoupe_getExecutionFunctionConfig_native() public { bytes4[] memory selectorsToCheck = new bytes4[](5); - FunctionReference[] memory expectedUserOpValidations = new FunctionReference[](5); - FunctionReference[] memory expectedRuntimeValidations = new FunctionReference[](5); + FunctionReference[] memory expectedValidations = new FunctionReference[](5); selectorsToCheck[0] = IStandardExecutor.execute.selector; - expectedUserOpValidations[0] = ownerUserOpValidation; - expectedRuntimeValidations[0] = ownerRuntimeValidation; + expectedValidations[0] = ownerValidation; selectorsToCheck[1] = IStandardExecutor.executeBatch.selector; - expectedUserOpValidations[1] = ownerUserOpValidation; - expectedRuntimeValidations[1] = ownerRuntimeValidation; + expectedValidations[1] = ownerValidation; selectorsToCheck[2] = UUPSUpgradeable.upgradeToAndCall.selector; - expectedUserOpValidations[2] = ownerUserOpValidation; - expectedRuntimeValidations[2] = ownerRuntimeValidation; + expectedValidations[2] = ownerValidation; selectorsToCheck[3] = IPluginManager.installPlugin.selector; - expectedUserOpValidations[3] = ownerUserOpValidation; - expectedRuntimeValidations[3] = ownerRuntimeValidation; + expectedValidations[3] = ownerValidation; selectorsToCheck[4] = IPluginManager.uninstallPlugin.selector; - expectedUserOpValidations[4] = ownerUserOpValidation; - expectedRuntimeValidations[4] = ownerRuntimeValidation; + expectedValidations[4] = ownerValidation; for (uint256 i = 0; i < selectorsToCheck.length; i++) { IAccountLoupe.ExecutionFunctionConfig memory config = @@ -96,12 +86,8 @@ contract AccountLoupeTest is OptimizedTest { assertEq(config.plugin, address(account1)); assertEq( - FunctionReference.unwrap(config.userOpValidationFunction), - FunctionReference.unwrap(expectedUserOpValidations[i]) - ); - assertEq( - FunctionReference.unwrap(config.runtimeValidationFunction), - FunctionReference.unwrap(expectedRuntimeValidations[i]) + FunctionReference.unwrap(config.validationFunction), + FunctionReference.unwrap(expectedValidations[i]) ); } } @@ -109,22 +95,17 @@ contract AccountLoupeTest is OptimizedTest { function test_pluginLoupe_getExecutionFunctionConfig_plugin() public { bytes4[] memory selectorsToCheck = new bytes4[](2); address[] memory expectedPluginAddress = new address[](2); - FunctionReference[] memory expectedUserOpValidations = new FunctionReference[](2); - FunctionReference[] memory expectedRuntimeValidations = new FunctionReference[](2); + FunctionReference[] memory expectedValidations = new FunctionReference[](2); selectorsToCheck[0] = comprehensivePlugin.foo.selector; expectedPluginAddress[0] = address(comprehensivePlugin); - expectedUserOpValidations[0] = FunctionReferenceLib.pack( - address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.USER_OP_VALIDATION) - ); - expectedRuntimeValidations[0] = FunctionReferenceLib.pack( - address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.RUNTIME_VALIDATION) + expectedValidations[0] = FunctionReferenceLib.pack( + address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.VALIDATION) ); selectorsToCheck[1] = singleOwnerPlugin.transferOwnership.selector; expectedPluginAddress[1] = address(singleOwnerPlugin); - expectedUserOpValidations[1] = ownerUserOpValidation; - expectedRuntimeValidations[1] = ownerRuntimeValidation; + expectedValidations[1] = ownerValidation; for (uint256 i = 0; i < selectorsToCheck.length; i++) { IAccountLoupe.ExecutionFunctionConfig memory config = @@ -132,12 +113,8 @@ contract AccountLoupeTest is OptimizedTest { assertEq(config.plugin, expectedPluginAddress[i]); assertEq( - FunctionReference.unwrap(config.userOpValidationFunction), - FunctionReference.unwrap(expectedUserOpValidations[i]) - ); - assertEq( - FunctionReference.unwrap(config.runtimeValidationFunction), - FunctionReference.unwrap(expectedRuntimeValidations[i]) + FunctionReference.unwrap(config.validationFunction), + FunctionReference.unwrap(expectedValidations[i]) ); } } diff --git a/test/account/ManifestValidity.t.sol b/test/account/ManifestValidity.t.sol index 55b2c9ba..169d6c81 100644 --- a/test/account/ManifestValidity.t.sol +++ b/test/account/ManifestValidity.t.sol @@ -11,7 +11,6 @@ import {SingleOwnerPlugin} from "../../src/plugins/owner/SingleOwnerPlugin.sol"; import {MSCAFactoryFixture} from "../mocks/MSCAFactoryFixture.sol"; import { - BadValidationMagicValue_UserOp_Plugin, BadValidationMagicValue_PreRuntimeValidationHook_Plugin, BadValidationMagicValue_PreUserOpValidationHook_Plugin, BadValidationMagicValue_PreExecHook_Plugin, @@ -39,22 +38,6 @@ contract ManifestValidityTest is OptimizedTest { account = factory.createAccount(address(this), 0); } - // Tests that the plugin manager rejects a plugin with a user op validationFunction set to "validation always - // allow" - function test_ManifestValidity_invalid_ValidationAlwaysAllow_UserOpValidationFunction() public { - BadValidationMagicValue_UserOp_Plugin plugin = new BadValidationMagicValue_UserOp_Plugin(); - - bytes32 manifestHash = keccak256(abi.encode(plugin.pluginManifest())); - - vm.expectRevert(abi.encodeWithSelector(PluginManagerInternals.InvalidPluginManifest.selector)); - account.installPlugin({ - plugin: address(plugin), - manifestHash: manifestHash, - pluginInstallData: "", - dependencies: new FunctionReference[](0) - }); - } - // Tests that the plugin manager rejects a plugin with a pre-runtime validation hook set to "validation always // allow" function test_ManifestValidity_invalid_ValidationAlwaysAllow_PreRuntimeValidationHook() public { diff --git a/test/account/UpgradeableModularAccount.t.sol b/test/account/UpgradeableModularAccount.t.sol index 1005cdf2..31f9b8f5 100644 --- a/test/account/UpgradeableModularAccount.t.sol +++ b/test/account/UpgradeableModularAccount.t.sol @@ -202,7 +202,7 @@ contract UpgradeableModularAccountTest is OptimizedTest { initCode: "", callData: abi.encodeCall( UpgradeableModularAccount.execute, (address(counter), 0, abi.encodeCall(counter.increment, ())) - ), + ), callGasLimit: CALL_GAS_LIMIT, verificationGasLimit: VERIFICATION_GAS_LIMIT, preVerificationGas: 0, diff --git a/test/comparison/CompareSimpleAccount.t.sol b/test/comparison/CompareSimpleAccount.t.sol index d379db5d..e9ea4cc2 100644 --- a/test/comparison/CompareSimpleAccount.t.sol +++ b/test/comparison/CompareSimpleAccount.t.sol @@ -135,7 +135,7 @@ contract CompareSimpleAccountTest is Test { initCode: "", callData: abi.encodeCall( SimpleAccount.execute, (address(counter), 0, abi.encodeCall(Counter.increment, ())) - ), + ), callGasLimit: 5000000, verificationGasLimit: 5000000, preVerificationGas: 0, diff --git a/test/mocks/plugins/BadTransferOwnershipPlugin.sol b/test/mocks/plugins/BadTransferOwnershipPlugin.sol index 0b291c03..03da8019 100644 --- a/test/mocks/plugins/BadTransferOwnershipPlugin.sol +++ b/test/mocks/plugins/BadTransferOwnershipPlugin.sol @@ -47,8 +47,8 @@ contract BadTransferOwnershipPlugin is BasePlugin { manifest.permittedExecutionSelectors = new bytes4[](1); manifest.permittedExecutionSelectors[0] = ISingleOwnerPlugin.transferOwnership.selector; - manifest.runtimeValidationFunctions = new ManifestAssociatedFunction[](1); - manifest.runtimeValidationFunctions[0] = ManifestAssociatedFunction({ + manifest.validationFunctions = new ManifestAssociatedFunction[](1); + manifest.validationFunctions[0] = ManifestAssociatedFunction({ executionSelector: this.evilTransferOwnership.selector, associatedFunction: ManifestFunction({ functionType: ManifestAssociatedFunctionType.RUNTIME_VALIDATION_ALWAYS_ALLOW, diff --git a/test/mocks/plugins/ComprehensivePlugin.sol b/test/mocks/plugins/ComprehensivePlugin.sol index 30b656cd..442b39a6 100644 --- a/test/mocks/plugins/ComprehensivePlugin.sol +++ b/test/mocks/plugins/ComprehensivePlugin.sol @@ -18,10 +18,9 @@ contract ComprehensivePlugin is BasePlugin { enum FunctionId { PRE_USER_OP_VALIDATION_HOOK_1, PRE_USER_OP_VALIDATION_HOOK_2, - USER_OP_VALIDATION, PRE_RUNTIME_VALIDATION_HOOK_1, PRE_RUNTIME_VALIDATION_HOOK_2, - RUNTIME_VALIDATION, + VALIDATION, PRE_EXECUTION_HOOK, PRE_PERMITTED_CALL_EXECUTION_HOOK, POST_EXECUTION_HOOK, @@ -66,7 +65,7 @@ contract ComprehensivePlugin is BasePlugin { override returns (uint256) { - if (functionId == uint8(FunctionId.USER_OP_VALIDATION)) { + if (functionId == uint8(FunctionId.VALIDATION)) { return 0; } revert NotImplemented(); @@ -86,7 +85,7 @@ contract ComprehensivePlugin is BasePlugin { pure override { - if (functionId == uint8(FunctionId.RUNTIME_VALIDATION)) { + if (functionId == uint8(FunctionId.VALIDATION)) { return; } revert NotImplemented(); @@ -121,26 +120,15 @@ contract ComprehensivePlugin is BasePlugin { manifest.executionFunctions = new bytes4[](1); manifest.executionFunctions[0] = this.foo.selector; - ManifestFunction memory fooUserOpValidationFunction = ManifestFunction({ + ManifestFunction memory fooValidationFunction = ManifestFunction({ functionType: ManifestAssociatedFunctionType.SELF, - functionId: uint8(FunctionId.USER_OP_VALIDATION), + functionId: uint8(FunctionId.VALIDATION), dependencyIndex: 0 // Unused. }); - manifest.userOpValidationFunctions = new ManifestAssociatedFunction[](1); - manifest.userOpValidationFunctions[0] = ManifestAssociatedFunction({ + manifest.validationFunctions = new ManifestAssociatedFunction[](1); + manifest.validationFunctions[0] = ManifestAssociatedFunction({ executionSelector: this.foo.selector, - associatedFunction: fooUserOpValidationFunction - }); - - ManifestFunction memory fooRuntimeValidationFunction = ManifestFunction({ - functionType: ManifestAssociatedFunctionType.SELF, - functionId: uint8(FunctionId.RUNTIME_VALIDATION), - dependencyIndex: 0 // Unused. - }); - manifest.runtimeValidationFunctions = new ManifestAssociatedFunction[](1); - manifest.runtimeValidationFunctions[0] = ManifestAssociatedFunction({ - executionSelector: this.foo.selector, - associatedFunction: fooRuntimeValidationFunction + associatedFunction: fooValidationFunction }); manifest.preUserOpValidationHooks = new ManifestAssociatedFunction[](4); diff --git a/test/mocks/plugins/ExecFromPluginPermissionsMocks.sol b/test/mocks/plugins/ExecFromPluginPermissionsMocks.sol index e858d8ee..3eea3ac0 100644 --- a/test/mocks/plugins/ExecFromPluginPermissionsMocks.sol +++ b/test/mocks/plugins/ExecFromPluginPermissionsMocks.sol @@ -45,7 +45,7 @@ contract EFPCallerPlugin is BaseTestPlugin { manifest.executionFunctions[9] = this.getNumberCounter3.selector; manifest.executionFunctions[10] = this.incrementCounter3.selector; - manifest.runtimeValidationFunctions = new ManifestAssociatedFunction[](11); + manifest.validationFunctions = new ManifestAssociatedFunction[](11); ManifestFunction memory alwaysAllowValidationFunction = ManifestFunction({ functionType: ManifestAssociatedFunctionType.RUNTIME_VALIDATION_ALWAYS_ALLOW, @@ -54,7 +54,7 @@ contract EFPCallerPlugin is BaseTestPlugin { }); for (uint256 i = 0; i < manifest.executionFunctions.length; i++) { - manifest.runtimeValidationFunctions[i] = ManifestAssociatedFunction({ + manifest.validationFunctions[i] = ManifestAssociatedFunction({ executionSelector: manifest.executionFunctions[i], associatedFunction: alwaysAllowValidationFunction }); @@ -181,8 +181,8 @@ contract EFPCallerPluginAnyExternal is BaseTestPlugin { manifest.executionFunctions = new bytes4[](1); manifest.executionFunctions[0] = this.passthroughExecute.selector; - manifest.runtimeValidationFunctions = new ManifestAssociatedFunction[](1); - manifest.runtimeValidationFunctions[0] = ManifestAssociatedFunction({ + manifest.validationFunctions = new ManifestAssociatedFunction[](1); + manifest.validationFunctions[0] = ManifestAssociatedFunction({ executionSelector: this.passthroughExecute.selector, associatedFunction: ManifestFunction({ functionType: ManifestAssociatedFunctionType.RUNTIME_VALIDATION_ALWAYS_ALLOW, diff --git a/test/mocks/plugins/ManifestValidityMocks.sol b/test/mocks/plugins/ManifestValidityMocks.sol index 27243f79..f19c67ca 100644 --- a/test/mocks/plugins/ManifestValidityMocks.sol +++ b/test/mocks/plugins/ManifestValidityMocks.sol @@ -16,36 +16,6 @@ import {IPlugin} from "../../../src/interfaces/IPlugin.sol"; import {BaseTestPlugin} from "./BaseTestPlugin.sol"; -contract BadValidationMagicValue_UserOp_Plugin is BaseTestPlugin { - function onInstall(bytes calldata) external override {} - - function onUninstall(bytes calldata) external override {} - - function foo() external pure returns (bytes32) { - return keccak256("bar"); - } - - function pluginManifest() external pure override returns (PluginManifest memory) { - PluginManifest memory manifest; - - manifest.executionFunctions = new bytes4[](1); - manifest.executionFunctions[0] = this.foo.selector; - - manifest.userOpValidationFunctions = new ManifestAssociatedFunction[](1); - manifest.userOpValidationFunctions[0] = ManifestAssociatedFunction({ - executionSelector: this.foo.selector, - associatedFunction: ManifestFunction({ - // Illegal assignment: validation always allow only usable on runtime validation functions - functionType: ManifestAssociatedFunctionType.RUNTIME_VALIDATION_ALWAYS_ALLOW, - functionId: 0, - dependencyIndex: 0 - }) - }); - - return manifest; - } -} - contract BadValidationMagicValue_PreRuntimeValidationHook_Plugin is BaseTestPlugin { function onInstall(bytes calldata) external override {} @@ -61,8 +31,8 @@ contract BadValidationMagicValue_PreRuntimeValidationHook_Plugin is BaseTestPlug manifest.executionFunctions = new bytes4[](1); manifest.executionFunctions[0] = this.foo.selector; - manifest.runtimeValidationFunctions = new ManifestAssociatedFunction[](1); - manifest.runtimeValidationFunctions[0] = ManifestAssociatedFunction({ + manifest.validationFunctions = new ManifestAssociatedFunction[](1); + manifest.validationFunctions[0] = ManifestAssociatedFunction({ executionSelector: this.foo.selector, associatedFunction: ManifestFunction({ functionType: ManifestAssociatedFunctionType.SELF, @@ -101,8 +71,8 @@ contract BadValidationMagicValue_PreUserOpValidationHook_Plugin is BaseTestPlugi manifest.executionFunctions = new bytes4[](1); manifest.executionFunctions[0] = this.foo.selector; - manifest.userOpValidationFunctions = new ManifestAssociatedFunction[](1); - manifest.userOpValidationFunctions[0] = ManifestAssociatedFunction({ + manifest.validationFunctions = new ManifestAssociatedFunction[](1); + manifest.validationFunctions[0] = ManifestAssociatedFunction({ executionSelector: this.foo.selector, associatedFunction: ManifestFunction({ functionType: ManifestAssociatedFunctionType.SELF, @@ -212,8 +182,8 @@ contract BadHookMagicValue_UserOpValidationFunction_Plugin is BaseTestPlugin { manifest.executionFunctions = new bytes4[](1); manifest.executionFunctions[0] = this.foo.selector; - manifest.userOpValidationFunctions = new ManifestAssociatedFunction[](1); - manifest.userOpValidationFunctions[0] = ManifestAssociatedFunction({ + manifest.validationFunctions = new ManifestAssociatedFunction[](1); + manifest.validationFunctions[0] = ManifestAssociatedFunction({ executionSelector: this.foo.selector, associatedFunction: ManifestFunction({ functionType: ManifestAssociatedFunctionType.PRE_HOOK_ALWAYS_DENY, @@ -241,8 +211,8 @@ contract BadHookMagicValue_RuntimeValidationFunction_Plugin is BaseTestPlugin { manifest.executionFunctions = new bytes4[](1); manifest.executionFunctions[0] = this.foo.selector; - manifest.runtimeValidationFunctions = new ManifestAssociatedFunction[](1); - manifest.runtimeValidationFunctions[0] = ManifestAssociatedFunction({ + manifest.validationFunctions = new ManifestAssociatedFunction[](1); + manifest.validationFunctions[0] = ManifestAssociatedFunction({ executionSelector: this.foo.selector, associatedFunction: ManifestFunction({ functionType: ManifestAssociatedFunctionType.PRE_HOOK_ALWAYS_DENY, diff --git a/test/mocks/plugins/ReturnDataPluginMocks.sol b/test/mocks/plugins/ReturnDataPluginMocks.sol index 34ce8e28..85925c0e 100644 --- a/test/mocks/plugins/ReturnDataPluginMocks.sol +++ b/test/mocks/plugins/ReturnDataPluginMocks.sol @@ -45,8 +45,8 @@ contract ResultCreatorPlugin is BaseTestPlugin { manifest.executionFunctions[0] = this.foo.selector; manifest.executionFunctions[1] = this.bar.selector; - manifest.runtimeValidationFunctions = new ManifestAssociatedFunction[](1); - manifest.runtimeValidationFunctions[0] = ManifestAssociatedFunction({ + manifest.validationFunctions = new ManifestAssociatedFunction[](1); + manifest.validationFunctions[0] = ManifestAssociatedFunction({ executionSelector: this.foo.selector, associatedFunction: ManifestFunction({ functionType: ManifestAssociatedFunctionType.RUNTIME_VALIDATION_ALWAYS_ALLOW, @@ -121,8 +121,8 @@ contract ResultConsumerPlugin is BaseTestPlugin { manifest.executionFunctions[0] = this.checkResultEFPFallback.selector; manifest.executionFunctions[1] = this.checkResultEFPExternal.selector; - manifest.runtimeValidationFunctions = new ManifestAssociatedFunction[](2); - manifest.runtimeValidationFunctions[0] = ManifestAssociatedFunction({ + manifest.validationFunctions = new ManifestAssociatedFunction[](2); + manifest.validationFunctions[0] = ManifestAssociatedFunction({ executionSelector: this.checkResultEFPFallback.selector, associatedFunction: ManifestFunction({ functionType: ManifestAssociatedFunctionType.RUNTIME_VALIDATION_ALWAYS_ALLOW, @@ -130,7 +130,7 @@ contract ResultConsumerPlugin is BaseTestPlugin { dependencyIndex: 0 }) }); - manifest.runtimeValidationFunctions[1] = ManifestAssociatedFunction({ + manifest.validationFunctions[1] = ManifestAssociatedFunction({ executionSelector: this.checkResultEFPExternal.selector, associatedFunction: ManifestFunction({ functionType: ManifestAssociatedFunctionType.RUNTIME_VALIDATION_ALWAYS_ALLOW, diff --git a/test/mocks/plugins/ValidationPluginMocks.sol b/test/mocks/plugins/ValidationPluginMocks.sol index 1e6cc941..4859a7e9 100644 --- a/test/mocks/plugins/ValidationPluginMocks.sol +++ b/test/mocks/plugins/ValidationPluginMocks.sol @@ -78,8 +78,8 @@ contract MockUserOpValidationPlugin is MockBaseUserOpValidationPlugin { manifest.executionFunctions = new bytes4[](1); manifest.executionFunctions[0] = this.foo.selector; - manifest.userOpValidationFunctions = new ManifestAssociatedFunction[](1); - manifest.userOpValidationFunctions[0] = ManifestAssociatedFunction({ + manifest.validationFunctions = new ManifestAssociatedFunction[](1); + manifest.validationFunctions[0] = ManifestAssociatedFunction({ executionSelector: this.foo.selector, associatedFunction: ManifestFunction({ functionType: ManifestAssociatedFunctionType.SELF, @@ -121,8 +121,8 @@ contract MockUserOpValidation1HookPlugin is MockBaseUserOpValidationPlugin { functionId: uint8(FunctionId.USER_OP_VALIDATION), dependencyIndex: 0 // Unused. }); - manifest.userOpValidationFunctions = new ManifestAssociatedFunction[](1); - manifest.userOpValidationFunctions[0] = ManifestAssociatedFunction({ + manifest.validationFunctions = new ManifestAssociatedFunction[](1); + manifest.validationFunctions[0] = ManifestAssociatedFunction({ executionSelector: this.bar.selector, associatedFunction: userOpValidationFunctionRef }); @@ -173,8 +173,8 @@ contract MockUserOpValidation2HookPlugin is MockBaseUserOpValidationPlugin { functionId: uint8(FunctionId.USER_OP_VALIDATION), dependencyIndex: 0 // Unused. }); - manifest.userOpValidationFunctions = new ManifestAssociatedFunction[](1); - manifest.userOpValidationFunctions[0] = ManifestAssociatedFunction({ + manifest.validationFunctions = new ManifestAssociatedFunction[](1); + manifest.validationFunctions[0] = ManifestAssociatedFunction({ executionSelector: this.baz.selector, associatedFunction: userOpValidationFunctionRef }); diff --git a/test/plugin/SingleOwnerPlugin.t.sol b/test/plugin/SingleOwnerPlugin.t.sol index 090e5be1..5784059e 100644 --- a/test/plugin/SingleOwnerPlugin.t.sol +++ b/test/plugin/SingleOwnerPlugin.t.sol @@ -113,13 +113,13 @@ contract SingleOwnerPluginTest is OptimizedTest { plugin.transferOwnership(owner1); assertEq(owner1, plugin.owner()); plugin.runtimeValidationFunction( - uint8(ISingleOwnerPlugin.FunctionId.RUNTIME_VALIDATION_OWNER_OR_SELF), owner1, 0, "" + uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER_OR_SELF), owner1, 0, "" ); vm.startPrank(b); vm.expectRevert(ISingleOwnerPlugin.NotAuthorized.selector); plugin.runtimeValidationFunction( - uint8(ISingleOwnerPlugin.FunctionId.RUNTIME_VALIDATION_OWNER_OR_SELF), owner1, 0, "" + uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER_OR_SELF), owner1, 0, "" ); } @@ -136,7 +136,7 @@ contract SingleOwnerPluginTest is OptimizedTest { // sig check should fail uint256 success = plugin.userOpValidationFunction( - uint8(ISingleOwnerPlugin.FunctionId.USER_OP_VALIDATION_OWNER), userOp, userOpHash + uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER_OR_SELF), userOp, userOpHash ); assertEq(success, 1); @@ -146,7 +146,7 @@ contract SingleOwnerPluginTest is OptimizedTest { // sig check should pass success = plugin.userOpValidationFunction( - uint8(ISingleOwnerPlugin.FunctionId.USER_OP_VALIDATION_OWNER), userOp, userOpHash + uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER_OR_SELF), userOp, userOpHash ); assertEq(success, 0); } diff --git a/test/samples/plugins/ModularSessionKeyPlugin.t.sol b/test/samples/plugins/ModularSessionKeyPlugin.t.sol index bdbcbfff..e20cbfca 100644 --- a/test/samples/plugins/ModularSessionKeyPlugin.t.sol +++ b/test/samples/plugins/ModularSessionKeyPlugin.t.sol @@ -99,12 +99,9 @@ contract ModularSessionKeyPluginTest is Test { vm.deal(address(account), 1 ether); vm.startPrank(owner); - FunctionReference[] memory modularSessionDependency = new FunctionReference[](2); + FunctionReference[] memory modularSessionDependency = new FunctionReference[](1); modularSessionDependency[0] = FunctionReferenceLib.pack( - address(ownerPlugin), uint8(ISingleOwnerPlugin.FunctionId.USER_OP_VALIDATION_OWNER) - ); - modularSessionDependency[1] = FunctionReferenceLib.pack( - address(ownerPlugin), uint8(ISingleOwnerPlugin.FunctionId.RUNTIME_VALIDATION_OWNER_OR_SELF) + address(ownerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER_OR_SELF) ); bytes32 modularSessionKeyManifestHash = keccak256(abi.encode(modularSessionKeyPlugin.pluginManifest())); @@ -130,14 +127,9 @@ contract ModularSessionKeyPluginTest is Test { dependencies: modularSessionDependency }); - FunctionReference[] memory tokenSessionDependency = new FunctionReference[](2); + FunctionReference[] memory tokenSessionDependency = new FunctionReference[](1); tokenSessionDependency[0] = FunctionReferenceLib.pack( - address(modularSessionKeyPlugin), - uint8(IModularSessionKeyPlugin.FunctionId.USER_OP_VALIDATION_TEMPORARY_OWNER) - ); - tokenSessionDependency[1] = FunctionReferenceLib.pack( - address(modularSessionKeyPlugin), - uint8(IModularSessionKeyPlugin.FunctionId.RUNTIME_VALIDATION_TEMPORARY_OWNER) + address(modularSessionKeyPlugin), uint8(IModularSessionKeyPlugin.FunctionId.VALIDATION_TEMPORARY_OWNER) ); bytes32 tokenSessionKeyManifestHash = keccak256(abi.encode(tokenSessionKeyPlugin.pluginManifest())); @@ -245,7 +237,7 @@ contract ModularSessionKeyPluginTest is Test { abi.encodeWithSelector( UpgradeableModularAccount.RuntimeValidationFunctionReverted.selector, address(modularSessionKeyPlugin), - IModularSessionKeyPlugin.FunctionId.RUNTIME_VALIDATION_TEMPORARY_OWNER, + IModularSessionKeyPlugin.FunctionId.VALIDATION_TEMPORARY_OWNER, revertReason ) ); @@ -286,7 +278,7 @@ contract ModularSessionKeyPluginTest is Test { abi.encodeWithSelector( UpgradeableModularAccount.RuntimeValidationFunctionReverted.selector, address(modularSessionKeyPlugin), - IModularSessionKeyPlugin.FunctionId.RUNTIME_VALIDATION_TEMPORARY_OWNER, + IModularSessionKeyPlugin.FunctionId.VALIDATION_TEMPORARY_OWNER, revertReason ) ); @@ -308,7 +300,7 @@ contract ModularSessionKeyPluginTest is Test { abi.encodeWithSelector( UpgradeableModularAccount.RuntimeValidationFunctionReverted.selector, address(modularSessionKeyPlugin), - IModularSessionKeyPlugin.FunctionId.RUNTIME_VALIDATION_TEMPORARY_OWNER, + IModularSessionKeyPlugin.FunctionId.VALIDATION_TEMPORARY_OWNER, revertReason ) ); From cf5355a104926c44e8c5a1df03eb2b9db1417ebc Mon Sep 17 00:00:00 2001 From: adam-alchemy <127769144+adam-alchemy@users.noreply.github.com> Date: Tue, 16 Apr 2024 10:41:08 -0700 Subject: [PATCH 003/111] refactor: [v0.8-develop] 6900 iteration setup (#43) --- .solhint-test.json | 4 +- package.json | 4 +- src/libraries/AssociatedLinkedListSetLib.sol | 1020 +++++++++-------- src/libraries/PluginStorageLib.sol | 103 +- src/plugins/owner/ISingleOwnerPlugin.sol | 2 - src/plugins/owner/SingleOwnerPlugin.sol | 61 +- .../plugins/ModularSessionKeyPlugin.sol | 735 ++++++------ src/samples/plugins/TokenSessionKeyPlugin.sol | 228 ++-- .../plugins/interfaces/ISessionKeyPlugin.sol | 187 +-- .../interfaces/ITokenSessionKeyPlugin.sol | 28 +- .../AssociatedLinkedListSetLib.t.sol | 334 +++--- test/libraries/PluginStorageLib.t.sol | 147 +-- .../plugins/BadTransferOwnershipPlugin.sol | 3 - .../ExecFromPluginPermissionsMocks.sol | 4 - test/mocks/plugins/ManifestValidityMocks.sol | 12 +- test/mocks/plugins/ReturnDataPluginMocks.sol | 13 +- .../plugins/ModularSessionKeyPlugin.t.sol | 740 ++++++------ 17 files changed, 1824 insertions(+), 1801 deletions(-) diff --git a/.solhint-test.json b/.solhint-test.json index c3efb843..cbd7bf02 100644 --- a/.solhint-test.json +++ b/.solhint-test.json @@ -11,7 +11,9 @@ "modifier-name-mixedcase": ["error"], "private-vars-leading-underscore": ["error"], "no-inline-assembly": "off", - "avoid-low-level-calls": "off" + "avoid-low-level-calls": "off", + "one-contract-per-file": "off", + "no-empty-blocks": "off" } } \ No newline at end of file diff --git a/package.json b/package.json index e6510e73..a9cc7c23 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ }, "scripts": { "lint": "pnpm lint:src && pnpm lint:test", - "lint:src": "solhint -c .solhint-src.json ./src/**/*.sol", - "lint:test": "solhint -c .solhint-test.json ./test/**/*.sol" + "lint:src": "solhint -c .solhint-src.json './src/**/*.sol'", + "lint:test": "solhint -c .solhint-test.json './test/**/*.sol'" } } diff --git a/src/libraries/AssociatedLinkedListSetLib.sol b/src/libraries/AssociatedLinkedListSetLib.sol index a6fa8ebf..97974b2c 100644 --- a/src/libraries/AssociatedLinkedListSetLib.sol +++ b/src/libraries/AssociatedLinkedListSetLib.sol @@ -1,507 +1,519 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -type SetValue is bytes30; - -/// @dev The sentinel value is used to indicate the head and tail of the list. -bytes32 constant SENTINEL_VALUE = bytes32(uint256(1)); - -/// @dev Removing the last element will result in this flag not being set correctly, but all operations will -/// function normally, albeit with one extra sload for getAll. -bytes32 constant HAS_NEXT_FLAG = bytes32(uint256(2)); - -/// @dev Type representing the set, which is just a storage slot placeholder like the solidity mapping type. -struct AssociatedLinkedListSet { - bytes32 placeholder; -} - -/// @title Associated Linked List Set Library -/// @notice Provides a set data structure that is enumerable and held in address-associated storage (per the -/// ERC-4337 spec) -library AssociatedLinkedListSetLib { - // Mapping Entry Byte Layout - // | value | 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA____ | - // | meta | 0x____________________________________________________________BBBB | - - // Bit-layout of the meta bytes (2 bytes) - // | user flags | 11111111 11111100 | - // | has next | 00000000 00000010 | - // | sentinel | 00000000 00000001 | - - // Mapping keys exclude the upper 15 bits of the meta bytes, which allows keys to be either a value or the - // sentinel. - - bytes4 internal constant _ASSOCIATED_STORAGE_PREFIX = 0x9cc6c923; // bytes4(keccak256("AssociatedLinkedListSet")) - - // A custom type representing the index of a storage slot - type StoragePointer is bytes32; - - // A custom type representing a pointer to a location in memory beyond the current free memory pointer. - // Holds a fixed-size buffer similar to "bytes memory", but without a length field. - // Care must be taken when using these, as they may be overwritten if ANY memory is allocated after allocating - // a TempBytesMemory. - type TempBytesMemory is bytes32; - - // INTERNAL METHODS - - /// @notice Adds a value to a set. - /// @param set The set to add the value to. - /// @param associated The address the set is associated with. - /// @param value The value to add. - /// @return True if the value was added, false if the value cannot be added (already exists or is zero). - function tryAdd(AssociatedLinkedListSet storage set, address associated, SetValue value) - internal - returns (bool) - { - bytes32 unwrappedKey = bytes32(SetValue.unwrap(value)); - if (unwrappedKey == bytes32(0)) { - // Cannot add the zero value - return false; - } - - TempBytesMemory keyBuffer = _allocateTempKeyBuffer(set, associated); - - StoragePointer valueSlot = _mapLookup(keyBuffer, unwrappedKey); - if (_load(valueSlot) != bytes32(0)) { - // Entry already exists - return false; - } - - // Load the head of the set - StoragePointer sentinelSlot = _mapLookup(keyBuffer, SENTINEL_VALUE); - bytes32 prev = _load(sentinelSlot); - if (prev == bytes32(0) || isSentinel(prev)) { - // set is empty, need to do: - // map[SENTINEL_VALUE] = unwrappedKey; - // map[unwrappedKey] = SENTINEL_VALUE; - _store(sentinelSlot, unwrappedKey); - _store(valueSlot, SENTINEL_VALUE); - } else { - // set is not empty, need to do: - // map[SENTINEL_VALUE] = unwrappedKey | HAS_NEXT_FLAG; - // map[unwrappedKey] = prev; - _store(sentinelSlot, unwrappedKey | HAS_NEXT_FLAG); - _store(valueSlot, prev); - } - - return true; - } - - /// @notice Removes a value from a set. - /// @dev This is an O(n) operation, where n is the number of elements in the set. - /// @param set The set to remove the value from - /// @param associated The address the set is associated with - /// @param value The value to remove - /// @return True if the value was removed, false if the value does not exist - function tryRemove(AssociatedLinkedListSet storage set, address associated, SetValue value) - internal - returns (bool) - { - bytes32 unwrappedKey = bytes32(SetValue.unwrap(value)); - TempBytesMemory keyBuffer = _allocateTempKeyBuffer(set, associated); - - StoragePointer valueSlot = _mapLookup(keyBuffer, unwrappedKey); - bytes32 nextValue = _load(valueSlot); - if (unwrappedKey == bytes32(0) || nextValue == bytes32(0)) { - // Entry does not exist - return false; - } - - bytes32 prevKey = SENTINEL_VALUE; - bytes32 currentVal; - do { - // Load the current entry - StoragePointer prevSlot = _mapLookup(keyBuffer, prevKey); - currentVal = _load(prevSlot); - bytes32 currentKey = clearFlags(currentVal); - if (currentKey == unwrappedKey) { - // Found the entry - // Set the previous value's next value to the next value, - // and the flags to the current value's flags. - // and the next value's `hasNext` flag to determine whether or not the next value is (or points to) - // the sentinel value. - - // Need to do: - // map[prevKey] = clearFlags(nextValue) | getUserFlags(currentVal) | (nextValue & HAS_NEXT_FLAG); - // map[currentKey] = bytes32(0); - - _store(prevSlot, clearFlags(nextValue) | getUserFlags(currentVal) | (nextValue & HAS_NEXT_FLAG)); - _store(valueSlot, bytes32(0)); - - return true; - } - prevKey = currentKey; - } while (!isSentinel(currentVal) && currentVal != bytes32(0)); - return false; - } - - /// @notice Removes a value from a set, given the previous value in the set. - /// @dev This is an O(1) operation but requires additional knowledge. - /// @param set The set to remove the value from - /// @param associated The address the set is associated with - /// @param value The value to remove - /// @param prev The previous value in the set - /// @return True if the value was removed, false if the value does not exist - function tryRemoveKnown(AssociatedLinkedListSet storage set, address associated, SetValue value, bytes32 prev) - internal - returns (bool) - { - bytes32 unwrappedKey = bytes32(SetValue.unwrap(value)); - TempBytesMemory keyBuffer = _allocateTempKeyBuffer(set, associated); - - prev = clearFlags(prev); - - if (prev == bytes32(0) || unwrappedKey == bytes32(0)) { - return false; - } - - // assert that the previous key's next value is the value to be removed - StoragePointer prevSlot = _mapLookup(keyBuffer, prev); - bytes32 currentValue = _load(prevSlot); - if (clearFlags(currentValue) != unwrappedKey) { - return false; - } - - StoragePointer valueSlot = _mapLookup(keyBuffer, unwrappedKey); - bytes32 next = _load(valueSlot); - if (next == bytes32(0)) { - // The set didn't actually contain the value - return false; - } - - // Need to do: - // map[prev] = clearFlags(next) | getUserFlags(currentValue) | (next & HAS_NEXT_FLAG); - // map[unwrappedKey] = bytes32(0); - _store(prevSlot, clearFlags(next) | getUserFlags(currentValue) | (next & HAS_NEXT_FLAG)); - _store(valueSlot, bytes32(0)); - - return true; - } - - /// @notice Removes all values from a set. - /// @dev This is an O(n) operation, where n is the number of elements in the set. - /// @param set The set to remove the values from - /// @param associated The address the set is associated with - function clear(AssociatedLinkedListSet storage set, address associated) internal { - TempBytesMemory keyBuffer = _allocateTempKeyBuffer(set, associated); - - bytes32 cursor = SENTINEL_VALUE; - - do { - bytes32 cleared = clearFlags(cursor); - StoragePointer cursorSlot = _mapLookup(keyBuffer, cleared); - bytes32 next = _load(cursorSlot); - _store(cursorSlot, bytes32(0)); - cursor = next; - } while (!isSentinel(cursor) && cursor != bytes32(0)); - - StoragePointer sentinelSlot = _mapLookup(keyBuffer, SENTINEL_VALUE); - _store(sentinelSlot, bytes32(0)); - } - - /// @notice Set the flags on a value in the set. - /// @dev The user flags can only be set on the upper 14 bits, because the lower two are reserved for the - /// sentinel and has next bit. - /// @param set The set containing the value. - /// @param associated The address the set is associated with. - /// @param value The value to set the flags on. - /// @param flags The flags to set. - /// @return True if the set contains the value and the operation succeeds, false otherwise. - function trySetFlags(AssociatedLinkedListSet storage set, address associated, SetValue value, uint16 flags) - internal - returns (bool) - { - bytes32 unwrappedKey = SetValue.unwrap(value); - TempBytesMemory keyBuffer = _allocateTempKeyBuffer(set, associated); - - // Ignore the lower 2 bits. - flags &= 0xFFFC; - - // If the set doesn't actually contain the value, return false; - StoragePointer valueSlot = _mapLookup(keyBuffer, unwrappedKey); - bytes32 next = _load(valueSlot); - if (next == bytes32(0)) { - return false; - } - - // Set the flags - _store(valueSlot, clearUserFlags(next) | bytes32(uint256(flags))); - - return true; - } - - /// @notice Set the given flags on a value in the set, preserving the values of other flags. - /// @dev The user flags can only be set on the upper 14 bits, because the lower two are reserved for the - /// sentinel and has next bit. - /// Short-circuits if the flags are already enabled, returning true. - /// @param set The set containing the value. - /// @param associated The address the set is associated with. - /// @param value The value to enable the flags on. - /// @param flags The flags to enable. - /// @return True if the operation succeeds or short-circuits due to the flags already being enabled. False - /// otherwise. - function tryEnableFlags(AssociatedLinkedListSet storage set, address associated, SetValue value, uint16 flags) - internal - returns (bool) - { - flags &= 0xFFFC; // Allow short-circuit if lower bits are accidentally set - uint16 currFlags = getFlags(set, associated, value); - if (currFlags & flags == flags) return true; // flags are already enabled - return trySetFlags(set, associated, value, currFlags | flags); - } - - /// @notice Clear the given flags on a value in the set, preserving the values of other flags. - /// @notice If the value is not in the set, this function will still return true. - /// @dev The user flags can only be set on the upper 14 bits, because the lower two are reserved for the - /// sentinel and has next bit. - /// Short-circuits if the flags are already disabled, or if set does not contain the value. Short-circuits - /// return true. - /// @param set The set containing the value. - /// @param associated The address the set is associated with. - /// @param value The value to disable the flags on. - /// @param flags The flags to disable. - /// @return True if the operation succeeds, or short-circuits due to the flags already being disabled or if the - /// set does not contain the value. False otherwise. - function tryDisableFlags(AssociatedLinkedListSet storage set, address associated, SetValue value, uint16 flags) - internal - returns (bool) - { - flags &= 0xFFFC; // Allow short-circuit if lower bits are accidentally set - uint16 currFlags = getFlags(set, associated, value); - if (currFlags & flags == 0) return true; // flags are already disabled - return trySetFlags(set, associated, value, currFlags & ~flags); - } - - /// @notice Checks if a set contains a value - /// @dev This method does not clear the upper bits of `value`, that is expected to be done as part of casting - /// to the correct type. If this function is provided the sentinel value by using the upper bits, this function - /// may returns `true`. - /// @param set The set to check - /// @param associated The address the set is associated with - /// @param value The value to check for - /// @return True if the set contains the value, false otherwise - function contains(AssociatedLinkedListSet storage set, address associated, SetValue value) - internal - view - returns (bool) - { - bytes32 unwrappedKey = bytes32(SetValue.unwrap(value)); - TempBytesMemory keyBuffer = _allocateTempKeyBuffer(set, associated); - - StoragePointer slot = _mapLookup(keyBuffer, unwrappedKey); - return _load(slot) != bytes32(0); - } - - /// @notice Checks if a set is empty - /// @param set The set to check - /// @param associated The address the set is associated with - /// @return True if the set is empty, false otherwise - function isEmpty(AssociatedLinkedListSet storage set, address associated) internal view returns (bool) { - TempBytesMemory keyBuffer = _allocateTempKeyBuffer(set, associated); - - StoragePointer sentinelSlot = _mapLookup(keyBuffer, SENTINEL_VALUE); - bytes32 val = _load(sentinelSlot); - return val == bytes32(0) || isSentinel(val); // either the sentinel is unset, or points to itself - } - - /// @notice Get the flags on a value in the set. - /// @dev The reserved lower 2 bits will not be returned, as those are reserved for the sentinel and has next - /// bit. - /// @param set The set containing the value. - /// @param associated The address the set is associated with. - /// @param value The value to get the flags from. - /// @return The flags set on the value. - function getFlags(AssociatedLinkedListSet storage set, address associated, SetValue value) - internal - view - returns (uint16) - { - bytes32 unwrappedKey = SetValue.unwrap(value); - TempBytesMemory keyBuffer = _allocateTempKeyBuffer(set, associated); - return uint16(uint256(_load(_mapLookup(keyBuffer, unwrappedKey))) & 0xFFFC); - } - - /// @notice Check if the flags on a value are enabled. - /// @dev The reserved lower 2 bits will be ignored, as those are reserved for the sentinel and has next bit. - /// @param set The set containing the value. - /// @param associated The address the set is associated with. - /// @param value The value to check the flags on. - /// @param flags The flags to check. - /// @return True if all of the flags are enabled, false otherwise. - function flagsEnabled(AssociatedLinkedListSet storage set, address associated, SetValue value, uint16 flags) - internal - view - returns (bool) - { - flags &= 0xFFFC; - return getFlags(set, associated, value) & flags == flags; - } - - /// @notice Check if the flags on a value are disabled. - /// @dev The reserved lower 2 bits will be ignored, as those are reserved for the sentinel and has next bit. - /// @param set The set containing the value. - /// @param associated The address the set is associated with. - /// @param value The value to check the flags on. - /// @param flags The flags to check. - /// @return True if all of the flags are disabled, false otherwise. - function flagsDisabled(AssociatedLinkedListSet storage set, address associated, SetValue value, uint16 flags) - internal - view - returns (bool) - { - flags &= 0xFFFC; - return ~(getFlags(set, associated, value)) & flags == flags; - } - - /// @notice Gets all elements in a set. - /// @dev This is an O(n) operation, where n is the number of elements in the set. - /// @param set The set to get the elements of. - /// @return res An array of all elements in the set. - function getAll(AssociatedLinkedListSet storage set, address associated) - internal - view - returns (SetValue[] memory res) - { - TempBytesMemory keyBuffer = _allocateTempKeyBuffer(set, associated); - - StoragePointer sentinelSlot = _mapLookup(keyBuffer, SENTINEL_VALUE); - bytes32 cursor = _load(sentinelSlot); - - uint256 count; - while (!isSentinel(cursor) && cursor != bytes32(0)) { - unchecked { - ++count; - } - bytes32 cleared = clearFlags(cursor); - - if (hasNext(cursor)) { - StoragePointer cursorSlot = _mapLookup(keyBuffer, cleared); - cursor = _load(cursorSlot); - } else { - cursor = bytes32(0); - } - } - - res = new SetValue[](count); - - if (count == 0) { - return res; - } - - // Re-allocate the key buffer because we just overwrote it! - keyBuffer = _allocateTempKeyBuffer(set, associated); - - cursor = SENTINEL_VALUE; - for (uint256 i = 0; i < count;) { - StoragePointer cursorSlot = _mapLookup(keyBuffer, cursor); - bytes32 cursorValue = _load(cursorSlot); - bytes32 cleared = clearFlags(cursorValue); - res[i] = SetValue.wrap(bytes30(cleared)); - cursor = cleared; - - unchecked { - ++i; - } - } - } - - function isSentinel(bytes32 value) internal pure returns (bool ret) { - assembly ("memory-safe") { - ret := and(value, 1) - } - } - - function hasNext(bytes32 value) internal pure returns (bool) { - return value & HAS_NEXT_FLAG != 0; - } - - function clearFlags(bytes32 val) internal pure returns (bytes32) { - return val & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0001; - } - - /// @dev Preserves the lower two bits - function clearUserFlags(bytes32 val) internal pure returns (bytes32) { - return val & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0003; - } - - function getUserFlags(bytes32 val) internal pure returns (bytes32) { - return val & bytes32(uint256(0xFFFC)); - } - - // PRIVATE METHODS - - /// @notice Given an allocated key buffer, returns the storage slot for a given key - function _mapLookup(TempBytesMemory keyBuffer, bytes32 value) private pure returns (StoragePointer slot) { - assembly ("memory-safe") { - // Store the value in the last word. - let keyWord2 := value - mstore(add(keyBuffer, 0x60), keyWord2) - slot := keccak256(keyBuffer, 0x80) - } - } - - /// @notice Allocates a key buffer for a given ID and associated address into scratch space memory. - /// @dev The returned buffer must not be used if any additional memory is allocated after calling this - /// function. - /// @param set The set to allocate the key buffer for. - /// @param associated The address the set is associated with. - /// @return key A key buffer that can be used to lookup values in the set - function _allocateTempKeyBuffer(AssociatedLinkedListSet storage set, address associated) - private - pure - returns (TempBytesMemory key) - { - // Key derivation for an entry - // associated addr (left-padded) || prefix || uint224(0) batchIndex || set storage slot || entry - // Word 1: - // | zeros | 0x000000000000000000000000________________________________________ | - // | address | 0x________________________AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA | - // Word 2: - // | prefix | 0xPPPPPPPP________________________________________________________ | - // | batch index (zero) | 0x________00000000000000000000000000000000000000000000000000000000 | - // Word 3: - // | set storage slot | 0xSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS | - // Word 4: - // | entry value | 0xVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV____ | - // | entry meta | 0x____________________________________________________________MMMM | - - // The batch index is for consistency with PluginStorageLib, and the prefix in front of it is - // to prevent any potential crafted collisions where the batch index may be equal to storage slot - // of the ALLS. The prefix is set to the upper bits of the batch index to make it infeasible to - // reach from just incrementing the value. - - // This segment is memory-safe because it only uses the scratch space memory after the value of the free - // memory pointer. - // See https://docs.soliditylang.org/en/v0.8.21/assembly.html#memory-safety - assembly ("memory-safe") { - // Clean upper bits of arguments - associated := and(associated, 0xffffffffffffffffffffffffffffffffffffffff) - - // Use memory past-the-free-memory-pointer without updating it, as this is just scratch space - key := mload(0x40) - // Store the associated address in the first word, left-padded with zeroes - mstore(key, associated) - // Store the prefix and a batch index of 0 - mstore(add(key, 0x20), _ASSOCIATED_STORAGE_PREFIX) - // Store the list's storage slot in the third word - mstore(add(key, 0x40), set.slot) - // Leaves the last word open for the value entry - } - - return key; - } - - /// @dev Loads a value from storage - function _load(StoragePointer ptr) private view returns (bytes32 val) { - assembly ("memory-safe") { - val := sload(ptr) - } - } - - /// @dev Writes a value into storage - function _store(StoragePointer ptr, bytes32 val) private { - assembly ("memory-safe") { - sstore(ptr, val) - } - } -} +// type SetValue is bytes30; + +// /// @dev The sentinel value is used to indicate the head and tail of the list. +// bytes32 constant SENTINEL_VALUE = bytes32(uint256(1)); + +// /// @dev Removing the last element will result in this flag not being set correctly, but all operations will +// /// function normally, albeit with one extra sload for getAll. +// bytes32 constant HAS_NEXT_FLAG = bytes32(uint256(2)); + +// /// @dev Type representing the set, which is just a storage slot placeholder like the solidity mapping type. +// struct AssociatedLinkedListSet { +// bytes32 placeholder; +// } + +// /// @title Associated Linked List Set Library +// /// @notice Provides a set data structure that is enumerable and held in address-associated storage (per the +// /// ERC-4337 spec) +// library AssociatedLinkedListSetLib { +// // Mapping Entry Byte Layout +// // | value | 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA____ | +// // | meta | 0x____________________________________________________________BBBB | + +// // Bit-layout of the meta bytes (2 bytes) +// // | user flags | 11111111 11111100 | +// // | has next | 00000000 00000010 | +// // | sentinel | 00000000 00000001 | + +// // Mapping keys exclude the upper 15 bits of the meta bytes, which allows keys to be either a value or the +// // sentinel. + +// bytes4 internal constant _ASSOCIATED_STORAGE_PREFIX = 0x9cc6c923; // +// bytes4(keccak256("AssociatedLinkedListSet")) + +// // A custom type representing the index of a storage slot +// type StoragePointer is bytes32; + +// // A custom type representing a pointer to a location in memory beyond the current free memory pointer. +// // Holds a fixed-size buffer similar to "bytes memory", but without a length field. +// // Care must be taken when using these, as they may be overwritten if ANY memory is allocated after +// allocating +// // a TempBytesMemory. +// type TempBytesMemory is bytes32; + +// // INTERNAL METHODS + +// /// @notice Adds a value to a set. +// /// @param set The set to add the value to. +// /// @param associated The address the set is associated with. +// /// @param value The value to add. +// /// @return True if the value was added, false if the value cannot be added (already exists or is zero). +// function tryAdd(AssociatedLinkedListSet storage set, address associated, SetValue value) +// internal +// returns (bool) +// { +// bytes32 unwrappedKey = bytes32(SetValue.unwrap(value)); +// if (unwrappedKey == bytes32(0)) { +// // Cannot add the zero value +// return false; +// } + +// TempBytesMemory keyBuffer = _allocateTempKeyBuffer(set, associated); + +// StoragePointer valueSlot = _mapLookup(keyBuffer, unwrappedKey); +// if (_load(valueSlot) != bytes32(0)) { +// // Entry already exists +// return false; +// } + +// // Load the head of the set +// StoragePointer sentinelSlot = _mapLookup(keyBuffer, SENTINEL_VALUE); +// bytes32 prev = _load(sentinelSlot); +// if (prev == bytes32(0) || isSentinel(prev)) { +// // set is empty, need to do: +// // map[SENTINEL_VALUE] = unwrappedKey; +// // map[unwrappedKey] = SENTINEL_VALUE; +// _store(sentinelSlot, unwrappedKey); +// _store(valueSlot, SENTINEL_VALUE); +// } else { +// // set is not empty, need to do: +// // map[SENTINEL_VALUE] = unwrappedKey | HAS_NEXT_FLAG; +// // map[unwrappedKey] = prev; +// _store(sentinelSlot, unwrappedKey | HAS_NEXT_FLAG); +// _store(valueSlot, prev); +// } + +// return true; +// } + +// /// @notice Removes a value from a set. +// /// @dev This is an O(n) operation, where n is the number of elements in the set. +// /// @param set The set to remove the value from +// /// @param associated The address the set is associated with +// /// @param value The value to remove +// /// @return True if the value was removed, false if the value does not exist +// function tryRemove(AssociatedLinkedListSet storage set, address associated, SetValue value) +// internal +// returns (bool) +// { +// bytes32 unwrappedKey = bytes32(SetValue.unwrap(value)); +// TempBytesMemory keyBuffer = _allocateTempKeyBuffer(set, associated); + +// StoragePointer valueSlot = _mapLookup(keyBuffer, unwrappedKey); +// bytes32 nextValue = _load(valueSlot); +// if (unwrappedKey == bytes32(0) || nextValue == bytes32(0)) { +// // Entry does not exist +// return false; +// } + +// bytes32 prevKey = SENTINEL_VALUE; +// bytes32 currentVal; +// do { +// // Load the current entry +// StoragePointer prevSlot = _mapLookup(keyBuffer, prevKey); +// currentVal = _load(prevSlot); +// bytes32 currentKey = clearFlags(currentVal); +// if (currentKey == unwrappedKey) { +// // Found the entry +// // Set the previous value's next value to the next value, +// // and the flags to the current value's flags. +// // and the next value's `hasNext` flag to determine whether or not the next value is (or points +// to) +// // the sentinel value. + +// // Need to do: +// // map[prevKey] = clearFlags(nextValue) | getUserFlags(currentVal) | (nextValue & +// HAS_NEXT_FLAG); +// // map[currentKey] = bytes32(0); + +// _store(prevSlot, clearFlags(nextValue) | getUserFlags(currentVal) | (nextValue & +// HAS_NEXT_FLAG)); +// _store(valueSlot, bytes32(0)); + +// return true; +// } +// prevKey = currentKey; +// } while (!isSentinel(currentVal) && currentVal != bytes32(0)); +// return false; +// } + +// /// @notice Removes a value from a set, given the previous value in the set. +// /// @dev This is an O(1) operation but requires additional knowledge. +// /// @param set The set to remove the value from +// /// @param associated The address the set is associated with +// /// @param value The value to remove +// /// @param prev The previous value in the set +// /// @return True if the value was removed, false if the value does not exist +// function tryRemoveKnown(AssociatedLinkedListSet storage set, address associated, SetValue value, bytes32 +// prev) +// internal +// returns (bool) +// { +// bytes32 unwrappedKey = bytes32(SetValue.unwrap(value)); +// TempBytesMemory keyBuffer = _allocateTempKeyBuffer(set, associated); + +// prev = clearFlags(prev); + +// if (prev == bytes32(0) || unwrappedKey == bytes32(0)) { +// return false; +// } + +// // assert that the previous key's next value is the value to be removed +// StoragePointer prevSlot = _mapLookup(keyBuffer, prev); +// bytes32 currentValue = _load(prevSlot); +// if (clearFlags(currentValue) != unwrappedKey) { +// return false; +// } + +// StoragePointer valueSlot = _mapLookup(keyBuffer, unwrappedKey); +// bytes32 next = _load(valueSlot); +// if (next == bytes32(0)) { +// // The set didn't actually contain the value +// return false; +// } + +// // Need to do: +// // map[prev] = clearFlags(next) | getUserFlags(currentValue) | (next & HAS_NEXT_FLAG); +// // map[unwrappedKey] = bytes32(0); +// _store(prevSlot, clearFlags(next) | getUserFlags(currentValue) | (next & HAS_NEXT_FLAG)); +// _store(valueSlot, bytes32(0)); + +// return true; +// } + +// /// @notice Removes all values from a set. +// /// @dev This is an O(n) operation, where n is the number of elements in the set. +// /// @param set The set to remove the values from +// /// @param associated The address the set is associated with +// function clear(AssociatedLinkedListSet storage set, address associated) internal { +// TempBytesMemory keyBuffer = _allocateTempKeyBuffer(set, associated); + +// bytes32 cursor = SENTINEL_VALUE; + +// do { +// bytes32 cleared = clearFlags(cursor); +// StoragePointer cursorSlot = _mapLookup(keyBuffer, cleared); +// bytes32 next = _load(cursorSlot); +// _store(cursorSlot, bytes32(0)); +// cursor = next; +// } while (!isSentinel(cursor) && cursor != bytes32(0)); + +// StoragePointer sentinelSlot = _mapLookup(keyBuffer, SENTINEL_VALUE); +// _store(sentinelSlot, bytes32(0)); +// } + +// /// @notice Set the flags on a value in the set. +// /// @dev The user flags can only be set on the upper 14 bits, because the lower two are reserved for the +// /// sentinel and has next bit. +// /// @param set The set containing the value. +// /// @param associated The address the set is associated with. +// /// @param value The value to set the flags on. +// /// @param flags The flags to set. +// /// @return True if the set contains the value and the operation succeeds, false otherwise. +// function trySetFlags(AssociatedLinkedListSet storage set, address associated, SetValue value, uint16 flags) +// internal +// returns (bool) +// { +// bytes32 unwrappedKey = SetValue.unwrap(value); +// TempBytesMemory keyBuffer = _allocateTempKeyBuffer(set, associated); + +// // Ignore the lower 2 bits. +// flags &= 0xFFFC; + +// // If the set doesn't actually contain the value, return false; +// StoragePointer valueSlot = _mapLookup(keyBuffer, unwrappedKey); +// bytes32 next = _load(valueSlot); +// if (next == bytes32(0)) { +// return false; +// } + +// // Set the flags +// _store(valueSlot, clearUserFlags(next) | bytes32(uint256(flags))); + +// return true; +// } + +// /// @notice Set the given flags on a value in the set, preserving the values of other flags. +// /// @dev The user flags can only be set on the upper 14 bits, because the lower two are reserved for the +// /// sentinel and has next bit. +// /// Short-circuits if the flags are already enabled, returning true. +// /// @param set The set containing the value. +// /// @param associated The address the set is associated with. +// /// @param value The value to enable the flags on. +// /// @param flags The flags to enable. +// /// @return True if the operation succeeds or short-circuits due to the flags already being enabled. False +// /// otherwise. +// function tryEnableFlags(AssociatedLinkedListSet storage set, address associated, SetValue value, uint16 +// flags) +// internal +// returns (bool) +// { +// flags &= 0xFFFC; // Allow short-circuit if lower bits are accidentally set +// uint16 currFlags = getFlags(set, associated, value); +// if (currFlags & flags == flags) return true; // flags are already enabled +// return trySetFlags(set, associated, value, currFlags | flags); +// } + +// /// @notice Clear the given flags on a value in the set, preserving the values of other flags. +// /// @notice If the value is not in the set, this function will still return true. +// /// @dev The user flags can only be set on the upper 14 bits, because the lower two are reserved for the +// /// sentinel and has next bit. +// /// Short-circuits if the flags are already disabled, or if set does not contain the value. Short-circuits +// /// return true. +// /// @param set The set containing the value. +// /// @param associated The address the set is associated with. +// /// @param value The value to disable the flags on. +// /// @param flags The flags to disable. +// /// @return True if the operation succeeds, or short-circuits due to the flags already being disabled or if +// the +// /// set does not contain the value. False otherwise. +// function tryDisableFlags(AssociatedLinkedListSet storage set, address associated, SetValue value, uint16 +// flags) +// internal +// returns (bool) +// { +// flags &= 0xFFFC; // Allow short-circuit if lower bits are accidentally set +// uint16 currFlags = getFlags(set, associated, value); +// if (currFlags & flags == 0) return true; // flags are already disabled +// return trySetFlags(set, associated, value, currFlags & ~flags); +// } + +// /// @notice Checks if a set contains a value +// /// @dev This method does not clear the upper bits of `value`, that is expected to be done as part of +// casting +// /// to the correct type. If this function is provided the sentinel value by using the upper bits, this +// function +// /// may returns `true`. +// /// @param set The set to check +// /// @param associated The address the set is associated with +// /// @param value The value to check for +// /// @return True if the set contains the value, false otherwise +// function contains(AssociatedLinkedListSet storage set, address associated, SetValue value) +// internal +// view +// returns (bool) +// { +// bytes32 unwrappedKey = bytes32(SetValue.unwrap(value)); +// TempBytesMemory keyBuffer = _allocateTempKeyBuffer(set, associated); + +// StoragePointer slot = _mapLookup(keyBuffer, unwrappedKey); +// return _load(slot) != bytes32(0); +// } + +// /// @notice Checks if a set is empty +// /// @param set The set to check +// /// @param associated The address the set is associated with +// /// @return True if the set is empty, false otherwise +// function isEmpty(AssociatedLinkedListSet storage set, address associated) internal view returns (bool) { +// TempBytesMemory keyBuffer = _allocateTempKeyBuffer(set, associated); + +// StoragePointer sentinelSlot = _mapLookup(keyBuffer, SENTINEL_VALUE); +// bytes32 val = _load(sentinelSlot); +// return val == bytes32(0) || isSentinel(val); // either the sentinel is unset, or points to itself +// } + +// /// @notice Get the flags on a value in the set. +// /// @dev The reserved lower 2 bits will not be returned, as those are reserved for the sentinel and has next +// /// bit. +// /// @param set The set containing the value. +// /// @param associated The address the set is associated with. +// /// @param value The value to get the flags from. +// /// @return The flags set on the value. +// function getFlags(AssociatedLinkedListSet storage set, address associated, SetValue value) +// internal +// view +// returns (uint16) +// { +// bytes32 unwrappedKey = SetValue.unwrap(value); +// TempBytesMemory keyBuffer = _allocateTempKeyBuffer(set, associated); +// return uint16(uint256(_load(_mapLookup(keyBuffer, unwrappedKey))) & 0xFFFC); +// } + +// /// @notice Check if the flags on a value are enabled. +// /// @dev The reserved lower 2 bits will be ignored, as those are reserved for the sentinel and has next bit. +// /// @param set The set containing the value. +// /// @param associated The address the set is associated with. +// /// @param value The value to check the flags on. +// /// @param flags The flags to check. +// /// @return True if all of the flags are enabled, false otherwise. +// function flagsEnabled(AssociatedLinkedListSet storage set, address associated, SetValue value, uint16 flags) +// internal +// view +// returns (bool) +// { +// flags &= 0xFFFC; +// return getFlags(set, associated, value) & flags == flags; +// } + +// /// @notice Check if the flags on a value are disabled. +// /// @dev The reserved lower 2 bits will be ignored, as those are reserved for the sentinel and has next bit. +// /// @param set The set containing the value. +// /// @param associated The address the set is associated with. +// /// @param value The value to check the flags on. +// /// @param flags The flags to check. +// /// @return True if all of the flags are disabled, false otherwise. +// function flagsDisabled(AssociatedLinkedListSet storage set, address associated, SetValue value, uint16 +// flags) +// internal +// view +// returns (bool) +// { +// flags &= 0xFFFC; +// return ~(getFlags(set, associated, value)) & flags == flags; +// } + +// /// @notice Gets all elements in a set. +// /// @dev This is an O(n) operation, where n is the number of elements in the set. +// /// @param set The set to get the elements of. +// /// @return res An array of all elements in the set. +// function getAll(AssociatedLinkedListSet storage set, address associated) +// internal +// view +// returns (SetValue[] memory res) +// { +// TempBytesMemory keyBuffer = _allocateTempKeyBuffer(set, associated); + +// StoragePointer sentinelSlot = _mapLookup(keyBuffer, SENTINEL_VALUE); +// bytes32 cursor = _load(sentinelSlot); + +// uint256 count; +// while (!isSentinel(cursor) && cursor != bytes32(0)) { +// unchecked { +// ++count; +// } +// bytes32 cleared = clearFlags(cursor); + +// if (hasNext(cursor)) { +// StoragePointer cursorSlot = _mapLookup(keyBuffer, cleared); +// cursor = _load(cursorSlot); +// } else { +// cursor = bytes32(0); +// } +// } + +// res = new SetValue[](count); + +// if (count == 0) { +// return res; +// } + +// // Re-allocate the key buffer because we just overwrote it! +// keyBuffer = _allocateTempKeyBuffer(set, associated); + +// cursor = SENTINEL_VALUE; +// for (uint256 i = 0; i < count;) { +// StoragePointer cursorSlot = _mapLookup(keyBuffer, cursor); +// bytes32 cursorValue = _load(cursorSlot); +// bytes32 cleared = clearFlags(cursorValue); +// res[i] = SetValue.wrap(bytes30(cleared)); +// cursor = cleared; + +// unchecked { +// ++i; +// } +// } +// } + +// function isSentinel(bytes32 value) internal pure returns (bool ret) { +// assembly ("memory-safe") { +// ret := and(value, 1) +// } +// } + +// function hasNext(bytes32 value) internal pure returns (bool) { +// return value & HAS_NEXT_FLAG != 0; +// } + +// function clearFlags(bytes32 val) internal pure returns (bytes32) { +// return val & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0001; +// } + +// /// @dev Preserves the lower two bits +// function clearUserFlags(bytes32 val) internal pure returns (bytes32) { +// return val & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0003; +// } + +// function getUserFlags(bytes32 val) internal pure returns (bytes32) { +// return val & bytes32(uint256(0xFFFC)); +// } + +// // PRIVATE METHODS + +// /// @notice Given an allocated key buffer, returns the storage slot for a given key +// function _mapLookup(TempBytesMemory keyBuffer, bytes32 value) private pure returns (StoragePointer slot) { +// assembly ("memory-safe") { +// // Store the value in the last word. +// let keyWord2 := value +// mstore(add(keyBuffer, 0x60), keyWord2) +// slot := keccak256(keyBuffer, 0x80) +// } +// } + +// /// @notice Allocates a key buffer for a given ID and associated address into scratch space memory. +// /// @dev The returned buffer must not be used if any additional memory is allocated after calling this +// /// function. +// /// @param set The set to allocate the key buffer for. +// /// @param associated The address the set is associated with. +// /// @return key A key buffer that can be used to lookup values in the set +// function _allocateTempKeyBuffer(AssociatedLinkedListSet storage set, address associated) +// private +// pure +// returns (TempBytesMemory key) +// { +// // Key derivation for an entry +// // associated addr (left-padded) || prefix || uint224(0) batchIndex || set storage slot || entry +// // Word 1: +// // | zeros | 0x000000000000000000000000________________________________________ | +// // | address | 0x________________________AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA | +// // Word 2: +// // | prefix | 0xPPPPPPPP________________________________________________________ | +// // | batch index (zero) | 0x________00000000000000000000000000000000000000000000000000000000 | +// // Word 3: +// // | set storage slot | 0xSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS | +// // Word 4: +// // | entry value | 0xVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV____ | +// // | entry meta | 0x____________________________________________________________MMMM | + +// // The batch index is for consistency with PluginStorageLib, and the prefix in front of it is +// // to prevent any potential crafted collisions where the batch index may be equal to storage slot +// // of the ALLS. The prefix is set to the upper bits of the batch index to make it infeasible to +// // reach from just incrementing the value. + +// // This segment is memory-safe because it only uses the scratch space memory after the value of the free +// // memory pointer. +// // See https://docs.soliditylang.org/en/v0.8.21/assembly.html#memory-safety +// assembly ("memory-safe") { +// // Clean upper bits of arguments +// associated := and(associated, 0xffffffffffffffffffffffffffffffffffffffff) + +// // Use memory past-the-free-memory-pointer without updating it, as this is just scratch space +// key := mload(0x40) +// // Store the associated address in the first word, left-padded with zeroes +// mstore(key, associated) +// // Store the prefix and a batch index of 0 +// mstore(add(key, 0x20), _ASSOCIATED_STORAGE_PREFIX) +// // Store the list's storage slot in the third word +// mstore(add(key, 0x40), set.slot) +// // Leaves the last word open for the value entry +// } + +// return key; +// } + +// /// @dev Loads a value from storage +// function _load(StoragePointer ptr) private view returns (bytes32 val) { +// assembly ("memory-safe") { +// val := sload(ptr) +// } +// } + +// /// @dev Writes a value into storage +// function _store(StoragePointer ptr, bytes32 val) private { +// assembly ("memory-safe") { +// sstore(ptr, val) +// } +// } +// } diff --git a/src/libraries/PluginStorageLib.sol b/src/libraries/PluginStorageLib.sol index 503f504a..35aaa7bb 100644 --- a/src/libraries/PluginStorageLib.sol +++ b/src/libraries/PluginStorageLib.sol @@ -1,59 +1,62 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -type StoragePointer is bytes32; +// type StoragePointer is bytes32; -/// @title Plugin Storage Library -/// @notice Library for allocating and accessing ERC-4337 address-associated storage within plugins. -library PluginStorageLib { - /// @notice Allocates a memory buffer for an associated storage key, and sets the associated address and batch - /// index. - /// @param addr The address to associate with the storage key. - /// @param batchIndex The batch index to associate with the storage key. - /// @param keySize The size of the key in words, where each word is 32 bytes. Not inclusive of the address and - /// batch index. - /// @return key The allocated memory buffer. - function allocateAssociatedStorageKey(address addr, uint256 batchIndex, uint8 keySize) - internal - pure - returns (bytes memory key) - { - assembly ("memory-safe") { - // Clear any dirty upper bits of keySize to prevent overflow - keySize := and(keySize, 0xff) +// /// @title Plugin Storage Library +// /// @notice Library for allocating and accessing ERC-4337 address-associated storage within plugins. +// library PluginStorageLib { +// /// @notice Allocates a memory buffer for an associated storage key, and sets the associated address and +// batch +// /// index. +// /// @param addr The address to associate with the storage key. +// /// @param batchIndex The batch index to associate with the storage key. +// /// @param keySize The size of the key in words, where each word is 32 bytes. Not inclusive of the address +// and +// /// batch index. +// /// @return key The allocated memory buffer. +// function allocateAssociatedStorageKey(address addr, uint256 batchIndex, uint8 keySize) +// internal +// pure +// returns (bytes memory key) +// { +// assembly ("memory-safe") { +// // Clear any dirty upper bits of keySize to prevent overflow +// keySize := and(keySize, 0xff) - // compute the total size of the buffer, include the address and batch index - let totalSize := add(64, mul(32, keySize)) +// // compute the total size of the buffer, include the address and batch index +// let totalSize := add(64, mul(32, keySize)) - // Allocate memory for the key - key := mload(0x40) - mstore(0x40, add(add(key, totalSize), 32)) - mstore(key, totalSize) +// // Allocate memory for the key +// key := mload(0x40) +// mstore(0x40, add(add(key, totalSize), 32)) +// mstore(key, totalSize) - // Clear any dirty upper bits of address - addr := and(addr, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) - // Store the address and batch index in the key buffer - mstore(add(key, 32), addr) - mstore(add(key, 64), batchIndex) - } - } +// // Clear any dirty upper bits of address +// addr := and(addr, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) +// // Store the address and batch index in the key buffer +// mstore(add(key, 32), addr) +// mstore(add(key, 64), batchIndex) +// } +// } - function associatedStorageLookup(bytes memory key, bytes32 input) internal pure returns (StoragePointer ptr) { - assembly ("memory-safe") { - mstore(add(key, 96), input) - ptr := keccak256(add(key, 32), mload(key)) - } - } +// function associatedStorageLookup(bytes memory key, bytes32 input) internal pure returns (StoragePointer ptr) +// { +// assembly ("memory-safe") { +// mstore(add(key, 96), input) +// ptr := keccak256(add(key, 32), mload(key)) +// } +// } - function associatedStorageLookup(bytes memory key, bytes32 input1, bytes32 input2) - internal - pure - returns (StoragePointer ptr) - { - assembly ("memory-safe") { - mstore(add(key, 96), input1) - mstore(add(key, 128), input2) - ptr := keccak256(add(key, 32), mload(key)) - } - } -} +// function associatedStorageLookup(bytes memory key, bytes32 input1, bytes32 input2) +// internal +// pure +// returns (StoragePointer ptr) +// { +// assembly ("memory-safe") { +// mstore(add(key, 96), input1) +// mstore(add(key, 128), input2) +// ptr := keccak256(add(key, 32), mload(key)) +// } +// } +// } diff --git a/src/plugins/owner/ISingleOwnerPlugin.sol b/src/plugins/owner/ISingleOwnerPlugin.sol index fc0622c8..4f5257e5 100644 --- a/src/plugins/owner/ISingleOwnerPlugin.sol +++ b/src/plugins/owner/ISingleOwnerPlugin.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.19; -import {UserOperation} from "@eth-infinitism/account-abstraction/interfaces/UserOperation.sol"; - interface ISingleOwnerPlugin { enum FunctionId { VALIDATION_OWNER_OR_SELF diff --git a/src/plugins/owner/SingleOwnerPlugin.sol b/src/plugins/owner/SingleOwnerPlugin.sol index 29103945..3c3507e2 100644 --- a/src/plugins/owner/SingleOwnerPlugin.sol +++ b/src/plugins/owner/SingleOwnerPlugin.sol @@ -60,34 +60,6 @@ contract SingleOwnerPlugin is BasePlugin, ISingleOwnerPlugin, IERC1271 { _transferOwnership(newOwner); } - /// @inheritdoc IERC1271 - /// @dev The signature is valid if it is signed by the owner's private key - /// (if the owner is an EOA) or if it is a valid ERC-1271 signature from the - /// owner (if the owner is a contract). Note that unlike the signature - /// validation used in `validateUserOp`, this does///*not** wrap the digest in - /// an "Ethereum Signed Message" envelope before checking the signature in - /// the EOA-owner case. - function isValidSignature(bytes32 digest, bytes memory signature) public view override returns (bytes4) { - if (SignatureChecker.isValidSignatureNow(_owners[msg.sender], digest, signature)) { - return _1271_MAGIC_VALUE; - } - return 0xffffffff; - } - - /// @inheritdoc ISingleOwnerPlugin - function owner() external view returns (address) { - return _owners[msg.sender]; - } - - // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Plugin view functions ┃ - // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - - /// @inheritdoc ISingleOwnerPlugin - function ownerOf(address account) external view returns (address) { - return _owners[account]; - } - // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ // ┃ Plugin interface functions ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ @@ -136,6 +108,38 @@ contract SingleOwnerPlugin is BasePlugin, ISingleOwnerPlugin, IERC1271 { revert NotImplemented(); } + // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ + // ┃ Execution view functions ┃ + // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + + /// @inheritdoc IERC1271 + /// @dev The signature is valid if it is signed by the owner's private key + /// (if the owner is an EOA) or if it is a valid ERC-1271 signature from the + /// owner (if the owner is a contract). Note that unlike the signature + /// validation used in `validateUserOp`, this does///*not** wrap the digest in + /// an "Ethereum Signed Message" envelope before checking the signature in + /// the EOA-owner case. + function isValidSignature(bytes32 digest, bytes memory signature) external view override returns (bytes4) { + if (SignatureChecker.isValidSignatureNow(_owners[msg.sender], digest, signature)) { + return _1271_MAGIC_VALUE; + } + return 0xffffffff; + } + + /// @inheritdoc ISingleOwnerPlugin + function owner() external view returns (address) { + return _owners[msg.sender]; + } + + // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ + // ┃ Plugin view functions ┃ + // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + + /// @inheritdoc ISingleOwnerPlugin + function ownerOf(address account) external view returns (address) { + return _owners[account]; + } + /// @inheritdoc BasePlugin function pluginManifest() external pure override returns (PluginManifest memory) { PluginManifest memory manifest; @@ -212,6 +216,7 @@ contract SingleOwnerPlugin is BasePlugin, ISingleOwnerPlugin, IERC1271 { return metadata; } + // ┏━━━━━━━━━━━━━━━┓ // ┃ EIP-165 ┃ // ┗━━━━━━━━━━━━━━━┛ diff --git a/src/samples/plugins/ModularSessionKeyPlugin.sol b/src/samples/plugins/ModularSessionKeyPlugin.sol index 7af6bb1c..035b62fa 100644 --- a/src/samples/plugins/ModularSessionKeyPlugin.sol +++ b/src/samples/plugins/ModularSessionKeyPlugin.sol @@ -1,368 +1,373 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.19; -import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import {UserOperation} from "@eth-infinitism/account-abstraction/interfaces/UserOperation.sol"; -import {UpgradeableModularAccount} from "../../account/UpgradeableModularAccount.sol"; -import { - ManifestFunction, - ManifestAssociatedFunctionType, - ManifestAssociatedFunction, - PluginManifest, - PluginMetadata, - SelectorPermission -} from "../../interfaces/IPlugin.sol"; -import {BasePlugin} from "../../plugins/BasePlugin.sol"; -import {IModularSessionKeyPlugin} from "./interfaces/ISessionKeyPlugin.sol"; -import {ISingleOwnerPlugin} from "../../plugins/owner/ISingleOwnerPlugin.sol"; -import {SingleOwnerPlugin} from "../../plugins/owner/SingleOwnerPlugin.sol"; -import {PluginStorageLib, StoragePointer} from "../../libraries/PluginStorageLib.sol"; - -/// @title Modular Session Key Plugin -/// @author Decipher ERC-6900 Team -/// @notice This plugin allows some designated EOA or smart contract to temporarily -/// own a modular account. Note that this plugin is ONLY for demonstrating the purpose -/// of the functionalities of ERC-6900, and MUST not be used at the production level. -/// This modular session key plugin acts as a 'parent plugin' for all specific session -/// keys. Using dependency, this plugin can be thought as a parent contract that stores -/// session key duration information, and validation functions for session keys. All -/// logics for session keys will be implemented in child plugins. -/// It allows for session key owners to access MSCA both through user operation and -/// runtime, with its own validation functions. -/// Also, it has a dependency on SingleOwnerPlugin, to make sure that only the owner of -/// the MSCA can add or remove session keys. -contract ModularSessionKeyPlugin is BasePlugin, IModularSessionKeyPlugin { - using ECDSA for bytes32; - using PluginStorageLib for address; - using PluginStorageLib for bytes; - using EnumerableSet for EnumerableSet.Bytes32Set; - - string public constant NAME = "Modular Session Key Plugin"; - string public constant VERSION = "1.0.0"; - string public constant AUTHOR = "Decipher ERC-6900 Team"; - - uint256 internal constant _SIG_VALIDATION_FAILED = 1; - - mapping(address account => EnumerableSet.Bytes32Set) private _sessionKeySet; - - struct SessionInfo { - uint48 validAfter; - uint48 validUntil; - } - - // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Execution functions ┃ - // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - - /// @inheritdoc IModularSessionKeyPlugin - function addSessionKey(address sessionKey, bytes4 allowedSelector, uint48 validAfter, uint48 validUntil) - external - { - _addSessionKey(msg.sender, sessionKey, allowedSelector, validAfter, validUntil); - emit SessionKeyAdded(msg.sender, sessionKey, allowedSelector, validAfter, validUntil); - } - - /// @inheritdoc IModularSessionKeyPlugin - function removeSessionKey(address sessionKey, bytes4 allowedSelector) external { - _removeSessionKey(msg.sender, sessionKey, allowedSelector); - emit SessionKeyRemoved(msg.sender, sessionKey, allowedSelector); - } - - /// @inheritdoc IModularSessionKeyPlugin - function addSessionKeyBatch( - address[] calldata sessionKeys, - bytes4[] calldata allowedSelectors, - uint48[] calldata validAfters, - uint48[] calldata validUntils - ) external { - if ( - sessionKeys.length != allowedSelectors.length || sessionKeys.length != validAfters.length - || sessionKeys.length != validUntils.length - ) { - revert WrongDataLength(); - } - for (uint256 i = 0; i < sessionKeys.length;) { - _addSessionKey(msg.sender, sessionKeys[i], allowedSelectors[i], validAfters[i], validUntils[i]); - - unchecked { - ++i; - } - } - emit SessionKeysAdded(msg.sender, sessionKeys, allowedSelectors, validAfters, validUntils); - } - - function removeSessionKeyBatch(address[] calldata sessionKeys, bytes4[] calldata allowedSelectors) external { - if (sessionKeys.length != allowedSelectors.length) { - revert WrongDataLength(); - } - for (uint256 i = 0; i < sessionKeys.length;) { - _removeSessionKey(msg.sender, sessionKeys[i], allowedSelectors[i]); - - unchecked { - ++i; - } - } - emit SessionKeysRemoved(msg.sender, sessionKeys, allowedSelectors); - } - - // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Plugin view functions ┃ - // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - - /// @inheritdoc IModularSessionKeyPlugin - function getSessionDuration(address account, address sessionKey, bytes4 allowedSelector) - external - view - returns (uint48 validAfter, uint48 validUntil) - { - bytes memory key = account.allocateAssociatedStorageKey(0, 1); - StoragePointer ptr = key.associatedStorageLookup(keccak256(abi.encodePacked(sessionKey, allowedSelector))); - SessionInfo storage sessionInfo = _castPtrToStruct(ptr); - validAfter = sessionInfo.validAfter; - validUntil = sessionInfo.validUntil; - } - - /// @inheritdoc IModularSessionKeyPlugin - function getSessionKeysAndSelectors(address account) - external - view - returns (address[] memory sessionKeys, bytes4[] memory selectors) - { - EnumerableSet.Bytes32Set storage sessionKeySet = _sessionKeySet[account]; - uint256 length = sessionKeySet.length(); - sessionKeys = new address[](length); - selectors = new bytes4[](length); - for (uint256 i = 0; i < length;) { - (sessionKeys[i], selectors[i]) = _castToAddressAndBytes4(sessionKeySet.at(i)); - - unchecked { - ++i; - } - } - } - - // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Plugin interface functions ┃ - // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - - /// @inheritdoc BasePlugin - function onInstall(bytes calldata data) external override { - if (data.length != 0) { - ( - address[] memory sessionKeys, - bytes4[] memory allowedSelectors, - uint48[] memory validAfters, - uint48[] memory validUntils - ) = abi.decode(data, (address[], bytes4[], uint48[], uint48[])); - if ( - sessionKeys.length != allowedSelectors.length || sessionKeys.length != validAfters.length - || sessionKeys.length != validUntils.length - ) { - revert WrongDataLength(); - } - for (uint256 i = 0; i < sessionKeys.length;) { - _addSessionKey(msg.sender, sessionKeys[i], allowedSelectors[i], validAfters[i], validUntils[i]); - - unchecked { - ++i; - } - } - } - } - - /// @inheritdoc BasePlugin - function onUninstall(bytes calldata) external override { - EnumerableSet.Bytes32Set storage sessionKeySet = _sessionKeySet[msg.sender]; - uint256 length = sessionKeySet.length(); - for (uint256 i = 0; i < length;) { - (address sessionKey, bytes4 allowedSelecor) = _castToAddressAndBytes4(sessionKeySet.at(i)); - _removeSessionKey(msg.sender, sessionKey, allowedSelecor); - - unchecked { - ++i; - } - } - } - - /// @inheritdoc BasePlugin - function userOpValidationFunction(uint8 functionId, UserOperation calldata userOp, bytes32 userOpHash) - external - view - override - returns (uint256) - { - if (functionId == uint8(FunctionId.VALIDATION_TEMPORARY_OWNER)) { - (address signer, ECDSA.RecoverError err) = - userOpHash.toEthSignedMessageHash().tryRecover(userOp.signature); - if (err != ECDSA.RecoverError.NoError) { - revert InvalidSignature(); - } - bytes4 selector = bytes4(userOp.callData[0:4]); - bytes memory key = msg.sender.allocateAssociatedStorageKey(0, 1); - StoragePointer ptr = key.associatedStorageLookup(keccak256(abi.encodePacked(signer, selector))); - SessionInfo storage duration = _castPtrToStruct(ptr); - uint48 validAfter = duration.validAfter; - uint48 validUntil = duration.validUntil; - - return _packValidationData(validUntil == 0, validUntil, validAfter); - } - revert NotImplemented(); - } - - /// @inheritdoc BasePlugin - function runtimeValidationFunction(uint8 functionId, address sender, uint256, bytes calldata data) - external - view - override - { - if (functionId == uint8(FunctionId.VALIDATION_TEMPORARY_OWNER)) { - bytes4 selector = bytes4(data[0:4]); - bytes memory key = msg.sender.allocateAssociatedStorageKey(0, 1); - StoragePointer ptr = key.associatedStorageLookup(keccak256(abi.encodePacked(sender, selector))); - SessionInfo storage duration = _castPtrToStruct(ptr); - uint48 validAfter = duration.validAfter; - uint48 validUntil = duration.validUntil; - - if (validUntil != 0) { - if (block.timestamp < validAfter || block.timestamp > validUntil) { - revert WrongTimeRangeForSession(); - } - return; - } - revert NotAuthorized(); - } - revert NotImplemented(); - } - - /// @inheritdoc BasePlugin - function pluginManifest() external pure override returns (PluginManifest memory) { - PluginManifest memory manifest; - - manifest.executionFunctions = new bytes4[](4); - manifest.executionFunctions[0] = this.addSessionKey.selector; - manifest.executionFunctions[1] = this.removeSessionKey.selector; - manifest.executionFunctions[2] = this.addSessionKeyBatch.selector; - manifest.executionFunctions[3] = this.removeSessionKeyBatch.selector; - - ManifestFunction memory ownerValidationFunction = ManifestFunction({ - functionType: ManifestAssociatedFunctionType.DEPENDENCY, - functionId: 0, // Unused. - dependencyIndex: 0 // Used as first index. - }); - manifest.validationFunctions = new ManifestAssociatedFunction[](5); - manifest.validationFunctions[0] = ManifestAssociatedFunction({ - executionSelector: this.addSessionKey.selector, - associatedFunction: ownerValidationFunction - }); - manifest.validationFunctions[1] = ManifestAssociatedFunction({ - executionSelector: this.removeSessionKey.selector, - associatedFunction: ownerValidationFunction - }); - manifest.validationFunctions[2] = ManifestAssociatedFunction({ - executionSelector: this.addSessionKeyBatch.selector, - associatedFunction: ownerValidationFunction - }); - manifest.validationFunctions[3] = ManifestAssociatedFunction({ - executionSelector: this.removeSessionKeyBatch.selector, - associatedFunction: ownerValidationFunction - }); - - ManifestFunction memory alwaysAllowFunction = ManifestFunction({ - functionType: ManifestAssociatedFunctionType.RUNTIME_VALIDATION_ALWAYS_ALLOW, - functionId: 0, // Unused. - dependencyIndex: 0 // Unused. - }); - - manifest.validationFunctions[4] = ManifestAssociatedFunction({ - executionSelector: this.getSessionDuration.selector, - associatedFunction: alwaysAllowFunction - }); - - manifest.dependencyInterfaceIds = new bytes4[](1); - manifest.dependencyInterfaceIds[0] = type(ISingleOwnerPlugin).interfaceId; - - return manifest; - } - - /// @inheritdoc BasePlugin - function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { - PluginMetadata memory metadata; - metadata.name = NAME; - metadata.version = VERSION; - metadata.author = AUTHOR; - - return metadata; - } - - // ┏━━━━━━━━━━━━━━━┓ - // ┃ EIP-165 ┃ - // ┗━━━━━━━━━━━━━━━┛ - - /// @inheritdoc BasePlugin - function supportsInterface(bytes4 interfaceId) public view override returns (bool) { - return interfaceId == type(IModularSessionKeyPlugin).interfaceId || super.supportsInterface(interfaceId); - } - - // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Internal / Private functions ┃ - // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - - function _addSessionKey( - address account, - address sessionKey, - bytes4 allowedSelector, - uint48 validAfter, - uint48 validUntil - ) internal { - if (validUntil <= validAfter) { - revert WrongTimeRangeForSession(); - } - bytes memory key = account.allocateAssociatedStorageKey(0, 1); - StoragePointer ptr = key.associatedStorageLookup(keccak256(abi.encodePacked(sessionKey, allowedSelector))); - SessionInfo storage sessionInfo = _castPtrToStruct(ptr); - sessionInfo.validAfter = validAfter; - sessionInfo.validUntil = validUntil; - - EnumerableSet.Bytes32Set storage sessionKeySet = _sessionKeySet[account]; - sessionKeySet.add(_castToBytes32(sessionKey, allowedSelector)); - } - - function _removeSessionKey(address account, address sessionKey, bytes4 allowedSelector) internal { - bytes memory key = account.allocateAssociatedStorageKey(0, 1); - StoragePointer ptr = key.associatedStorageLookup(keccak256(abi.encodePacked(sessionKey, allowedSelector))); - SessionInfo storage sessionInfo = _castPtrToStruct(ptr); - sessionInfo.validAfter = 0; - sessionInfo.validUntil = 0; - - EnumerableSet.Bytes32Set storage sessionKeySet = _sessionKeySet[account]; - sessionKeySet.remove(_castToBytes32(sessionKey, allowedSelector)); - } - - function _castPtrToStruct(StoragePointer ptr) internal pure returns (SessionInfo storage val) { - assembly ("memory-safe") { - val.slot := ptr - } - } - - function _castToBytes32(address addr, bytes4 b4) internal pure returns (bytes32 res) { - assembly { - res := or(shl(32, addr), b4) - } - } - - function _castToAddressAndBytes4(bytes32 b32) internal pure returns (address addr, bytes4 b4) { - assembly { - addr := shr(32, b32) - b4 := and(b32, 0xFFFFFFFF) - } - } - - function _packValidationData(bool sigFailed, uint48 validUntil, uint48 validAfter) - internal - pure - returns (uint256) - { - return (sigFailed ? 1 : 0) | (uint256(validUntil) << 160) | (uint256(validAfter) << (160 + 48)); - } -} +// import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +// import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +// import {UserOperation} from "@eth-infinitism/account-abstraction/interfaces/UserOperation.sol"; +// import {UpgradeableModularAccount} from "../../account/UpgradeableModularAccount.sol"; +// import { +// ManifestFunction, +// ManifestAssociatedFunctionType, +// ManifestAssociatedFunction, +// PluginManifest, +// PluginMetadata, +// SelectorPermission +// } from "../../interfaces/IPlugin.sol"; +// import {BasePlugin} from "../../plugins/BasePlugin.sol"; +// import {IModularSessionKeyPlugin} from "./interfaces/ISessionKeyPlugin.sol"; +// import {ISingleOwnerPlugin} from "../../plugins/owner/ISingleOwnerPlugin.sol"; +// import {SingleOwnerPlugin} from "../../plugins/owner/SingleOwnerPlugin.sol"; +// import {PluginStorageLib, StoragePointer} from "../../libraries/PluginStorageLib.sol"; + +// /// @title Modular Session Key Plugin +// /// @author Decipher ERC-6900 Team +// /// @notice This plugin allows some designated EOA or smart contract to temporarily +// /// own a modular account. Note that this plugin is ONLY for demonstrating the purpose +// /// of the functionalities of ERC-6900, and MUST not be used at the production level. +// /// This modular session key plugin acts as a 'parent plugin' for all specific session +// /// keys. Using dependency, this plugin can be thought as a parent contract that stores +// /// session key duration information, and validation functions for session keys. All +// /// logics for session keys will be implemented in child plugins. +// /// It allows for session key owners to access MSCA both through user operation and +// /// runtime, with its own validation functions. +// /// Also, it has a dependency on SingleOwnerPlugin, to make sure that only the owner of +// /// the MSCA can add or remove session keys. +// contract ModularSessionKeyPlugin is BasePlugin, IModularSessionKeyPlugin { +// using ECDSA for bytes32; +// using PluginStorageLib for address; +// using PluginStorageLib for bytes; +// using EnumerableSet for EnumerableSet.Bytes32Set; + +// string public constant NAME = "Modular Session Key Plugin"; +// string public constant VERSION = "1.0.0"; +// string public constant AUTHOR = "Decipher ERC-6900 Team"; + +// uint256 internal constant _SIG_VALIDATION_FAILED = 1; + +// mapping(address account => EnumerableSet.Bytes32Set) private _sessionKeySet; + +// struct SessionInfo { +// uint48 validAfter; +// uint48 validUntil; +// } + +// // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +// // ┃ Execution functions ┃ +// // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + +// /// @inheritdoc IModularSessionKeyPlugin +// function addSessionKey(address sessionKey, bytes4 allowedSelector, uint48 validAfter, uint48 validUntil) +// external +// { +// _addSessionKey(msg.sender, sessionKey, allowedSelector, validAfter, validUntil); +// emit SessionKeyAdded(msg.sender, sessionKey, allowedSelector, validAfter, validUntil); +// } + +// /// @inheritdoc IModularSessionKeyPlugin +// function removeSessionKey(address sessionKey, bytes4 allowedSelector) external { +// _removeSessionKey(msg.sender, sessionKey, allowedSelector); +// emit SessionKeyRemoved(msg.sender, sessionKey, allowedSelector); +// } + +// /// @inheritdoc IModularSessionKeyPlugin +// function addSessionKeyBatch( +// address[] calldata sessionKeys, +// bytes4[] calldata allowedSelectors, +// uint48[] calldata validAfters, +// uint48[] calldata validUntils +// ) external { +// if ( +// sessionKeys.length != allowedSelectors.length || sessionKeys.length != validAfters.length +// || sessionKeys.length != validUntils.length +// ) { +// revert WrongDataLength(); +// } +// for (uint256 i = 0; i < sessionKeys.length;) { +// _addSessionKey(msg.sender, sessionKeys[i], allowedSelectors[i], validAfters[i], validUntils[i]); + +// unchecked { +// ++i; +// } +// } +// emit SessionKeysAdded(msg.sender, sessionKeys, allowedSelectors, validAfters, validUntils); +// } + +// function removeSessionKeyBatch(address[] calldata sessionKeys, bytes4[] calldata allowedSelectors) external +// { +// if (sessionKeys.length != allowedSelectors.length) { +// revert WrongDataLength(); +// } +// for (uint256 i = 0; i < sessionKeys.length;) { +// _removeSessionKey(msg.sender, sessionKeys[i], allowedSelectors[i]); + +// unchecked { +// ++i; +// } +// } +// emit SessionKeysRemoved(msg.sender, sessionKeys, allowedSelectors); +// } + +// // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +// // ┃ Plugin view functions ┃ +// // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + +// /// @inheritdoc IModularSessionKeyPlugin +// function getSessionDuration(address account, address sessionKey, bytes4 allowedSelector) +// external +// view +// returns (uint48 validAfter, uint48 validUntil) +// { +// bytes memory key = account.allocateAssociatedStorageKey(0, 1); +// StoragePointer ptr = key.associatedStorageLookup(keccak256(abi.encodePacked(sessionKey, +// allowedSelector))); +// SessionInfo storage sessionInfo = _castPtrToStruct(ptr); +// validAfter = sessionInfo.validAfter; +// validUntil = sessionInfo.validUntil; +// } + +// /// @inheritdoc IModularSessionKeyPlugin +// function getSessionKeysAndSelectors(address account) +// external +// view +// returns (address[] memory sessionKeys, bytes4[] memory selectors) +// { +// EnumerableSet.Bytes32Set storage sessionKeySet = _sessionKeySet[account]; +// uint256 length = sessionKeySet.length(); +// sessionKeys = new address[](length); +// selectors = new bytes4[](length); +// for (uint256 i = 0; i < length;) { +// (sessionKeys[i], selectors[i]) = _castToAddressAndBytes4(sessionKeySet.at(i)); + +// unchecked { +// ++i; +// } +// } +// } + +// // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +// // ┃ Plugin interface functions ┃ +// // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + +// /// @inheritdoc BasePlugin +// function onInstall(bytes calldata data) external override { +// if (data.length != 0) { +// ( +// address[] memory sessionKeys, +// bytes4[] memory allowedSelectors, +// uint48[] memory validAfters, +// uint48[] memory validUntils +// ) = abi.decode(data, (address[], bytes4[], uint48[], uint48[])); +// if ( +// sessionKeys.length != allowedSelectors.length || sessionKeys.length != validAfters.length +// || sessionKeys.length != validUntils.length +// ) { +// revert WrongDataLength(); +// } +// for (uint256 i = 0; i < sessionKeys.length;) { +// _addSessionKey(msg.sender, sessionKeys[i], allowedSelectors[i], validAfters[i], validUntils[i]); + +// unchecked { +// ++i; +// } +// } +// } +// } + +// /// @inheritdoc BasePlugin +// function onUninstall(bytes calldata) external override { +// EnumerableSet.Bytes32Set storage sessionKeySet = _sessionKeySet[msg.sender]; +// uint256 length = sessionKeySet.length(); +// for (uint256 i = 0; i < length;) { +// (address sessionKey, bytes4 allowedSelecor) = _castToAddressAndBytes4(sessionKeySet.at(i)); +// _removeSessionKey(msg.sender, sessionKey, allowedSelecor); + +// unchecked { +// ++i; +// } +// } +// } + +// /// @inheritdoc BasePlugin +// function userOpValidationFunction(uint8 functionId, UserOperation calldata userOp, bytes32 userOpHash) +// external +// view +// override +// returns (uint256) +// { +// if (functionId == uint8(FunctionId.VALIDATION_TEMPORARY_OWNER)) { +// (address signer, ECDSA.RecoverError err) = +// userOpHash.toEthSignedMessageHash().tryRecover(userOp.signature); +// if (err != ECDSA.RecoverError.NoError) { +// revert InvalidSignature(); +// } +// bytes4 selector = bytes4(userOp.callData[0:4]); +// bytes memory key = msg.sender.allocateAssociatedStorageKey(0, 1); +// StoragePointer ptr = key.associatedStorageLookup(keccak256(abi.encodePacked(signer, selector))); +// SessionInfo storage duration = _castPtrToStruct(ptr); +// uint48 validAfter = duration.validAfter; +// uint48 validUntil = duration.validUntil; + +// return _packValidationData(validUntil == 0, validUntil, validAfter); +// } +// revert NotImplemented(); +// } + +// /// @inheritdoc BasePlugin +// function runtimeValidationFunction(uint8 functionId, address sender, uint256, bytes calldata data) +// external +// view +// override +// { +// if (functionId == uint8(FunctionId.VALIDATION_TEMPORARY_OWNER)) { +// bytes4 selector = bytes4(data[0:4]); +// bytes memory key = msg.sender.allocateAssociatedStorageKey(0, 1); +// StoragePointer ptr = key.associatedStorageLookup(keccak256(abi.encodePacked(sender, selector))); +// SessionInfo storage duration = _castPtrToStruct(ptr); +// uint48 validAfter = duration.validAfter; +// uint48 validUntil = duration.validUntil; + +// if (validUntil != 0) { +// if (block.timestamp < validAfter || block.timestamp > validUntil) { +// revert WrongTimeRangeForSession(); +// } +// return; +// } +// revert NotAuthorized(); +// } +// revert NotImplemented(); +// } + +// /// @inheritdoc BasePlugin +// function pluginManifest() external pure override returns (PluginManifest memory) { +// PluginManifest memory manifest; + +// manifest.executionFunctions = new bytes4[](4); +// manifest.executionFunctions[0] = this.addSessionKey.selector; +// manifest.executionFunctions[1] = this.removeSessionKey.selector; +// manifest.executionFunctions[2] = this.addSessionKeyBatch.selector; +// manifest.executionFunctions[3] = this.removeSessionKeyBatch.selector; + +// ManifestFunction memory ownerValidationFunction = ManifestFunction({ +// functionType: ManifestAssociatedFunctionType.DEPENDENCY, +// functionId: 0, // Unused. +// dependencyIndex: 0 // Used as first index. +// }); +// manifest.validationFunctions = new ManifestAssociatedFunction[](5); +// manifest.validationFunctions[0] = ManifestAssociatedFunction({ +// executionSelector: this.addSessionKey.selector, +// associatedFunction: ownerValidationFunction +// }); +// manifest.validationFunctions[1] = ManifestAssociatedFunction({ +// executionSelector: this.removeSessionKey.selector, +// associatedFunction: ownerValidationFunction +// }); +// manifest.validationFunctions[2] = ManifestAssociatedFunction({ +// executionSelector: this.addSessionKeyBatch.selector, +// associatedFunction: ownerValidationFunction +// }); +// manifest.validationFunctions[3] = ManifestAssociatedFunction({ +// executionSelector: this.removeSessionKeyBatch.selector, +// associatedFunction: ownerValidationFunction +// }); + +// ManifestFunction memory alwaysAllowFunction = ManifestFunction({ +// functionType: ManifestAssociatedFunctionType.RUNTIME_VALIDATION_ALWAYS_ALLOW, +// functionId: 0, // Unused. +// dependencyIndex: 0 // Unused. +// }); + +// manifest.validationFunctions[4] = ManifestAssociatedFunction({ +// executionSelector: this.getSessionDuration.selector, +// associatedFunction: alwaysAllowFunction +// }); + +// manifest.dependencyInterfaceIds = new bytes4[](1); +// manifest.dependencyInterfaceIds[0] = type(ISingleOwnerPlugin).interfaceId; + +// return manifest; +// } + +// /// @inheritdoc BasePlugin +// function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { +// PluginMetadata memory metadata; +// metadata.name = NAME; +// metadata.version = VERSION; +// metadata.author = AUTHOR; + +// return metadata; +// } + +// // ┏━━━━━━━━━━━━━━━┓ +// // ┃ EIP-165 ┃ +// // ┗━━━━━━━━━━━━━━━┛ + +// /// @inheritdoc BasePlugin +// function supportsInterface(bytes4 interfaceId) public view override returns (bool) { +// return interfaceId == type(IModularSessionKeyPlugin).interfaceId || +// super.supportsInterface(interfaceId); +// } + +// // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +// // ┃ Internal / Private functions ┃ +// // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + +// function _addSessionKey( +// address account, +// address sessionKey, +// bytes4 allowedSelector, +// uint48 validAfter, +// uint48 validUntil +// ) internal { +// if (validUntil <= validAfter) { +// revert WrongTimeRangeForSession(); +// } +// bytes memory key = account.allocateAssociatedStorageKey(0, 1); +// StoragePointer ptr = key.associatedStorageLookup(keccak256(abi.encodePacked(sessionKey, +// allowedSelector))); +// SessionInfo storage sessionInfo = _castPtrToStruct(ptr); +// sessionInfo.validAfter = validAfter; +// sessionInfo.validUntil = validUntil; + +// EnumerableSet.Bytes32Set storage sessionKeySet = _sessionKeySet[account]; +// sessionKeySet.add(_castToBytes32(sessionKey, allowedSelector)); +// } + +// function _removeSessionKey(address account, address sessionKey, bytes4 allowedSelector) internal { +// bytes memory key = account.allocateAssociatedStorageKey(0, 1); +// StoragePointer ptr = key.associatedStorageLookup(keccak256(abi.encodePacked(sessionKey, +// allowedSelector))); +// SessionInfo storage sessionInfo = _castPtrToStruct(ptr); +// sessionInfo.validAfter = 0; +// sessionInfo.validUntil = 0; + +// EnumerableSet.Bytes32Set storage sessionKeySet = _sessionKeySet[account]; +// sessionKeySet.remove(_castToBytes32(sessionKey, allowedSelector)); +// } + +// function _castPtrToStruct(StoragePointer ptr) internal pure returns (SessionInfo storage val) { +// assembly ("memory-safe") { +// val.slot := ptr +// } +// } + +// function _castToBytes32(address addr, bytes4 b4) internal pure returns (bytes32 res) { +// assembly { +// res := or(shl(32, addr), b4) +// } +// } + +// function _castToAddressAndBytes4(bytes32 b32) internal pure returns (address addr, bytes4 b4) { +// assembly { +// addr := shr(32, b32) +// b4 := and(b32, 0xFFFFFFFF) +// } +// } + +// function _packValidationData(bool sigFailed, uint48 validUntil, uint48 validAfter) +// internal +// pure +// returns (uint256) +// { +// return (sigFailed ? 1 : 0) | (uint256(validUntil) << 160) | (uint256(validAfter) << (160 + 48)); +// } +// } diff --git a/src/samples/plugins/TokenSessionKeyPlugin.sol b/src/samples/plugins/TokenSessionKeyPlugin.sol index ebc58eda..82cc6d24 100644 --- a/src/samples/plugins/TokenSessionKeyPlugin.sol +++ b/src/samples/plugins/TokenSessionKeyPlugin.sol @@ -1,117 +1,117 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.19; -import { - ManifestFunction, - ManifestAssociatedFunctionType, - ManifestAssociatedFunction, - PluginManifest, - PluginMetadata, - SelectorPermission, - ManifestExternalCallPermission -} from "../../interfaces/IPlugin.sol"; -import {BasePlugin} from "../../plugins/BasePlugin.sol"; -import {ModularSessionKeyPlugin} from "./ModularSessionKeyPlugin.sol"; -import {ITokenSessionKeyPlugin} from "./interfaces/ITokenSessionKeyPlugin.sol"; -import {IModularSessionKeyPlugin} from "./interfaces/ISessionKeyPlugin.sol"; -import {IPluginExecutor} from "../../interfaces/IPluginExecutor.sol"; - -/// @title Token Session Key Plugin -/// @author Decipher ERC-6900 Team -/// @notice This plugin acts as a 'child plugin' for ModularSessionKeyPlugin. -/// It implements the logic for session keys that are allowed to call ERC20 -/// transferFrom function. It allows for session key owners to access MSCA -/// with `transferFromSessionKey` function, which calls `executeFromPluginExternal` -/// function in PluginExecutor contract. -/// The target ERC20 contract and the selector for transferFrom function are hardcoded -/// in this plugin, since the pluginManifest function requires the information of -/// permitted external calls not to be changed in the future. For other child session -/// key plugins, there can be a set of permitted external calls according to the -/// specific needs. -contract TokenSessionKeyPlugin is BasePlugin, ITokenSessionKeyPlugin { - string public constant NAME = "Token Session Key Plugin"; - string public constant VERSION = "1.0.0"; - string public constant AUTHOR = "Decipher ERC-6900 Team"; - - // Mock address of target ERC20 contract - address public constant TARGET_ERC20_CONTRACT = 0xdeaDDeADDEaDdeaDdEAddEADDEAdDeadDEADDEaD; - bytes4 public constant TRANSFERFROM_SELECTOR = - bytes4(keccak256(bytes("transferFrom(address,address,uint256)"))); - - // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Execution functions ┃ - // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - - /// @inheritdoc ITokenSessionKeyPlugin - function transferFromSessionKey(address target, address from, address to, uint256 amount) - external - returns (bytes memory returnData) - { - bytes memory data = abi.encodeWithSelector(TRANSFERFROM_SELECTOR, from, to, amount); - returnData = IPluginExecutor(msg.sender).executeFromPluginExternal(target, 0, data); - } - - // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Plugin interface functions ┃ - // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - - /// @inheritdoc BasePlugin - function onInstall(bytes calldata data) external override {} - - /// @inheritdoc BasePlugin - function onUninstall(bytes calldata data) external override {} - - /// @inheritdoc BasePlugin - function pluginManifest() external pure override returns (PluginManifest memory) { - PluginManifest memory manifest; - - manifest.executionFunctions = new bytes4[](1); - manifest.executionFunctions[0] = this.transferFromSessionKey.selector; - - ManifestFunction memory tempOwnerValidationFunction = ManifestFunction({ - functionType: ManifestAssociatedFunctionType.DEPENDENCY, - functionId: 0, // Unused - dependencyIndex: 0 // Used as first index - }); - - manifest.validationFunctions = new ManifestAssociatedFunction[](1); - manifest.validationFunctions[0] = ManifestAssociatedFunction({ - executionSelector: this.transferFromSessionKey.selector, - associatedFunction: tempOwnerValidationFunction - }); - - manifest.dependencyInterfaceIds = new bytes4[](1); - manifest.dependencyInterfaceIds[0] = type(IModularSessionKeyPlugin).interfaceId; - - bytes4[] memory permittedExternalSelectors = new bytes4[](1); - permittedExternalSelectors[0] = TRANSFERFROM_SELECTOR; - - manifest.permittedExternalCalls = new ManifestExternalCallPermission[](1); - manifest.permittedExternalCalls[0] = ManifestExternalCallPermission({ - externalAddress: TARGET_ERC20_CONTRACT, - permitAnySelector: false, - selectors: permittedExternalSelectors - }); - - return manifest; - } - - /// @inheritdoc BasePlugin - function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { - PluginMetadata memory metadata; - metadata.name = NAME; - metadata.version = VERSION; - metadata.author = AUTHOR; - - return metadata; - } - - // ┏━━━━━━━━━━━━━━━┓ - // ┃ EIP-165 ┃ - // ┗━━━━━━━━━━━━━━━┛ - - /// @inheritdoc BasePlugin - function supportsInterface(bytes4 interfaceId) public view override returns (bool) { - return interfaceId == type(ITokenSessionKeyPlugin).interfaceId || super.supportsInterface(interfaceId); - } -} +// import { +// ManifestFunction, +// ManifestAssociatedFunctionType, +// ManifestAssociatedFunction, +// PluginManifest, +// PluginMetadata, +// SelectorPermission, +// ManifestExternalCallPermission +// } from "../../interfaces/IPlugin.sol"; +// import {BasePlugin} from "../../plugins/BasePlugin.sol"; +// import {ModularSessionKeyPlugin} from "./ModularSessionKeyPlugin.sol"; +// import {ITokenSessionKeyPlugin} from "./interfaces/ITokenSessionKeyPlugin.sol"; +// import {IModularSessionKeyPlugin} from "./interfaces/ISessionKeyPlugin.sol"; +// import {IPluginExecutor} from "../../interfaces/IPluginExecutor.sol"; + +// /// @title Token Session Key Plugin +// /// @author Decipher ERC-6900 Team +// /// @notice This plugin acts as a 'child plugin' for ModularSessionKeyPlugin. +// /// It implements the logic for session keys that are allowed to call ERC20 +// /// transferFrom function. It allows for session key owners to access MSCA +// /// with `transferFromSessionKey` function, which calls `executeFromPluginExternal` +// /// function in PluginExecutor contract. +// /// The target ERC20 contract and the selector for transferFrom function are hardcoded +// /// in this plugin, since the pluginManifest function requires the information of +// /// permitted external calls not to be changed in the future. For other child session +// /// key plugins, there can be a set of permitted external calls according to the +// /// specific needs. +// contract TokenSessionKeyPlugin is BasePlugin, ITokenSessionKeyPlugin { +// string public constant NAME = "Token Session Key Plugin"; +// string public constant VERSION = "1.0.0"; +// string public constant AUTHOR = "Decipher ERC-6900 Team"; + +// // Mock address of target ERC20 contract +// address public constant TARGET_ERC20_CONTRACT = 0xdeaDDeADDEaDdeaDdEAddEADDEAdDeadDEADDEaD; +// bytes4 public constant TRANSFERFROM_SELECTOR = +// bytes4(keccak256(bytes("transferFrom(address,address,uint256)"))); + +// // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +// // ┃ Execution functions ┃ +// // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + +// /// @inheritdoc ITokenSessionKeyPlugin +// function transferFromSessionKey(address target, address from, address to, uint256 amount) +// external +// returns (bytes memory returnData) +// { +// bytes memory data = abi.encodeWithSelector(TRANSFERFROM_SELECTOR, from, to, amount); +// returnData = IPluginExecutor(msg.sender).executeFromPluginExternal(target, 0, data); +// } + +// // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +// // ┃ Plugin interface functions ┃ +// // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + +// /// @inheritdoc BasePlugin +// function onInstall(bytes calldata data) external override {} + +// /// @inheritdoc BasePlugin +// function onUninstall(bytes calldata data) external override {} + +// /// @inheritdoc BasePlugin +// function pluginManifest() external pure override returns (PluginManifest memory) { +// PluginManifest memory manifest; + +// manifest.executionFunctions = new bytes4[](1); +// manifest.executionFunctions[0] = this.transferFromSessionKey.selector; + +// ManifestFunction memory tempOwnerValidationFunction = ManifestFunction({ +// functionType: ManifestAssociatedFunctionType.DEPENDENCY, +// functionId: 0, // Unused +// dependencyIndex: 0 // Used as first index +// }); + +// manifest.validationFunctions = new ManifestAssociatedFunction[](1); +// manifest.validationFunctions[0] = ManifestAssociatedFunction({ +// executionSelector: this.transferFromSessionKey.selector, +// associatedFunction: tempOwnerValidationFunction +// }); + +// manifest.dependencyInterfaceIds = new bytes4[](1); +// manifest.dependencyInterfaceIds[0] = type(IModularSessionKeyPlugin).interfaceId; + +// bytes4[] memory permittedExternalSelectors = new bytes4[](1); +// permittedExternalSelectors[0] = TRANSFERFROM_SELECTOR; + +// manifest.permittedExternalCalls = new ManifestExternalCallPermission[](1); +// manifest.permittedExternalCalls[0] = ManifestExternalCallPermission({ +// externalAddress: TARGET_ERC20_CONTRACT, +// permitAnySelector: false, +// selectors: permittedExternalSelectors +// }); + +// return manifest; +// } + +// /// @inheritdoc BasePlugin +// function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { +// PluginMetadata memory metadata; +// metadata.name = NAME; +// metadata.version = VERSION; +// metadata.author = AUTHOR; + +// return metadata; +// } + +// // ┏━━━━━━━━━━━━━━━┓ +// // ┃ EIP-165 ┃ +// // ┗━━━━━━━━━━━━━━━┛ + +// /// @inheritdoc BasePlugin +// function supportsInterface(bytes4 interfaceId) public view override returns (bool) { +// return interfaceId == type(ITokenSessionKeyPlugin).interfaceId || super.supportsInterface(interfaceId); +// } +// } diff --git a/src/samples/plugins/interfaces/ISessionKeyPlugin.sol b/src/samples/plugins/interfaces/ISessionKeyPlugin.sol index 18a2204c..3d378d29 100644 --- a/src/samples/plugins/interfaces/ISessionKeyPlugin.sol +++ b/src/samples/plugins/interfaces/ISessionKeyPlugin.sol @@ -1,108 +1,109 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.19; -import {UserOperation} from "@eth-infinitism/account-abstraction/interfaces/UserOperation.sol"; +// import {UserOperation} from "@eth-infinitism/account-abstraction/interfaces/UserOperation.sol"; -interface IModularSessionKeyPlugin { - enum FunctionId { - VALIDATION_TEMPORARY_OWNER - } +// interface IModularSessionKeyPlugin { +// enum FunctionId { +// VALIDATION_TEMPORARY_OWNER +// } - /// @notice This event is emitted when a session key is added to the account. - /// @param account The account whose session key is updated. - /// @param sessionKey The address of the session key. - /// @param selector The selector of the function that the session key is allowed to call. - /// @param validAfter The time after which the owner is valid. - /// @param validUntil The time until which the owner is valid. - event SessionKeyAdded( - address indexed account, address indexed sessionKey, bytes4 selector, uint48 validAfter, uint48 validUntil - ); +// /// @notice This event is emitted when a session key is added to the account. +// /// @param account The account whose session key is updated. +// /// @param sessionKey The address of the session key. +// /// @param selector The selector of the function that the session key is allowed to call. +// /// @param validAfter The time after which the owner is valid. +// /// @param validUntil The time until which the owner is valid. +// event SessionKeyAdded( +// address indexed account, address indexed sessionKey, bytes4 selector, uint48 validAfter, uint48 +// validUntil +// ); - /// @notice This event is emitted when a session key is removed from the account. - /// @param account The account whose session key is updated. - /// @param sessionKey The address of the session key. - /// @param selector The selector of the function that the session key is allowed to call. - event SessionKeyRemoved(address indexed account, address indexed sessionKey, bytes4 selector); +// /// @notice This event is emitted when a session key is removed from the account. +// /// @param account The account whose session key is updated. +// /// @param sessionKey The address of the session key. +// /// @param selector The selector of the function that the session key is allowed to call. +// event SessionKeyRemoved(address indexed account, address indexed sessionKey, bytes4 selector); - /// @notice This event is emitted when session keys are added to the account. - /// @param account The account whose session keys are updated. - /// @param sessionKeys The addresses of the session keys. - /// @param selectors The selectors of the functions that the session keys are allowed to call. - /// @param validAfters The times after which the owners are valid. - /// @param validUntils The times until which the owners are valid. - event SessionKeysAdded( - address indexed account, - address[] sessionKeys, - bytes4[] selectors, - uint48[] validAfters, - uint48[] validUntils - ); +// /// @notice This event is emitted when session keys are added to the account. +// /// @param account The account whose session keys are updated. +// /// @param sessionKeys The addresses of the session keys. +// /// @param selectors The selectors of the functions that the session keys are allowed to call. +// /// @param validAfters The times after which the owners are valid. +// /// @param validUntils The times until which the owners are valid. +// event SessionKeysAdded( +// address indexed account, +// address[] sessionKeys, +// bytes4[] selectors, +// uint48[] validAfters, +// uint48[] validUntils +// ); - /// @notice This event is emitted when session keys are removed from the account. - /// @param account The account whose session keys are updated. - /// @param sessionKeys The addresses of the session keys. - /// @param selectors The selectors of the functions that the session keys are allowed to call. - event SessionKeysRemoved(address indexed account, address[] sessionKeys, bytes4[] selectors); +// /// @notice This event is emitted when session keys are removed from the account. +// /// @param account The account whose session keys are updated. +// /// @param sessionKeys The addresses of the session keys. +// /// @param selectors The selectors of the functions that the session keys are allowed to call. +// event SessionKeysRemoved(address indexed account, address[] sessionKeys, bytes4[] selectors); - error InvalidSignature(); - error NotAuthorized(); - error WrongTimeRangeForSession(); - error WrongDataLength(); +// error InvalidSignature(); +// error NotAuthorized(); +// error WrongTimeRangeForSession(); +// error WrongDataLength(); - /// @notice Add a session key to the account. - /// @dev This function is installed on the account as part of plugin installation, and should - /// only be called from an account. The function selector installed by a child session key plugin - /// is passed as a parameter, which enforces its own permissions on the calls it can make. - /// @param sessionKey The address of the session key. - /// @param allowedSelector The selector of the function that the session key is allowed to call. - /// @param validAfter The time after which the owner is valid. - /// @param validUntil The time until which the owner is valid. - function addSessionKey(address sessionKey, bytes4 allowedSelector, uint48 validAfter, uint48 validUntil) - external; +// /// @notice Add a session key to the account. +// /// @dev This function is installed on the account as part of plugin installation, and should +// /// only be called from an account. The function selector installed by a child session key plugin +// /// is passed as a parameter, which enforces its own permissions on the calls it can make. +// /// @param sessionKey The address of the session key. +// /// @param allowedSelector The selector of the function that the session key is allowed to call. +// /// @param validAfter The time after which the owner is valid. +// /// @param validUntil The time until which the owner is valid. +// function addSessionKey(address sessionKey, bytes4 allowedSelector, uint48 validAfter, uint48 validUntil) +// external; - /// @notice Remove a session key from the account. - /// @dev This function is installed on the account as part of plugin installation, and should - /// only be called from an account. - /// @param sessionKey The address of the session key. - /// @param allowedSelector The selector of the function that the session key is allowed to call. - function removeSessionKey(address sessionKey, bytes4 allowedSelector) external; +// /// @notice Remove a session key from the account. +// /// @dev This function is installed on the account as part of plugin installation, and should +// /// only be called from an account. +// /// @param sessionKey The address of the session key. +// /// @param allowedSelector The selector of the function that the session key is allowed to call. +// function removeSessionKey(address sessionKey, bytes4 allowedSelector) external; - /// @notice Add session keys to the account. - /// @dev This function is installed on the account as part of plugin installation, and should - /// only be called from an account. - /// @param sessionKeys The addresses of the session keys. - /// @param allowedSelectors The selectors of the functions that the session keys are allowed to call. - /// @param validAfters The times after which the owners are valid. - /// @param validUntils The times until which the owners are valid. - function addSessionKeyBatch( - address[] calldata sessionKeys, - bytes4[] calldata allowedSelectors, - uint48[] calldata validAfters, - uint48[] calldata validUntils - ) external; +// /// @notice Add session keys to the account. +// /// @dev This function is installed on the account as part of plugin installation, and should +// /// only be called from an account. +// /// @param sessionKeys The addresses of the session keys. +// /// @param allowedSelectors The selectors of the functions that the session keys are allowed to call. +// /// @param validAfters The times after which the owners are valid. +// /// @param validUntils The times until which the owners are valid. +// function addSessionKeyBatch( +// address[] calldata sessionKeys, +// bytes4[] calldata allowedSelectors, +// uint48[] calldata validAfters, +// uint48[] calldata validUntils +// ) external; - /// @notice Remove session keys from the account. - /// @dev This function is installed on the account as part of plugin installation, and should - /// only be called from an account. - /// @param sessionKeys The addresses of the session keys. - /// @param allowedSelectors The selectors of the functions that the session keys are allowed to call. - function removeSessionKeyBatch(address[] calldata sessionKeys, bytes4[] calldata allowedSelectors) external; +// /// @notice Remove session keys from the account. +// /// @dev This function is installed on the account as part of plugin installation, and should +// /// only be called from an account. +// /// @param sessionKeys The addresses of the session keys. +// /// @param allowedSelectors The selectors of the functions that the session keys are allowed to call. +// function removeSessionKeyBatch(address[] calldata sessionKeys, bytes4[] calldata allowedSelectors) external; - /// @notice Get Session data for a given account and session key. - /// @param account The account to get session data for. - /// @param sessionKey The address of the session key. - /// @param allowedSelector The selector of the function that the session key is allowed to call. - function getSessionDuration(address account, address sessionKey, bytes4 allowedSelector) - external - view - returns (uint48 validAfter, uint48 validUntil); +// /// @notice Get Session data for a given account and session key. +// /// @param account The account to get session data for. +// /// @param sessionKey The address of the session key. +// /// @param allowedSelector The selector of the function that the session key is allowed to call. +// function getSessionDuration(address account, address sessionKey, bytes4 allowedSelector) +// external +// view +// returns (uint48 validAfter, uint48 validUntil); - /// @notice Get all session keys and selectors for a given account. - /// @param account The account to get session keys and selectors for. - /// @return sessionKeys The addresses of the session keys. - /// @return selectors The selectors of the functions that the session keys are allowed to call. - function getSessionKeysAndSelectors(address account) - external - view - returns (address[] memory sessionKeys, bytes4[] memory selectors); -} +// /// @notice Get all session keys and selectors for a given account. +// /// @param account The account to get session keys and selectors for. +// /// @return sessionKeys The addresses of the session keys. +// /// @return selectors The selectors of the functions that the session keys are allowed to call. +// function getSessionKeysAndSelectors(address account) +// external +// view +// returns (address[] memory sessionKeys, bytes4[] memory selectors); +// } diff --git a/src/samples/plugins/interfaces/ITokenSessionKeyPlugin.sol b/src/samples/plugins/interfaces/ITokenSessionKeyPlugin.sol index 65e64113..2e990ef7 100644 --- a/src/samples/plugins/interfaces/ITokenSessionKeyPlugin.sol +++ b/src/samples/plugins/interfaces/ITokenSessionKeyPlugin.sol @@ -1,19 +1,19 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.19; -import {UserOperation} from "@eth-infinitism/account-abstraction/interfaces/UserOperation.sol"; +// import {UserOperation} from "@eth-infinitism/account-abstraction/interfaces/UserOperation.sol"; -interface ITokenSessionKeyPlugin { - error NotAuthorized(); +// interface ITokenSessionKeyPlugin { +// error NotAuthorized(); - /// @notice Route call to executeFromPluginExternal at the MSCA. - /// @dev This function will call with value = 0, since sending ether - /// to ERC20 contract is not a normal case. - /// @param target The target address to execute the call on. - /// @param from The address to transfer tokens from. - /// @param to The address to transfer tokens to. - /// @param amount The amount of tokens to transfer. - function transferFromSessionKey(address target, address from, address to, uint256 amount) - external - returns (bytes memory returnData); -} +// /// @notice Route call to executeFromPluginExternal at the MSCA. +// /// @dev This function will call with value = 0, since sending ether +// /// to ERC20 contract is not a normal case. +// /// @param target The target address to execute the call on. +// /// @param from The address to transfer tokens from. +// /// @param to The address to transfer tokens to. +// /// @param amount The amount of tokens to transfer. +// function transferFromSessionKey(address target, address from, address to, uint256 amount) +// external +// returns (bytes memory returnData); +// } diff --git a/test/libraries/AssociatedLinkedListSetLib.t.sol b/test/libraries/AssociatedLinkedListSetLib.t.sol index 21dd59ef..99ed5e25 100644 --- a/test/libraries/AssociatedLinkedListSetLib.t.sol +++ b/test/libraries/AssociatedLinkedListSetLib.t.sol @@ -1,220 +1,220 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {Test} from "forge-std/Test.sol"; -import { - AssociatedLinkedListSet, - AssociatedLinkedListSetLib, - SENTINEL_VALUE, - SetValue -} from "../../src/libraries/AssociatedLinkedListSetLib.sol"; +// import {Test} from "forge-std/Test.sol"; +// import { +// AssociatedLinkedListSet, +// AssociatedLinkedListSetLib, +// SENTINEL_VALUE, +// SetValue +// } from "../../src/libraries/AssociatedLinkedListSetLib.sol"; -contract AssociatedLinkedListSetLibTest is Test { - using AssociatedLinkedListSetLib for AssociatedLinkedListSet; +// contract AssociatedLinkedListSetLibTest is Test { +// using AssociatedLinkedListSetLib for AssociatedLinkedListSet; - AssociatedLinkedListSet internal _set1; - AssociatedLinkedListSet internal _set2; +// AssociatedLinkedListSet internal _set1; +// AssociatedLinkedListSet internal _set2; - address internal _associated = address(this); +// address internal _associated = address(this); - // User-defined function for wrapping from bytes30 (uint240) to SetValue - // Can define a custom one for addresses, uints, etc. - function _getListValue(uint240 value) internal pure returns (SetValue) { - return SetValue.wrap(bytes30(value)); - } +// // User-defined function for wrapping from bytes30 (uint240) to SetValue +// // Can define a custom one for addresses, uints, etc. +// function _getListValue(uint240 value) internal pure returns (SetValue) { +// return SetValue.wrap(bytes30(value)); +// } - function test_add_contains() public { - SetValue value = _getListValue(12); +// function test_add_contains() public { +// SetValue value = _getListValue(12); - assertTrue(_set1.tryAdd(_associated, value)); - assertTrue(_set1.contains(_associated, value)); - } +// assertTrue(_set1.tryAdd(_associated, value)); +// assertTrue(_set1.contains(_associated, value)); +// } - function test_empty() public { - SetValue value = _getListValue(12); +// function test_empty() public { +// SetValue value = _getListValue(12); - assertFalse(_set1.contains(_associated, value)); - assertTrue(_set1.isEmpty(_associated)); - } +// assertFalse(_set1.contains(_associated, value)); +// assertTrue(_set1.isEmpty(_associated)); +// } - function test_remove() public { - SetValue value = _getListValue(12); +// function test_remove() public { +// SetValue value = _getListValue(12); - assertTrue(_set1.tryAdd(_associated, value)); - assertTrue(_set1.contains(_associated, value)); +// assertTrue(_set1.tryAdd(_associated, value)); +// assertTrue(_set1.contains(_associated, value)); - assertTrue(_set1.tryRemove(_associated, value)); - assertFalse(_set1.contains(_associated, value)); - } +// assertTrue(_set1.tryRemove(_associated, value)); +// assertFalse(_set1.contains(_associated, value)); +// } - function test_remove_empty() public { - SetValue value = _getListValue(12); +// function test_remove_empty() public { +// SetValue value = _getListValue(12); - assertFalse(_set1.tryRemove(_associated, value)); - } +// assertFalse(_set1.tryRemove(_associated, value)); +// } - function test_remove_nonexistent() public { - SetValue value = _getListValue(12); +// function test_remove_nonexistent() public { +// SetValue value = _getListValue(12); - assertTrue(_set1.tryAdd(_associated, value)); - assertTrue(_set1.contains(_associated, value)); +// assertTrue(_set1.tryAdd(_associated, value)); +// assertTrue(_set1.contains(_associated, value)); - SetValue value2 = _getListValue(13); - assertFalse(_set1.tryRemove(_associated, value2)); - assertTrue(_set1.contains(_associated, value)); - } +// SetValue value2 = _getListValue(13); +// assertFalse(_set1.tryRemove(_associated, value2)); +// assertTrue(_set1.contains(_associated, value)); +// } - function test_remove_nonexistent_empty() public { - SetValue value = _getListValue(12); +// function test_remove_nonexistent_empty() public { +// SetValue value = _getListValue(12); - assertFalse(_set1.tryRemove(_associated, value)); - } +// assertFalse(_set1.tryRemove(_associated, value)); +// } - function test_remove_nonexistent_empty2() public { - SetValue value = _getListValue(12); +// function test_remove_nonexistent_empty2() public { +// SetValue value = _getListValue(12); - assertTrue(_set1.tryAdd(_associated, value)); - assertTrue(_set1.contains(_associated, value)); +// assertTrue(_set1.tryAdd(_associated, value)); +// assertTrue(_set1.contains(_associated, value)); - SetValue value2 = _getListValue(13); - assertFalse(_set1.tryRemove(_associated, value2)); - assertTrue(_set1.contains(_associated, value)); - } +// SetValue value2 = _getListValue(13); +// assertFalse(_set1.tryRemove(_associated, value2)); +// assertTrue(_set1.contains(_associated, value)); +// } - function test_add_remove_add() public { - SetValue value = _getListValue(12); +// function test_add_remove_add() public { +// SetValue value = _getListValue(12); - assertTrue(_set1.tryAdd(_associated, value)); - assertTrue(_set1.contains(_associated, value)); +// assertTrue(_set1.tryAdd(_associated, value)); +// assertTrue(_set1.contains(_associated, value)); - assertTrue(_set1.tryRemove(_associated, value)); - assertFalse(_set1.contains(_associated, value)); +// assertTrue(_set1.tryRemove(_associated, value)); +// assertFalse(_set1.contains(_associated, value)); - assertTrue(_set1.tryAdd(_associated, value)); - assertTrue(_set1.contains(_associated, value)); - } +// assertTrue(_set1.tryAdd(_associated, value)); +// assertTrue(_set1.contains(_associated, value)); +// } - function test_add_remove_add_empty() public { - SetValue value = _getListValue(12); +// function test_add_remove_add_empty() public { +// SetValue value = _getListValue(12); - assertTrue(_set1.tryAdd(_associated, value)); - assertTrue(_set1.contains(_associated, value)); +// assertTrue(_set1.tryAdd(_associated, value)); +// assertTrue(_set1.contains(_associated, value)); - assertTrue(_set1.tryRemove(_associated, value)); - assertFalse(_set1.contains(_associated, value)); +// assertTrue(_set1.tryRemove(_associated, value)); +// assertFalse(_set1.contains(_associated, value)); - assertTrue(_set1.tryAdd(_associated, value)); - assertTrue(_set1.contains(_associated, value)); - } +// assertTrue(_set1.tryAdd(_associated, value)); +// assertTrue(_set1.contains(_associated, value)); +// } - function test_no_address_collision() public { - SetValue value = _getListValue(12); +// function test_no_address_collision() public { +// SetValue value = _getListValue(12); - assertTrue(_set1.tryAdd(_associated, value)); - assertTrue(_set1.contains(_associated, value)); - assertFalse(_set2.contains(_associated, value)); - } +// assertTrue(_set1.tryAdd(_associated, value)); +// assertTrue(_set1.contains(_associated, value)); +// assertFalse(_set2.contains(_associated, value)); +// } - function test_clear() public { - SetValue value = _getListValue(12); +// function test_clear() public { +// SetValue value = _getListValue(12); - assertTrue(_set1.tryAdd(_associated, value)); - assertTrue(_set1.contains(_associated, value)); +// assertTrue(_set1.tryAdd(_associated, value)); +// assertTrue(_set1.contains(_associated, value)); - _set1.clear(_associated); +// _set1.clear(_associated); - assertFalse(_set1.contains(_associated, value)); - assertTrue(_set1.isEmpty(_associated)); - } +// assertFalse(_set1.contains(_associated, value)); +// assertTrue(_set1.isEmpty(_associated)); +// } - function test_getAll() public { - SetValue value = _getListValue(12); - SetValue value2 = _getListValue(13); +// function test_getAll() public { +// SetValue value = _getListValue(12); +// SetValue value2 = _getListValue(13); - assertTrue(_set1.tryAdd(_associated, value)); - assertTrue(_set1.tryAdd(_associated, value2)); +// assertTrue(_set1.tryAdd(_associated, value)); +// assertTrue(_set1.tryAdd(_associated, value2)); - SetValue[] memory values = _set1.getAll(_associated); - assertEq(values.length, 2); - // Returned set will be in reverse order of added elements - assertEq(SetValue.unwrap(values[1]), SetValue.unwrap(value)); - assertEq(SetValue.unwrap(values[0]), SetValue.unwrap(value2)); - } +// SetValue[] memory values = _set1.getAll(_associated); +// assertEq(values.length, 2); +// // Returned set will be in reverse order of added elements +// assertEq(SetValue.unwrap(values[1]), SetValue.unwrap(value)); +// assertEq(SetValue.unwrap(values[0]), SetValue.unwrap(value2)); +// } - function test_getAll2() public { - SetValue value = _getListValue(12); - SetValue value2 = _getListValue(13); - SetValue value3 = _getListValue(14); +// function test_getAll2() public { +// SetValue value = _getListValue(12); +// SetValue value2 = _getListValue(13); +// SetValue value3 = _getListValue(14); - assertTrue(_set1.tryAdd(_associated, value)); - assertTrue(_set1.tryAdd(_associated, value2)); - assertTrue(_set1.tryAdd(_associated, value3)); +// assertTrue(_set1.tryAdd(_associated, value)); +// assertTrue(_set1.tryAdd(_associated, value2)); +// assertTrue(_set1.tryAdd(_associated, value3)); - SetValue[] memory values = _set1.getAll(_associated); - assertEq(values.length, 3); - // Returned set will be in reverse order of added elements - assertEq(SetValue.unwrap(values[2]), SetValue.unwrap(value)); - assertEq(SetValue.unwrap(values[1]), SetValue.unwrap(value2)); - assertEq(SetValue.unwrap(values[0]), SetValue.unwrap(value3)); - } +// SetValue[] memory values = _set1.getAll(_associated); +// assertEq(values.length, 3); +// // Returned set will be in reverse order of added elements +// assertEq(SetValue.unwrap(values[2]), SetValue.unwrap(value)); +// assertEq(SetValue.unwrap(values[1]), SetValue.unwrap(value2)); +// assertEq(SetValue.unwrap(values[0]), SetValue.unwrap(value3)); +// } - function test_getAll_empty() public { - SetValue[] memory values = _set1.getAll(_associated); - assertEq(values.length, 0); - } +// function test_getAll_empty() public { +// SetValue[] memory values = _set1.getAll(_associated); +// assertEq(values.length, 0); +// } - function test_tryRemoveKnown1() public { - SetValue value = _getListValue(12); +// function test_tryRemoveKnown1() public { +// SetValue value = _getListValue(12); - assertTrue(_set1.tryAdd(_associated, value)); - assertTrue(_set1.contains(_associated, value)); +// assertTrue(_set1.tryAdd(_associated, value)); +// assertTrue(_set1.contains(_associated, value)); - assertTrue(_set1.tryRemoveKnown(_associated, value, SENTINEL_VALUE)); - assertFalse(_set1.contains(_associated, value)); - assertTrue(_set1.isEmpty(_associated)); - } +// assertTrue(_set1.tryRemoveKnown(_associated, value, SENTINEL_VALUE)); +// assertFalse(_set1.contains(_associated, value)); +// assertTrue(_set1.isEmpty(_associated)); +// } - function test_tryRemoveKnown2() public { - SetValue value1 = _getListValue(12); - SetValue value2 = _getListValue(13); +// function test_tryRemoveKnown2() public { +// SetValue value1 = _getListValue(12); +// SetValue value2 = _getListValue(13); - assertTrue(_set1.tryAdd(_associated, value1)); - assertTrue(_set1.tryAdd(_associated, value2)); - assertTrue(_set1.contains(_associated, value1)); - assertTrue(_set1.contains(_associated, value2)); +// assertTrue(_set1.tryAdd(_associated, value1)); +// assertTrue(_set1.tryAdd(_associated, value2)); +// assertTrue(_set1.contains(_associated, value1)); +// assertTrue(_set1.contains(_associated, value2)); - // Assert that getAll returns the correct values - SetValue[] memory values = _set1.getAll(_associated); - assertEq(values.length, 2); - assertEq(SetValue.unwrap(values[1]), SetValue.unwrap(value1)); - assertEq(SetValue.unwrap(values[0]), SetValue.unwrap(value2)); +// // Assert that getAll returns the correct values +// SetValue[] memory values = _set1.getAll(_associated); +// assertEq(values.length, 2); +// assertEq(SetValue.unwrap(values[1]), SetValue.unwrap(value1)); +// assertEq(SetValue.unwrap(values[0]), SetValue.unwrap(value2)); - assertTrue(_set1.tryRemoveKnown(_associated, value1, bytes32(SetValue.unwrap(value2)))); - assertFalse(_set1.contains(_associated, value1)); - assertTrue(_set1.contains(_associated, value2)); +// assertTrue(_set1.tryRemoveKnown(_associated, value1, bytes32(SetValue.unwrap(value2)))); +// assertFalse(_set1.contains(_associated, value1)); +// assertTrue(_set1.contains(_associated, value2)); - // Assert that getAll returns the correct values - values = _set1.getAll(_associated); - assertEq(values.length, 1); - assertEq(SetValue.unwrap(values[0]), SetValue.unwrap(value2)); - - assertTrue(_set1.tryRemoveKnown(_associated, value2, SENTINEL_VALUE)); - assertFalse(_set1.contains(_associated, value1)); - - assertTrue(_set1.isEmpty(_associated)); - } - - function test_tryRemoveKnown_invalid1() public { - SetValue value1 = _getListValue(12); - SetValue value2 = _getListValue(13); - - assertTrue(_set1.tryAdd(_associated, value1)); - assertTrue(_set1.tryAdd(_associated, value2)); - - assertFalse(_set1.tryRemoveKnown(_associated, value1, bytes32(SetValue.unwrap(value1)))); - assertTrue(_set1.contains(_associated, value1)); - - assertFalse(_set1.tryRemoveKnown(_associated, value2, bytes32(SetValue.unwrap(value2)))); - assertTrue(_set1.contains(_associated, value2)); - } -} +// // Assert that getAll returns the correct values +// values = _set1.getAll(_associated); +// assertEq(values.length, 1); +// assertEq(SetValue.unwrap(values[0]), SetValue.unwrap(value2)); + +// assertTrue(_set1.tryRemoveKnown(_associated, value2, SENTINEL_VALUE)); +// assertFalse(_set1.contains(_associated, value1)); + +// assertTrue(_set1.isEmpty(_associated)); +// } + +// function test_tryRemoveKnown_invalid1() public { +// SetValue value1 = _getListValue(12); +// SetValue value2 = _getListValue(13); + +// assertTrue(_set1.tryAdd(_associated, value1)); +// assertTrue(_set1.tryAdd(_associated, value2)); + +// assertFalse(_set1.tryRemoveKnown(_associated, value1, bytes32(SetValue.unwrap(value1)))); +// assertTrue(_set1.contains(_associated, value1)); + +// assertFalse(_set1.tryRemoveKnown(_associated, value2, bytes32(SetValue.unwrap(value2)))); +// assertTrue(_set1.contains(_associated, value2)); +// } +// } diff --git a/test/libraries/PluginStorageLib.t.sol b/test/libraries/PluginStorageLib.t.sol index 2dc58e1f..3bd1dc76 100644 --- a/test/libraries/PluginStorageLib.t.sol +++ b/test/libraries/PluginStorageLib.t.sol @@ -1,88 +1,89 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {Test} from "forge-std/Test.sol"; -import {PluginStorageLib, StoragePointer} from "../../src/libraries/PluginStorageLib.sol"; +// import {Test} from "forge-std/Test.sol"; +// import {PluginStorageLib, StoragePointer} from "../../src/libraries/PluginStorageLib.sol"; -contract PluginStorageLibTest is Test { - using PluginStorageLib for bytes; - using PluginStorageLib for bytes32; +// contract PluginStorageLibTest is Test { +// using PluginStorageLib for bytes; +// using PluginStorageLib for bytes32; - uint256 public constant FUZZ_ARR_SIZE = 32; +// uint256 public constant FUZZ_ARR_SIZE = 32; - address public account1; +// address public account1; - struct TestStruct { - uint256 a; - uint256 b; - } +// struct TestStruct { +// uint256 a; +// uint256 b; +// } - function setUp() public { - account1 = makeAddr("account1"); - } +// function setUp() public { +// account1 = makeAddr("account1"); +// } - function test_storagePointer() public { - bytes memory key = PluginStorageLib.allocateAssociatedStorageKey(account1, 0, 1); +// function test_storagePointer() public { +// bytes memory key = PluginStorageLib.allocateAssociatedStorageKey(account1, 0, 1); - StoragePointer ptr = PluginStorageLib.associatedStorageLookup( - key, hex"00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff" - ); - TestStruct storage val = _castPtrToStruct(ptr); +// StoragePointer ptr = PluginStorageLib.associatedStorageLookup( +// key, hex"00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff" +// ); +// TestStruct storage val = _castPtrToStruct(ptr); - vm.record(); - val.a = 0xdeadbeef; - val.b = 123; - (, bytes32[] memory accountWrites) = vm.accesses(address(this)); +// vm.record(); +// val.a = 0xdeadbeef; +// val.b = 123; +// (, bytes32[] memory accountWrites) = vm.accesses(address(this)); - assertEq(accountWrites.length, 2); - bytes32 expectedKey = keccak256( - abi.encodePacked( - uint256(uint160(account1)), - uint256(0), - hex"00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff" - ) - ); - assertEq(accountWrites[0], expectedKey); - assertEq(vm.load(address(this), expectedKey), bytes32(uint256(0xdeadbeef))); - assertEq(accountWrites[1], bytes32(uint256(expectedKey) + 1)); - assertEq(vm.load(address(this), bytes32(uint256(expectedKey) + 1)), bytes32(uint256(123))); - } +// assertEq(accountWrites.length, 2); +// bytes32 expectedKey = keccak256( +// abi.encodePacked( +// uint256(uint160(account1)), +// uint256(0), +// hex"00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff" +// ) +// ); +// assertEq(accountWrites[0], expectedKey); +// assertEq(vm.load(address(this), expectedKey), bytes32(uint256(0xdeadbeef))); +// assertEq(accountWrites[1], bytes32(uint256(expectedKey) + 1)); +// assertEq(vm.load(address(this), bytes32(uint256(expectedKey) + 1)), bytes32(uint256(123))); +// } - function testFuzz_storagePointer( - address account, - uint256 batchIndex, - bytes32 inputKey, - uint256[FUZZ_ARR_SIZE] calldata values - ) public { - bytes memory key = PluginStorageLib.allocateAssociatedStorageKey(account, batchIndex, 1); - uint256[FUZZ_ARR_SIZE] storage val = - _castPtrToArray(PluginStorageLib.associatedStorageLookup(key, inputKey)); - // Write values to storage - vm.record(); - for (uint256 i = 0; i < FUZZ_ARR_SIZE; i++) { - val[i] = values[i]; - } - // Assert the writes took place in the right location, and the correct value is stored there - (, bytes32[] memory accountWrites) = vm.accesses(address(this)); - assertEq(accountWrites.length, FUZZ_ARR_SIZE); - for (uint256 i = 0; i < FUZZ_ARR_SIZE; i++) { - bytes32 expectedKey = bytes32( - uint256(keccak256(abi.encodePacked(uint256(uint160(account)), uint256(batchIndex), inputKey))) + i - ); - assertEq(accountWrites[i], expectedKey); - assertEq(vm.load(address(this), expectedKey), bytes32(uint256(values[i]))); - } - } +// function testFuzz_storagePointer( +// address account, +// uint256 batchIndex, +// bytes32 inputKey, +// uint256[FUZZ_ARR_SIZE] calldata values +// ) public { +// bytes memory key = PluginStorageLib.allocateAssociatedStorageKey(account, batchIndex, 1); +// uint256[FUZZ_ARR_SIZE] storage val = +// _castPtrToArray(PluginStorageLib.associatedStorageLookup(key, inputKey)); +// // Write values to storage +// vm.record(); +// for (uint256 i = 0; i < FUZZ_ARR_SIZE; i++) { +// val[i] = values[i]; +// } +// // Assert the writes took place in the right location, and the correct value is stored there +// (, bytes32[] memory accountWrites) = vm.accesses(address(this)); +// assertEq(accountWrites.length, FUZZ_ARR_SIZE); +// for (uint256 i = 0; i < FUZZ_ARR_SIZE; i++) { +// bytes32 expectedKey = bytes32( +// uint256(keccak256(abi.encodePacked(uint256(uint160(account)), uint256(batchIndex), inputKey))) + +// i +// ); +// assertEq(accountWrites[i], expectedKey); +// assertEq(vm.load(address(this), expectedKey), bytes32(uint256(values[i]))); +// } +// } - function _castPtrToArray(StoragePointer ptr) internal pure returns (uint256[FUZZ_ARR_SIZE] storage val) { - assembly ("memory-safe") { - val.slot := ptr - } - } +// function _castPtrToArray(StoragePointer ptr) internal pure returns (uint256[FUZZ_ARR_SIZE] storage val) { +// assembly ("memory-safe") { +// val.slot := ptr +// } +// } - function _castPtrToStruct(StoragePointer ptr) internal pure returns (TestStruct storage val) { - assembly ("memory-safe") { - val.slot := ptr - } - } -} +// function _castPtrToStruct(StoragePointer ptr) internal pure returns (TestStruct storage val) { +// assembly ("memory-safe") { +// val.slot := ptr +// } +// } +// } diff --git a/test/mocks/plugins/BadTransferOwnershipPlugin.sol b/test/mocks/plugins/BadTransferOwnershipPlugin.sol index 03da8019..9d07687e 100644 --- a/test/mocks/plugins/BadTransferOwnershipPlugin.sol +++ b/test/mocks/plugins/BadTransferOwnershipPlugin.sol @@ -2,17 +2,14 @@ pragma solidity ^0.8.19; import { - ManifestExecutionHook, ManifestFunction, ManifestAssociatedFunctionType, ManifestAssociatedFunction, PluginManifest, PluginMetadata } from "../../../src/interfaces/IPlugin.sol"; -import {IPluginManager} from "../../../src/interfaces/IPluginManager.sol"; import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; import {ISingleOwnerPlugin} from "../../../src/plugins/owner/ISingleOwnerPlugin.sol"; -import {IStandardExecutor} from "../../../src/interfaces/IStandardExecutor.sol"; import {IPluginExecutor} from "../../../src/interfaces/IPluginExecutor.sol"; contract BadTransferOwnershipPlugin is BasePlugin { diff --git a/test/mocks/plugins/ExecFromPluginPermissionsMocks.sol b/test/mocks/plugins/ExecFromPluginPermissionsMocks.sol index 3eea3ac0..663e5f7f 100644 --- a/test/mocks/plugins/ExecFromPluginPermissionsMocks.sol +++ b/test/mocks/plugins/ExecFromPluginPermissionsMocks.sol @@ -1,18 +1,14 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {FunctionReference} from "../../../src/helpers/FunctionReferenceLib.sol"; import { ManifestFunction, ManifestAssociatedFunctionType, ManifestAssociatedFunction, ManifestExternalCallPermission, - ManifestExecutionHook, PluginManifest } from "../../../src/interfaces/IPlugin.sol"; -import {IStandardExecutor} from "../../../src/interfaces/IStandardExecutor.sol"; import {IPluginExecutor} from "../../../src/interfaces/IPluginExecutor.sol"; -import {IPlugin} from "../../../src/interfaces/IPlugin.sol"; import {BaseTestPlugin} from "./BaseTestPlugin.sol"; import {ResultCreatorPlugin} from "./ReturnDataPluginMocks.sol"; diff --git a/test/mocks/plugins/ManifestValidityMocks.sol b/test/mocks/plugins/ManifestValidityMocks.sol index f19c67ca..d2083db6 100644 --- a/test/mocks/plugins/ManifestValidityMocks.sol +++ b/test/mocks/plugins/ManifestValidityMocks.sol @@ -1,21 +1,17 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {FunctionReference} from "../../../src/helpers/FunctionReferenceLib.sol"; import { ManifestFunction, ManifestAssociatedFunctionType, ManifestAssociatedFunction, ManifestExecutionHook, - ManifestExternalCallPermission, PluginManifest } from "../../../src/interfaces/IPlugin.sol"; -import {IStandardExecutor} from "../../../src/interfaces/IStandardExecutor.sol"; -import {IPluginExecutor} from "../../../src/interfaces/IPluginExecutor.sol"; -import {IPlugin} from "../../../src/interfaces/IPlugin.sol"; import {BaseTestPlugin} from "./BaseTestPlugin.sol"; +// solhint-disable-next-line contract-name-camelcase contract BadValidationMagicValue_PreRuntimeValidationHook_Plugin is BaseTestPlugin { function onInstall(bytes calldata) external override {} @@ -56,6 +52,7 @@ contract BadValidationMagicValue_PreRuntimeValidationHook_Plugin is BaseTestPlug } } +// solhint-disable-next-line contract-name-camelcase contract BadValidationMagicValue_PreUserOpValidationHook_Plugin is BaseTestPlugin { function onInstall(bytes calldata) external override {} @@ -96,6 +93,7 @@ contract BadValidationMagicValue_PreUserOpValidationHook_Plugin is BaseTestPlugi } } +// solhint-disable-next-line contract-name-camelcase contract BadValidationMagicValue_PreExecHook_Plugin is BaseTestPlugin { function onInstall(bytes calldata) external override {} @@ -132,6 +130,7 @@ contract BadValidationMagicValue_PreExecHook_Plugin is BaseTestPlugin { } } +// solhint-disable-next-line contract-name-camelcase contract BadValidationMagicValue_PostExecHook_Plugin is BaseTestPlugin { function onInstall(bytes calldata) external override {} @@ -167,6 +166,7 @@ contract BadValidationMagicValue_PostExecHook_Plugin is BaseTestPlugin { } } +// solhint-disable-next-line contract-name-camelcase contract BadHookMagicValue_UserOpValidationFunction_Plugin is BaseTestPlugin { function onInstall(bytes calldata) external override {} @@ -196,6 +196,7 @@ contract BadHookMagicValue_UserOpValidationFunction_Plugin is BaseTestPlugin { } } +// solhint-disable-next-line contract-name-camelcase contract BadHookMagicValue_RuntimeValidationFunction_Plugin is BaseTestPlugin { function onInstall(bytes calldata) external override {} @@ -225,6 +226,7 @@ contract BadHookMagicValue_RuntimeValidationFunction_Plugin is BaseTestPlugin { } } +// solhint-disable-next-line contract-name-camelcase contract BadHookMagicValue_PostExecHook_Plugin is BaseTestPlugin { function onInstall(bytes calldata) external override {} diff --git a/test/mocks/plugins/ReturnDataPluginMocks.sol b/test/mocks/plugins/ReturnDataPluginMocks.sol index 85925c0e..153a4340 100644 --- a/test/mocks/plugins/ReturnDataPluginMocks.sol +++ b/test/mocks/plugins/ReturnDataPluginMocks.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {FunctionReference} from "../../../src/helpers/FunctionReferenceLib.sol"; import { ManifestFunction, ManifestAssociatedFunctionType, @@ -9,9 +8,7 @@ import { ManifestExternalCallPermission, PluginManifest } from "../../../src/interfaces/IPlugin.sol"; -import {IStandardExecutor} from "../../../src/interfaces/IStandardExecutor.sol"; import {IPluginExecutor} from "../../../src/interfaces/IPluginExecutor.sol"; -import {IPlugin} from "../../../src/interfaces/IPlugin.sol"; import {BaseTestPlugin} from "./BaseTestPlugin.sol"; @@ -60,12 +57,12 @@ contract ResultCreatorPlugin is BaseTestPlugin { } contract ResultConsumerPlugin is BaseTestPlugin { - ResultCreatorPlugin public immutable resultCreator; - RegularResultContract public immutable regularResultContract; + ResultCreatorPlugin public immutable RESULT_CREATOR; + RegularResultContract public immutable REGULAR_RESULT_CONTRACT; constructor(ResultCreatorPlugin _resultCreator, RegularResultContract _regularResultContract) { - resultCreator = _resultCreator; - regularResultContract = _regularResultContract; + RESULT_CREATOR = _resultCreator; + REGULAR_RESULT_CONTRACT = _regularResultContract; } // Check the return data through the executeFromPlugin fallback case @@ -147,7 +144,7 @@ contract ResultConsumerPlugin is BaseTestPlugin { bytes4[] memory allowedSelectors = new bytes4[](1); allowedSelectors[0] = RegularResultContract.foo.selector; manifest.permittedExternalCalls[0] = ManifestExternalCallPermission({ - externalAddress: address(regularResultContract), + externalAddress: address(REGULAR_RESULT_CONTRACT), permitAnySelector: false, selectors: allowedSelectors }); diff --git a/test/samples/plugins/ModularSessionKeyPlugin.t.sol b/test/samples/plugins/ModularSessionKeyPlugin.t.sol index e20cbfca..5001e56f 100644 --- a/test/samples/plugins/ModularSessionKeyPlugin.t.sol +++ b/test/samples/plugins/ModularSessionKeyPlugin.t.sol @@ -1,371 +1,375 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {Test} from "forge-std/Test.sol"; - -import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.sol"; -import {UserOperation} from "@eth-infinitism/account-abstraction/interfaces/UserOperation.sol"; -import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; - -import {SingleOwnerPlugin} from "../../../src/plugins/owner/SingleOwnerPlugin.sol"; -import {ISingleOwnerPlugin} from "../../../src/plugins/owner/ISingleOwnerPlugin.sol"; -import {ModularSessionKeyPlugin} from "../../../src/samples/plugins/ModularSessionKeyPlugin.sol"; -import {IModularSessionKeyPlugin} from "../../../src/samples/plugins/interfaces/ISessionKeyPlugin.sol"; -import {TokenSessionKeyPlugin} from "../../../src/samples/plugins/TokenSessionKeyPlugin.sol"; -import {ITokenSessionKeyPlugin} from "../../../src/samples/plugins/interfaces/ITokenSessionKeyPlugin.sol"; - -import {UpgradeableModularAccount} from "../../../src/account/UpgradeableModularAccount.sol"; -import {MSCAFactoryFixture} from "../../mocks/MSCAFactoryFixture.sol"; -import {FunctionReference, FunctionReferenceLib} from "../../../src/helpers/FunctionReferenceLib.sol"; -import {IPluginManager} from "../../../src/interfaces/IPluginManager.sol"; -import {MockERC20} from "../../mocks/MockERC20.sol"; - -contract ModularSessionKeyPluginTest is Test { - using ECDSA for bytes32; - - SingleOwnerPlugin public ownerPlugin; - ModularSessionKeyPlugin public modularSessionKeyPlugin; - TokenSessionKeyPlugin public tokenSessionKeyPlugin; - EntryPoint public entryPoint; - MSCAFactoryFixture public factory; - UpgradeableModularAccount public account; - - MockERC20 public mockERC20impl; - MockERC20 public mockERC20; - address public mockEmptyERC20Addr; - - address public owner; - uint256 public ownerKey; - - address public maliciousOwner; - - address public tempOwner; - uint256 public tempOwnerKey; - - address public target; - - address payable public beneficiary; - - uint256 public constant CALL_GAS_LIMIT = 150000; - uint256 public constant VERIFICATION_GAS_LIMIT = 3600000; - - bytes4 public constant TRANSFERFROM_SESSIONKEY_SELECTOR = - ITokenSessionKeyPlugin.transferFromSessionKey.selector; - - // Event declarations (needed for vm.expectEmit) - event UserOperationRevertReason( - bytes32 indexed userOpHash, address indexed sender, uint256 nonce, bytes revertReason - ); - event SessionKeyAdded( - address indexed account, address indexed sessionKey, bytes4 allowedSelector, uint48 _after, uint48 _until - ); - event SessionKeyRemoved(address indexed account, address indexed sessionKey, bytes4 allowedSelector); - event SessionKeysAdded( - address indexed account, address[] sessionKeys, bytes4[] allowedSelectors, uint48[] afters, uint48[] untils - ); - event SessionKeysRemoved(address indexed account, address[] sessionKeys, bytes4[] allowedSelectors); - event PluginUninstalled(address indexed plugin, bool indexed onUninstallSuccess); - - function setUp() public { - ownerPlugin = new SingleOwnerPlugin(); - modularSessionKeyPlugin = new ModularSessionKeyPlugin(); - tokenSessionKeyPlugin = new TokenSessionKeyPlugin(); - - entryPoint = new EntryPoint(); - factory = new MSCAFactoryFixture(entryPoint, ownerPlugin); - mockERC20impl = new MockERC20("Mock", "MCK"); - - // Etching MockERC20 code into hardcoded address at TokenSessionKeyPlugin - mockEmptyERC20Addr = tokenSessionKeyPlugin.TARGET_ERC20_CONTRACT(); - bytes memory code = address(mockERC20impl).code; - vm.etch(mockEmptyERC20Addr, code); - mockERC20 = MockERC20(mockEmptyERC20Addr); - - (owner, ownerKey) = makeAddrAndKey("owner"); - (maliciousOwner,) = makeAddrAndKey("maliciousOwner"); - (tempOwner, tempOwnerKey) = makeAddrAndKey("tempOwner"); - target = makeAddr("target"); - - beneficiary = payable(makeAddr("beneficiary")); - vm.deal(beneficiary, 1 wei); - vm.deal(owner, 10 ether); - - // Here, SingleOwnerPlugin already installed in factory - account = factory.createAccount(owner, 0); - - // Mint Mock ERC20 Tokens to account - mockERC20.mint(address(account), 1 ether); - // Fund the account with some ether - vm.deal(address(account), 1 ether); - - vm.startPrank(owner); - FunctionReference[] memory modularSessionDependency = new FunctionReference[](1); - modularSessionDependency[0] = FunctionReferenceLib.pack( - address(ownerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER_OR_SELF) - ); - - bytes32 modularSessionKeyManifestHash = keccak256(abi.encode(modularSessionKeyPlugin.pluginManifest())); - - address[] memory tempOwners = new address[](1); - tempOwners[0] = address(tempOwner); - - bytes4[] memory allowedSelectors = new bytes4[](1); - allowedSelectors[0] = TRANSFERFROM_SESSIONKEY_SELECTOR; - - uint48[] memory afters = new uint48[](1); - afters[0] = 0; - - uint48[] memory untils = new uint48[](1); - untils[0] = 2; - - bytes memory data = abi.encode(tempOwners, allowedSelectors, afters, untils); - - account.installPlugin({ - plugin: address(modularSessionKeyPlugin), - manifestHash: modularSessionKeyManifestHash, - pluginInstallData: data, - dependencies: modularSessionDependency - }); - - FunctionReference[] memory tokenSessionDependency = new FunctionReference[](1); - tokenSessionDependency[0] = FunctionReferenceLib.pack( - address(modularSessionKeyPlugin), uint8(IModularSessionKeyPlugin.FunctionId.VALIDATION_TEMPORARY_OWNER) - ); - bytes32 tokenSessionKeyManifestHash = keccak256(abi.encode(tokenSessionKeyPlugin.pluginManifest())); - - account.installPlugin({ - plugin: address(tokenSessionKeyPlugin), - manifestHash: tokenSessionKeyManifestHash, - pluginInstallData: "", - dependencies: tokenSessionDependency - }); - vm.stopPrank(); - - vm.startPrank(address(account)); - mockERC20.approve(address(account), 1 ether); - - (uint48 _after, uint48 _until) = modularSessionKeyPlugin.getSessionDuration( - address(account), tempOwner, TRANSFERFROM_SESSIONKEY_SELECTOR - ); - - assertEq(_after, 0); - assertEq(_until, 2); - vm.stopPrank(); - } - - function test_sessionKey_batch() public { - address tempOwner2 = makeAddr("tempOwner2"); - address tempOwner3 = makeAddr("tempOwner3"); - - address[] memory tempOwners = new address[](2); - tempOwners[0] = tempOwner2; - tempOwners[1] = tempOwner3; - - bytes4[] memory allowedSelectors = new bytes4[](2); - allowedSelectors[0] = TRANSFERFROM_SESSIONKEY_SELECTOR; - allowedSelectors[1] = TRANSFERFROM_SESSIONKEY_SELECTOR; - - uint48[] memory afters = new uint48[](2); - afters[0] = 0; - afters[1] = 0; - - uint48[] memory untils = new uint48[](2); - untils[0] = 2; - untils[1] = 2; - - vm.expectEmit(true, true, true, true); - emit SessionKeysAdded(address(account), tempOwners, allowedSelectors, afters, untils); - vm.prank(address(account)); - modularSessionKeyPlugin.addSessionKeyBatch(tempOwners, allowedSelectors, afters, untils); - - vm.prank(tempOwner3); - TokenSessionKeyPlugin(address(account)).transferFromSessionKey( - address(mockERC20), address(account), target, 1 ether - ); - - assertEq(mockERC20.balanceOf(address(account)), 0); - assertEq(mockERC20.balanceOf(target), 1 ether); - - vm.expectEmit(true, true, true, true); - emit SessionKeysRemoved(address(account), tempOwners, allowedSelectors); - vm.prank(address(account)); - modularSessionKeyPlugin.removeSessionKeyBatch(tempOwners, allowedSelectors); - } - - function test_sessionKey_userOp() public { - UserOperation[] memory userOps = new UserOperation[](1); - - (, UserOperation memory userOp) = _constructUserOp(address(mockERC20), address(account), target, 1 ether); - userOps[0] = userOp; - - entryPoint.handleOps(userOps, beneficiary); - - assertEq(mockERC20.balanceOf(address(account)), 0); - assertEq(mockERC20.balanceOf(target), 1 ether); - } - - function test_sessionKey_runtime() public { - vm.prank(address(tempOwner)); - TokenSessionKeyPlugin(address(account)).transferFromSessionKey( - address(mockERC20), address(account), target, 1 ether - ); - - assertEq(mockERC20.balanceOf(address(account)), 0); - assertEq(mockERC20.balanceOf(target), 1 ether); - } - - function test_sessionKey_removeTempOwner() public { - vm.startPrank(address(account)); - - vm.expectEmit(true, true, true, true); - emit SessionKeyRemoved(address(account), tempOwner, TRANSFERFROM_SESSIONKEY_SELECTOR); - modularSessionKeyPlugin.removeSessionKey(tempOwner, TRANSFERFROM_SESSIONKEY_SELECTOR); - - vm.stopPrank(); - - (uint48 _after, uint48 _until) = modularSessionKeyPlugin.getSessionDuration( - address(account), tempOwner, TRANSFERFROM_SESSIONKEY_SELECTOR - ); - assertEq(_after, 0); - assertEq(_until, 0); - - // Check if tempOwner can still send user operations - vm.startPrank(address(tempOwner)); - - bytes memory revertReason = abi.encodeWithSelector(IModularSessionKeyPlugin.NotAuthorized.selector); - vm.expectRevert( - abi.encodeWithSelector( - UpgradeableModularAccount.RuntimeValidationFunctionReverted.selector, - address(modularSessionKeyPlugin), - IModularSessionKeyPlugin.FunctionId.VALIDATION_TEMPORARY_OWNER, - revertReason - ) - ); - TokenSessionKeyPlugin(address(account)).transferFromSessionKey( - address(mockERC20), address(account), target, 1 ether - ); - } - - function test_sessionKey_invalidContractFails() public { - address wrongERC20Contract = makeAddr("wrongERC20Contract"); - (bytes32 userOpHash, UserOperation memory userOp) = - _constructUserOp(address(wrongERC20Contract), address(account), target, 1 ether); - - UserOperation[] memory userOps = new UserOperation[](1); - userOps[0] = userOp; - - bytes memory revertCallData = abi.encodeWithSelector( - tokenSessionKeyPlugin.TRANSFERFROM_SELECTOR(), address(account), target, 1 ether - ); - bytes memory revertReason = abi.encodeWithSelector( - UpgradeableModularAccount.ExecFromPluginExternalNotPermitted.selector, - address(tokenSessionKeyPlugin), - address(wrongERC20Contract), - 0, - revertCallData - ); - vm.expectEmit(true, true, true, true); - emit UserOperationRevertReason(userOpHash, address(account), 0, revertReason); - - entryPoint.handleOps(userOps, beneficiary); - } - - function test_sessionKey_unregisteredTempOwnerFails() public { - vm.prank(address(maliciousOwner)); - bytes memory revertReason = abi.encodeWithSelector(IModularSessionKeyPlugin.NotAuthorized.selector); - - vm.expectRevert( - abi.encodeWithSelector( - UpgradeableModularAccount.RuntimeValidationFunctionReverted.selector, - address(modularSessionKeyPlugin), - IModularSessionKeyPlugin.FunctionId.VALIDATION_TEMPORARY_OWNER, - revertReason - ) - ); - TokenSessionKeyPlugin(address(account)).transferFromSessionKey( - address(mockERC20), address(account), target, 1 ether - ); - } - - function test_sessionKey_invalidSessionDurationFails() public { - // Move block.timestamp to 12345 - vm.warp(12345); - - vm.startPrank(address(tempOwner)); - - bytes memory revertReason = - abi.encodeWithSelector(IModularSessionKeyPlugin.WrongTimeRangeForSession.selector); - - vm.expectRevert( - abi.encodeWithSelector( - UpgradeableModularAccount.RuntimeValidationFunctionReverted.selector, - address(modularSessionKeyPlugin), - IModularSessionKeyPlugin.FunctionId.VALIDATION_TEMPORARY_OWNER, - revertReason - ) - ); - TokenSessionKeyPlugin(address(account)).transferFromSessionKey( - address(mockERC20), address(account), target, 1 ether - ); - } - - function test_sessionKey_uninstallModularSessionKeyPlugin() public { - address[] memory tempOwners = new address[](1); - tempOwners[0] = address(tempOwner); - - bytes4[] memory allowedSelectors = new bytes4[](1); - allowedSelectors[0] = TRANSFERFROM_SESSIONKEY_SELECTOR; - - vm.startPrank(owner); - - vm.expectEmit(true, true, true, true); - - emit PluginUninstalled(address(tokenSessionKeyPlugin), true); - account.uninstallPlugin({ - plugin: address(tokenSessionKeyPlugin), - config: bytes(""), - pluginUninstallData: "" - }); - - vm.expectEmit(true, true, true, true); - emit PluginUninstalled(address(modularSessionKeyPlugin), true); - account.uninstallPlugin({ - plugin: address(modularSessionKeyPlugin), - config: bytes(""), - pluginUninstallData: "" - }); - - vm.stopPrank(); - } - - // Internal Function - function _constructUserOp(address targetContract, address from, address to, uint256 amount) - internal - view - returns (bytes32, UserOperation memory) - { - bytes memory userOpCallData = - abi.encodeCall(TokenSessionKeyPlugin.transferFromSessionKey, (targetContract, from, to, amount)); - - UserOperation memory userOp = UserOperation({ - sender: address(account), - nonce: 0, - initCode: "", - callData: userOpCallData, - callGasLimit: CALL_GAS_LIMIT, - verificationGasLimit: VERIFICATION_GAS_LIMIT, - preVerificationGas: 0, - maxFeePerGas: 2, - maxPriorityFeePerGas: 1, - paymasterAndData: "", - signature: "" - }); - - // Generate signature - bytes32 userOpHash = entryPoint.getUserOpHash(userOp); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(tempOwnerKey, userOpHash.toEthSignedMessageHash()); - userOp.signature = abi.encodePacked(r, s, v); - - return (userOpHash, userOp); - } -} +// import {Test} from "forge-std/Test.sol"; + +// import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.sol"; +// import {UserOperation} from "@eth-infinitism/account-abstraction/interfaces/UserOperation.sol"; +// import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; + +// import {SingleOwnerPlugin} from "../../../src/plugins/owner/SingleOwnerPlugin.sol"; +// import {ISingleOwnerPlugin} from "../../../src/plugins/owner/ISingleOwnerPlugin.sol"; +// import {ModularSessionKeyPlugin} from "../../../src/samples/plugins/ModularSessionKeyPlugin.sol"; +// import {IModularSessionKeyPlugin} from "../../../src/samples/plugins/interfaces/ISessionKeyPlugin.sol"; +// import {TokenSessionKeyPlugin} from "../../../src/samples/plugins/TokenSessionKeyPlugin.sol"; +// import {ITokenSessionKeyPlugin} from "../../../src/samples/plugins/interfaces/ITokenSessionKeyPlugin.sol"; + +// import {UpgradeableModularAccount} from "../../../src/account/UpgradeableModularAccount.sol"; +// import {MSCAFactoryFixture} from "../../mocks/MSCAFactoryFixture.sol"; +// import {FunctionReference, FunctionReferenceLib} from "../../../src/helpers/FunctionReferenceLib.sol"; +// import {IPluginManager} from "../../../src/interfaces/IPluginManager.sol"; +// import {MockERC20} from "../../mocks/MockERC20.sol"; + +// contract ModularSessionKeyPluginTest is Test { +// using ECDSA for bytes32; + +// SingleOwnerPlugin public ownerPlugin; +// ModularSessionKeyPlugin public modularSessionKeyPlugin; +// TokenSessionKeyPlugin public tokenSessionKeyPlugin; +// EntryPoint public entryPoint; +// MSCAFactoryFixture public factory; +// UpgradeableModularAccount public account; + +// MockERC20 public mockERC20impl; +// MockERC20 public mockERC20; +// address public mockEmptyERC20Addr; + +// address public owner; +// uint256 public ownerKey; + +// address public maliciousOwner; + +// address public tempOwner; +// uint256 public tempOwnerKey; + +// address public target; + +// address payable public beneficiary; + +// uint256 public constant CALL_GAS_LIMIT = 150000; +// uint256 public constant VERIFICATION_GAS_LIMIT = 3600000; + +// bytes4 public constant TRANSFERFROM_SESSIONKEY_SELECTOR = +// ITokenSessionKeyPlugin.transferFromSessionKey.selector; + +// // Event declarations (needed for vm.expectEmit) +// event UserOperationRevertReason( +// bytes32 indexed userOpHash, address indexed sender, uint256 nonce, bytes revertReason +// ); +// event SessionKeyAdded( +// address indexed account, address indexed sessionKey, bytes4 allowedSelector, uint48 _after, uint48 +// _until +// ); +// event SessionKeyRemoved(address indexed account, address indexed sessionKey, bytes4 allowedSelector); +// event SessionKeysAdded( +// address indexed account, address[] sessionKeys, bytes4[] allowedSelectors, uint48[] afters, uint48[] +// untils +// ); +// event SessionKeysRemoved(address indexed account, address[] sessionKeys, bytes4[] allowedSelectors); +// event PluginUninstalled(address indexed plugin, bool indexed onUninstallSuccess); + +// function setUp() public { +// ownerPlugin = new SingleOwnerPlugin(); +// modularSessionKeyPlugin = new ModularSessionKeyPlugin(); +// tokenSessionKeyPlugin = new TokenSessionKeyPlugin(); + +// entryPoint = new EntryPoint(); +// factory = new MSCAFactoryFixture(entryPoint, ownerPlugin); +// mockERC20impl = new MockERC20("Mock", "MCK"); + +// // Etching MockERC20 code into hardcoded address at TokenSessionKeyPlugin +// mockEmptyERC20Addr = tokenSessionKeyPlugin.TARGET_ERC20_CONTRACT(); +// bytes memory code = address(mockERC20impl).code; +// vm.etch(mockEmptyERC20Addr, code); +// mockERC20 = MockERC20(mockEmptyERC20Addr); + +// (owner, ownerKey) = makeAddrAndKey("owner"); +// (maliciousOwner,) = makeAddrAndKey("maliciousOwner"); +// (tempOwner, tempOwnerKey) = makeAddrAndKey("tempOwner"); +// target = makeAddr("target"); + +// beneficiary = payable(makeAddr("beneficiary")); +// vm.deal(beneficiary, 1 wei); +// vm.deal(owner, 10 ether); + +// // Here, SingleOwnerPlugin already installed in factory +// account = factory.createAccount(owner, 0); + +// // Mint Mock ERC20 Tokens to account +// mockERC20.mint(address(account), 1 ether); +// // Fund the account with some ether +// vm.deal(address(account), 1 ether); + +// vm.startPrank(owner); +// FunctionReference[] memory modularSessionDependency = new FunctionReference[](1); +// modularSessionDependency[0] = FunctionReferenceLib.pack( +// address(ownerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER_OR_SELF) +// ); + +// bytes32 modularSessionKeyManifestHash = keccak256(abi.encode(modularSessionKeyPlugin.pluginManifest())); + +// address[] memory tempOwners = new address[](1); +// tempOwners[0] = address(tempOwner); + +// bytes4[] memory allowedSelectors = new bytes4[](1); +// allowedSelectors[0] = TRANSFERFROM_SESSIONKEY_SELECTOR; + +// uint48[] memory afters = new uint48[](1); +// afters[0] = 0; + +// uint48[] memory untils = new uint48[](1); +// untils[0] = 2; + +// bytes memory data = abi.encode(tempOwners, allowedSelectors, afters, untils); + +// account.installPlugin({ +// plugin: address(modularSessionKeyPlugin), +// manifestHash: modularSessionKeyManifestHash, +// pluginInstallData: data, +// dependencies: modularSessionDependency +// }); + +// FunctionReference[] memory tokenSessionDependency = new FunctionReference[](1); +// tokenSessionDependency[0] = FunctionReferenceLib.pack( +// address(modularSessionKeyPlugin), +// uint8(IModularSessionKeyPlugin.FunctionId.VALIDATION_TEMPORARY_OWNER) +// ); +// bytes32 tokenSessionKeyManifestHash = keccak256(abi.encode(tokenSessionKeyPlugin.pluginManifest())); + +// account.installPlugin({ +// plugin: address(tokenSessionKeyPlugin), +// manifestHash: tokenSessionKeyManifestHash, +// pluginInstallData: "", +// dependencies: tokenSessionDependency +// }); +// vm.stopPrank(); + +// vm.startPrank(address(account)); +// mockERC20.approve(address(account), 1 ether); + +// (uint48 _after, uint48 _until) = modularSessionKeyPlugin.getSessionDuration( +// address(account), tempOwner, TRANSFERFROM_SESSIONKEY_SELECTOR +// ); + +// assertEq(_after, 0); +// assertEq(_until, 2); +// vm.stopPrank(); +// } + +// function test_sessionKey_batch() public { +// address tempOwner2 = makeAddr("tempOwner2"); +// address tempOwner3 = makeAddr("tempOwner3"); + +// address[] memory tempOwners = new address[](2); +// tempOwners[0] = tempOwner2; +// tempOwners[1] = tempOwner3; + +// bytes4[] memory allowedSelectors = new bytes4[](2); +// allowedSelectors[0] = TRANSFERFROM_SESSIONKEY_SELECTOR; +// allowedSelectors[1] = TRANSFERFROM_SESSIONKEY_SELECTOR; + +// uint48[] memory afters = new uint48[](2); +// afters[0] = 0; +// afters[1] = 0; + +// uint48[] memory untils = new uint48[](2); +// untils[0] = 2; +// untils[1] = 2; + +// vm.expectEmit(true, true, true, true); +// emit SessionKeysAdded(address(account), tempOwners, allowedSelectors, afters, untils); +// vm.prank(address(account)); +// modularSessionKeyPlugin.addSessionKeyBatch(tempOwners, allowedSelectors, afters, untils); + +// vm.prank(tempOwner3); +// TokenSessionKeyPlugin(address(account)).transferFromSessionKey( +// address(mockERC20), address(account), target, 1 ether +// ); + +// assertEq(mockERC20.balanceOf(address(account)), 0); +// assertEq(mockERC20.balanceOf(target), 1 ether); + +// vm.expectEmit(true, true, true, true); +// emit SessionKeysRemoved(address(account), tempOwners, allowedSelectors); +// vm.prank(address(account)); +// modularSessionKeyPlugin.removeSessionKeyBatch(tempOwners, allowedSelectors); +// } + +// function test_sessionKey_userOp() public { +// UserOperation[] memory userOps = new UserOperation[](1); + +// (, UserOperation memory userOp) = _constructUserOp(address(mockERC20), address(account), target, 1 +// ether); +// userOps[0] = userOp; + +// entryPoint.handleOps(userOps, beneficiary); + +// assertEq(mockERC20.balanceOf(address(account)), 0); +// assertEq(mockERC20.balanceOf(target), 1 ether); +// } + +// function test_sessionKey_runtime() public { +// vm.prank(address(tempOwner)); +// TokenSessionKeyPlugin(address(account)).transferFromSessionKey( +// address(mockERC20), address(account), target, 1 ether +// ); + +// assertEq(mockERC20.balanceOf(address(account)), 0); +// assertEq(mockERC20.balanceOf(target), 1 ether); +// } + +// function test_sessionKey_removeTempOwner() public { +// vm.startPrank(address(account)); + +// vm.expectEmit(true, true, true, true); +// emit SessionKeyRemoved(address(account), tempOwner, TRANSFERFROM_SESSIONKEY_SELECTOR); +// modularSessionKeyPlugin.removeSessionKey(tempOwner, TRANSFERFROM_SESSIONKEY_SELECTOR); + +// vm.stopPrank(); + +// (uint48 _after, uint48 _until) = modularSessionKeyPlugin.getSessionDuration( +// address(account), tempOwner, TRANSFERFROM_SESSIONKEY_SELECTOR +// ); +// assertEq(_after, 0); +// assertEq(_until, 0); + +// // Check if tempOwner can still send user operations +// vm.startPrank(address(tempOwner)); + +// bytes memory revertReason = abi.encodeWithSelector(IModularSessionKeyPlugin.NotAuthorized.selector); +// vm.expectRevert( +// abi.encodeWithSelector( +// UpgradeableModularAccount.RuntimeValidationFunctionReverted.selector, +// address(modularSessionKeyPlugin), +// IModularSessionKeyPlugin.FunctionId.VALIDATION_TEMPORARY_OWNER, +// revertReason +// ) +// ); +// TokenSessionKeyPlugin(address(account)).transferFromSessionKey( +// address(mockERC20), address(account), target, 1 ether +// ); +// } + +// function test_sessionKey_invalidContractFails() public { +// address wrongERC20Contract = makeAddr("wrongERC20Contract"); +// (bytes32 userOpHash, UserOperation memory userOp) = +// _constructUserOp(address(wrongERC20Contract), address(account), target, 1 ether); + +// UserOperation[] memory userOps = new UserOperation[](1); +// userOps[0] = userOp; + +// bytes memory revertCallData = abi.encodeWithSelector( +// tokenSessionKeyPlugin.TRANSFERFROM_SELECTOR(), address(account), target, 1 ether +// ); +// bytes memory revertReason = abi.encodeWithSelector( +// UpgradeableModularAccount.ExecFromPluginExternalNotPermitted.selector, +// address(tokenSessionKeyPlugin), +// address(wrongERC20Contract), +// 0, +// revertCallData +// ); +// vm.expectEmit(true, true, true, true); +// emit UserOperationRevertReason(userOpHash, address(account), 0, revertReason); + +// entryPoint.handleOps(userOps, beneficiary); +// } + +// function test_sessionKey_unregisteredTempOwnerFails() public { +// vm.prank(address(maliciousOwner)); +// bytes memory revertReason = abi.encodeWithSelector(IModularSessionKeyPlugin.NotAuthorized.selector); + +// vm.expectRevert( +// abi.encodeWithSelector( +// UpgradeableModularAccount.RuntimeValidationFunctionReverted.selector, +// address(modularSessionKeyPlugin), +// IModularSessionKeyPlugin.FunctionId.VALIDATION_TEMPORARY_OWNER, +// revertReason +// ) +// ); +// TokenSessionKeyPlugin(address(account)).transferFromSessionKey( +// address(mockERC20), address(account), target, 1 ether +// ); +// } + +// function test_sessionKey_invalidSessionDurationFails() public { +// // Move block.timestamp to 12345 +// vm.warp(12345); + +// vm.startPrank(address(tempOwner)); + +// bytes memory revertReason = +// abi.encodeWithSelector(IModularSessionKeyPlugin.WrongTimeRangeForSession.selector); + +// vm.expectRevert( +// abi.encodeWithSelector( +// UpgradeableModularAccount.RuntimeValidationFunctionReverted.selector, +// address(modularSessionKeyPlugin), +// IModularSessionKeyPlugin.FunctionId.VALIDATION_TEMPORARY_OWNER, +// revertReason +// ) +// ); +// TokenSessionKeyPlugin(address(account)).transferFromSessionKey( +// address(mockERC20), address(account), target, 1 ether +// ); +// } + +// function test_sessionKey_uninstallModularSessionKeyPlugin() public { +// address[] memory tempOwners = new address[](1); +// tempOwners[0] = address(tempOwner); + +// bytes4[] memory allowedSelectors = new bytes4[](1); +// allowedSelectors[0] = TRANSFERFROM_SESSIONKEY_SELECTOR; + +// vm.startPrank(owner); + +// vm.expectEmit(true, true, true, true); + +// emit PluginUninstalled(address(tokenSessionKeyPlugin), true); +// account.uninstallPlugin({ +// plugin: address(tokenSessionKeyPlugin), +// config: bytes(""), +// pluginUninstallData: "" +// }); + +// vm.expectEmit(true, true, true, true); +// emit PluginUninstalled(address(modularSessionKeyPlugin), true); +// account.uninstallPlugin({ +// plugin: address(modularSessionKeyPlugin), +// config: bytes(""), +// pluginUninstallData: "" +// }); + +// vm.stopPrank(); +// } + +// // Internal Function +// function _constructUserOp(address targetContract, address from, address to, uint256 amount) +// internal +// view +// returns (bytes32, UserOperation memory) +// { +// bytes memory userOpCallData = +// abi.encodeCall(TokenSessionKeyPlugin.transferFromSessionKey, (targetContract, from, to, amount)); + +// UserOperation memory userOp = UserOperation({ +// sender: address(account), +// nonce: 0, +// initCode: "", +// callData: userOpCallData, +// callGasLimit: CALL_GAS_LIMIT, +// verificationGasLimit: VERIFICATION_GAS_LIMIT, +// preVerificationGas: 0, +// maxFeePerGas: 2, +// maxPriorityFeePerGas: 1, +// paymasterAndData: "", +// signature: "" +// }); + +// // Generate signature +// bytes32 userOpHash = entryPoint.getUserOpHash(userOp); +// (uint8 v, bytes32 r, bytes32 s) = vm.sign(tempOwnerKey, userOpHash.toEthSignedMessageHash()); +// userOp.signature = abi.encodePacked(r, s, v); + +// return (userOpHash, userOp); +// } +// } From a66dcd8e037ba82fc8648e53aed8cb33f8342c3c Mon Sep 17 00:00:00 2001 From: adam-alchemy <127769144+adam-alchemy@users.noreply.github.com> Date: Wed, 17 Apr 2024 12:30:56 -0700 Subject: [PATCH 004/111] refactor: [v0.8-develop] account test base (#44) --- test/account/AccountExecHooks.t.sol | 46 ++---- test/account/AccountLoupe.t.sol | 19 +-- test/account/AccountReturnData.t.sol | 37 ++--- .../ExecuteFromPluginPermissions.t.sol | 70 ++++----- test/account/ManifestValidity.t.sol | 38 ++--- test/account/UpgradeableModularAccount.t.sol | 134 ++++++++---------- test/account/ValidationIntersection.t.sol | 21 +-- .../ExecFromPluginPermissionsMocks.sol | 35 ++++- test/utils/AccountTestBase.sol | 42 ++++++ 9 files changed, 191 insertions(+), 251 deletions(-) create mode 100644 test/utils/AccountTestBase.sol diff --git a/test/account/AccountExecHooks.t.sol b/test/account/AccountExecHooks.t.sol index bf432e03..db5df7eb 100644 --- a/test/account/AccountExecHooks.t.sol +++ b/test/account/AccountExecHooks.t.sol @@ -1,9 +1,6 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.sol"; - import { IPlugin, ManifestAssociatedFunctionType, @@ -12,24 +9,13 @@ import { ManifestFunction, PluginManifest } from "../../src/interfaces/IPlugin.sol"; -import {SingleOwnerPlugin} from "../../src/plugins/owner/SingleOwnerPlugin.sol"; import {PluginManagerInternals} from "../../src/account/PluginManagerInternals.sol"; -import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; import {MockPlugin} from "../mocks/MockPlugin.sol"; -import {MSCAFactoryFixture} from "../mocks/MSCAFactoryFixture.sol"; -import {OptimizedTest} from "../utils/OptimizedTest.sol"; - -contract AccountExecHooksTest is OptimizedTest { - using ECDSA for bytes32; - - EntryPoint public entryPoint; - SingleOwnerPlugin public singleOwnerPlugin; - MSCAFactoryFixture public factory; - - UpgradeableModularAccount public account; +import {AccountTestBase} from "../utils/AccountTestBase.sol"; +contract AccountExecHooksTest is AccountTestBase { MockPlugin public mockPlugin1; MockPlugin public mockPlugin2; bytes32 public manifestHash1; @@ -50,13 +36,7 @@ contract AccountExecHooksTest is OptimizedTest { event ReceivedCall(bytes msgData, uint256 msgValue); function setUp() public { - entryPoint = new EntryPoint(); - singleOwnerPlugin = _deploySingleOwnerPlugin(); - factory = new MSCAFactoryFixture(entryPoint, singleOwnerPlugin); - - // Create an account with "this" as the owner, so we can execute along the runtime path with regular - // solidity semantics - account = factory.createAccount(address(this), 0); + _transferOwnershipToTest(); m1.executionFunctions.push(_EXEC_SELECTOR); @@ -101,7 +81,7 @@ contract AccountExecHooksTest is OptimizedTest { 0 // msg value in call to plugin ); - (bool success,) = address(account).call(abi.encodeWithSelector(_EXEC_SELECTOR)); + (bool success,) = address(account1).call(abi.encodeWithSelector(_EXEC_SELECTOR)); assertTrue(success); } @@ -154,7 +134,7 @@ contract AccountExecHooksTest is OptimizedTest { 0 // msg value in call to plugin ); - (bool success,) = address(account).call(abi.encodeWithSelector(_EXEC_SELECTOR)); + (bool success,) = address(account1).call(abi.encodeWithSelector(_EXEC_SELECTOR)); assertTrue(success); } @@ -187,7 +167,7 @@ contract AccountExecHooksTest is OptimizedTest { 0 // msg value in call to plugin ); - (bool success,) = address(account).call(abi.encodeWithSelector(_EXEC_SELECTOR)); + (bool success,) = address(account1).call(abi.encodeWithSelector(_EXEC_SELECTOR)); assertTrue(success); } @@ -225,7 +205,7 @@ contract AccountExecHooksTest is OptimizedTest { } function test_overlappingPreExecHooks_run() public { - (bool success,) = address(account).call(abi.encodeWithSelector(_EXEC_SELECTOR)); + (bool success,) = address(account1).call(abi.encodeWithSelector(_EXEC_SELECTOR)); assertFalse(success); } @@ -236,14 +216,14 @@ contract AccountExecHooksTest is OptimizedTest { _uninstallPlugin(mockPlugin2); // Expect the pre hook to still exist. - (bool success,) = address(account).call(abi.encodeWithSelector(_EXEC_SELECTOR)); + (bool success,) = address(account1).call(abi.encodeWithSelector(_EXEC_SELECTOR)); assertFalse(success); // Uninstall the first plugin. _uninstallPlugin(mockPlugin1); // Execution selector should no longer exist. - (success,) = address(account).call(abi.encodeWithSelector(_EXEC_SELECTOR)); + (success,) = address(account1).call(abi.encodeWithSelector(_EXEC_SELECTOR)); assertFalse(success); } @@ -302,7 +282,7 @@ contract AccountExecHooksTest is OptimizedTest { vm.expectEmit(true, true, true, true); emit PluginInstalled(address(mockPlugin1), manifestHash1, new FunctionReference[](0)); - account.installPlugin({ + account1.installPlugin({ plugin: address(mockPlugin1), manifestHash: manifestHash1, pluginInstallData: bytes(""), @@ -333,7 +313,7 @@ contract AccountExecHooksTest is OptimizedTest { vm.expectEmit(true, true, true, true); emit PluginInstalled(address(mockPlugin2), manifestHash2, dependencies); - account.installPlugin({ + account1.installPlugin({ plugin: address(mockPlugin2), manifestHash: manifestHash2, pluginInstallData: bytes(""), @@ -361,7 +341,7 @@ contract AccountExecHooksTest is OptimizedTest { manifestHash2 = keccak256(abi.encode(mockPlugin2.pluginManifest())); vm.expectRevert(revertData); - account.installPlugin({ + account1.installPlugin({ plugin: address(mockPlugin2), manifestHash: manifestHash2, pluginInstallData: bytes(""), @@ -375,6 +355,6 @@ contract AccountExecHooksTest is OptimizedTest { vm.expectEmit(true, true, true, true); emit PluginUninstalled(address(plugin), true); - account.uninstallPlugin(address(plugin), bytes(""), bytes("")); + account1.uninstallPlugin(address(plugin), bytes(""), bytes("")); } } diff --git a/test/account/AccountLoupe.t.sol b/test/account/AccountLoupe.t.sol index 35c7b6e2..c487a39e 100644 --- a/test/account/AccountLoupe.t.sol +++ b/test/account/AccountLoupe.t.sol @@ -2,9 +2,7 @@ pragma solidity ^0.8.19; import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; -import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.sol"; -import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; import { ManifestAssociatedFunctionType, @@ -16,34 +14,23 @@ import {IAccountLoupe} from "../../src/interfaces/IAccountLoupe.sol"; import {IPluginManager} from "../../src/interfaces/IPluginManager.sol"; import {IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; import {ISingleOwnerPlugin} from "../../src/plugins/owner/ISingleOwnerPlugin.sol"; -import {SingleOwnerPlugin} from "../../src/plugins/owner/SingleOwnerPlugin.sol"; -import {MSCAFactoryFixture} from "../mocks/MSCAFactoryFixture.sol"; import {ComprehensivePlugin} from "../mocks/plugins/ComprehensivePlugin.sol"; import {MockPlugin} from "../mocks/MockPlugin.sol"; -import {OptimizedTest} from "../utils/OptimizedTest.sol"; +import {AccountTestBase} from "../utils/AccountTestBase.sol"; -contract AccountLoupeTest is OptimizedTest { - EntryPoint public entryPoint; - SingleOwnerPlugin public singleOwnerPlugin; - MSCAFactoryFixture public factory; +contract AccountLoupeTest is AccountTestBase { ComprehensivePlugin public comprehensivePlugin; - UpgradeableModularAccount public account1; - FunctionReference public ownerValidation; event ReceivedCall(bytes msgData, uint256 msgValue); function setUp() public { - entryPoint = new EntryPoint(); + _transferOwnershipToTest(); - singleOwnerPlugin = _deploySingleOwnerPlugin(); - factory = new MSCAFactoryFixture(entryPoint, singleOwnerPlugin); comprehensivePlugin = new ComprehensivePlugin(); - account1 = factory.createAccount(address(this), 0); - bytes32 manifestHash = keccak256(abi.encode(comprehensivePlugin.pluginManifest())); account1.installPlugin(address(comprehensivePlugin), manifestHash, "", new FunctionReference[](0)); diff --git a/test/account/AccountReturnData.t.sol b/test/account/AccountReturnData.t.sol index 2837b904..46463ed1 100644 --- a/test/account/AccountReturnData.t.sol +++ b/test/account/AccountReturnData.t.sol @@ -1,49 +1,32 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.sol"; - -import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {FunctionReference} from "../../src/helpers/FunctionReferenceLib.sol"; import {Call} from "../../src/interfaces/IStandardExecutor.sol"; -import {SingleOwnerPlugin} from "../../src/plugins/owner/SingleOwnerPlugin.sol"; import { RegularResultContract, ResultCreatorPlugin, ResultConsumerPlugin } from "../mocks/plugins/ReturnDataPluginMocks.sol"; -import {MSCAFactoryFixture} from "../mocks/MSCAFactoryFixture.sol"; -import {OptimizedTest} from "../utils/OptimizedTest.sol"; +import {AccountTestBase} from "../utils/AccountTestBase.sol"; // Tests all the different ways that return data can be read from plugins through an account -contract AccountReturnDataTest is OptimizedTest { - EntryPoint public entryPoint; // Just to be able to construct the factory - SingleOwnerPlugin public singleOwnerPlugin; - MSCAFactoryFixture public factory; - +contract AccountReturnDataTest is AccountTestBase { RegularResultContract public regularResultContract; ResultCreatorPlugin public resultCreatorPlugin; ResultConsumerPlugin public resultConsumerPlugin; - UpgradeableModularAccount public account; - function setUp() public { - entryPoint = new EntryPoint(); - singleOwnerPlugin = _deploySingleOwnerPlugin(); - factory = new MSCAFactoryFixture(entryPoint, singleOwnerPlugin); + _transferOwnershipToTest(); regularResultContract = new RegularResultContract(); resultCreatorPlugin = new ResultCreatorPlugin(); resultConsumerPlugin = new ResultConsumerPlugin(resultCreatorPlugin, regularResultContract); - // Create an account with "this" as the owner, so we can execute along the runtime path with regular - // solidity semantics - account = factory.createAccount(address(this), 0); - // Add the result creator plugin to the account bytes32 resultCreatorManifestHash = keccak256(abi.encode(resultCreatorPlugin.pluginManifest())); - account.installPlugin({ + account1.installPlugin({ plugin: address(resultCreatorPlugin), manifestHash: resultCreatorManifestHash, pluginInstallData: "", @@ -51,7 +34,7 @@ contract AccountReturnDataTest is OptimizedTest { }); // Add the result consumer plugin to the account bytes32 resultConsumerManifestHash = keccak256(abi.encode(resultConsumerPlugin.pluginManifest())); - account.installPlugin({ + account1.installPlugin({ plugin: address(resultConsumerPlugin), manifestHash: resultConsumerManifestHash, pluginInstallData: "", @@ -61,7 +44,7 @@ contract AccountReturnDataTest is OptimizedTest { // Tests the ability to read the result of plugin execution functions via the account's fallback function test_returnData_fallback() public { - bytes32 result = ResultCreatorPlugin(address(account)).foo(); + bytes32 result = ResultCreatorPlugin(address(account1)).foo(); assertEq(result, keccak256("bar")); } @@ -69,7 +52,7 @@ contract AccountReturnDataTest is OptimizedTest { // Tests the ability to read the results of contracts called via IStandardExecutor.execute function test_returnData_singular_execute() public { bytes memory returnData = - account.execute(address(regularResultContract), 0, abi.encodeCall(RegularResultContract.foo, ())); + account1.execute(address(regularResultContract), 0, abi.encodeCall(RegularResultContract.foo, ())); bytes32 result = abi.decode(returnData, (bytes32)); @@ -90,7 +73,7 @@ contract AccountReturnDataTest is OptimizedTest { data: abi.encodeCall(RegularResultContract.bar, ()) }); - bytes[] memory returnDatas = account.executeBatch(calls); + bytes[] memory returnDatas = account1.executeBatch(calls); bytes32 result1 = abi.decode(returnDatas[0], (bytes32)); bytes32 result2 = abi.decode(returnDatas[1], (bytes32)); @@ -101,14 +84,14 @@ contract AccountReturnDataTest is OptimizedTest { // Tests the ability to read data via executeFromPlugin routing to fallback functions function test_returnData_execFromPlugin_fallback() public { - bool result = ResultConsumerPlugin(address(account)).checkResultEFPFallback(keccak256("bar")); + bool result = ResultConsumerPlugin(address(account1)).checkResultEFPFallback(keccak256("bar")); assertTrue(result); } // Tests the ability to read data via executeFromPluginExternal function test_returnData_execFromPlugin_execute() public { - bool result = ResultConsumerPlugin(address(account)).checkResultEFPExternal( + bool result = ResultConsumerPlugin(address(account1)).checkResultEFPExternal( address(regularResultContract), keccak256("bar") ); diff --git a/test/account/ExecuteFromPluginPermissions.t.sol b/test/account/ExecuteFromPluginPermissions.t.sol index 88ce0c98..1e9f17e2 100644 --- a/test/account/ExecuteFromPluginPermissions.t.sol +++ b/test/account/ExecuteFromPluginPermissions.t.sol @@ -3,55 +3,39 @@ pragma solidity ^0.8.19; import {console} from "forge-std/Test.sol"; -import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.sol"; - import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {FunctionReference} from "../../src/helpers/FunctionReferenceLib.sol"; -import {SingleOwnerPlugin} from "../../src/plugins/owner/SingleOwnerPlugin.sol"; -import {MSCAFactoryFixture} from "../mocks/MSCAFactoryFixture.sol"; import {Counter} from "../mocks/Counter.sol"; import {ResultCreatorPlugin} from "../mocks/plugins/ReturnDataPluginMocks.sol"; import {EFPCallerPlugin, EFPCallerPluginAnyExternal} from "../mocks/plugins/ExecFromPluginPermissionsMocks.sol"; -import {OptimizedTest} from "../utils/OptimizedTest.sol"; +import {AccountTestBase} from "../utils/AccountTestBase.sol"; -contract ExecuteFromPluginPermissionsTest is OptimizedTest { +contract ExecuteFromPluginPermissionsTest is AccountTestBase { Counter public counter1; Counter public counter2; Counter public counter3; ResultCreatorPlugin public resultCreatorPlugin; - EntryPoint public entryPoint; // Just to be able to construct the factory - SingleOwnerPlugin public singleOwnerPlugin; - MSCAFactoryFixture public factory; - UpgradeableModularAccount public account; - EFPCallerPlugin public efpCallerPlugin; EFPCallerPluginAnyExternal public efpCallerPluginAnyExternal; function setUp() public { + _transferOwnershipToTest(); + // Initialize the interaction targets counter1 = new Counter(); counter2 = new Counter(); counter3 = new Counter(); resultCreatorPlugin = new ResultCreatorPlugin(); - // Initialize the contracts needed to use the account. - entryPoint = new EntryPoint(); - singleOwnerPlugin = _deploySingleOwnerPlugin(); - factory = new MSCAFactoryFixture(entryPoint, singleOwnerPlugin); - // Initialize the EFP caller plugins, which will attempt to use the permissions system to authorize calls. - efpCallerPlugin = new EFPCallerPlugin(); + efpCallerPlugin = new EFPCallerPlugin(address(counter1), address(counter2), address(counter3)); efpCallerPluginAnyExternal = new EFPCallerPluginAnyExternal(); - // Create an account with "this" as the owner, so we can execute along the runtime path with regular - // solidity semantics - account = factory.createAccount(address(this), 0); - // Add the result creator plugin to the account bytes32 resultCreatorManifestHash = keccak256(abi.encode(resultCreatorPlugin.pluginManifest())); - account.installPlugin({ + account1.installPlugin({ plugin: address(resultCreatorPlugin), manifestHash: resultCreatorManifestHash, pluginInstallData: "", @@ -59,7 +43,7 @@ contract ExecuteFromPluginPermissionsTest is OptimizedTest { }); // Add the EFP caller plugin to the account bytes32 efpCallerManifestHash = keccak256(abi.encode(efpCallerPlugin.pluginManifest())); - account.installPlugin({ + account1.installPlugin({ plugin: address(efpCallerPlugin), manifestHash: efpCallerManifestHash, pluginInstallData: "", @@ -69,7 +53,7 @@ contract ExecuteFromPluginPermissionsTest is OptimizedTest { // Add the EFP caller plugin with any external permissions to the account bytes32 efpCallerAnyExternalManifestHash = keccak256(abi.encode(efpCallerPluginAnyExternal.pluginManifest())); - account.installPlugin({ + account1.installPlugin({ plugin: address(efpCallerPluginAnyExternal), manifestHash: efpCallerAnyExternalManifestHash, pluginInstallData: "", @@ -88,7 +72,7 @@ contract ExecuteFromPluginPermissionsTest is OptimizedTest { } function test_executeFromPluginAllowed() public { - bytes memory result = EFPCallerPlugin(address(account)).useEFPPermissionAllowed(); + bytes memory result = EFPCallerPlugin(address(account1)).useEFPPermissionAllowed(); bytes32 actual = abi.decode(result, (bytes32)); assertEq(actual, keccak256("bar")); @@ -102,18 +86,18 @@ contract ExecuteFromPluginPermissionsTest is OptimizedTest { ResultCreatorPlugin.bar.selector ) ); - EFPCallerPlugin(address(account)).useEFPPermissionNotAllowed(); + EFPCallerPlugin(address(account1)).useEFPPermissionNotAllowed(); } function test_executeFromPluginExternal_Allowed_IndividualSelectors() public { - EFPCallerPlugin(address(account)).setNumberCounter1(17); - uint256 retrievedNumber = EFPCallerPlugin(address(account)).getNumberCounter1(); + EFPCallerPlugin(address(account1)).setNumberCounter1(17); + uint256 retrievedNumber = EFPCallerPlugin(address(account1)).getNumberCounter1(); assertEq(retrievedNumber, 17); } function test_executeFromPluginExternal_NotAlowed_IndividualSelectors() public { - EFPCallerPlugin(address(account)).setNumberCounter1(17); + EFPCallerPlugin(address(account1)).setNumberCounter1(17); // Call to increment should fail vm.expectRevert( @@ -125,17 +109,17 @@ contract ExecuteFromPluginPermissionsTest is OptimizedTest { abi.encodePacked(Counter.increment.selector) ) ); - EFPCallerPlugin(address(account)).incrementCounter1(); + EFPCallerPlugin(address(account1)).incrementCounter1(); - uint256 retrievedNumber = EFPCallerPlugin(address(account)).getNumberCounter1(); + uint256 retrievedNumber = EFPCallerPlugin(address(account1)).getNumberCounter1(); assertEq(retrievedNumber, 17); } function test_executeFromPluginExternal_Allowed_AllSelectors() public { - EFPCallerPlugin(address(account)).setNumberCounter2(17); - EFPCallerPlugin(address(account)).incrementCounter2(); - uint256 retrievedNumber = EFPCallerPlugin(address(account)).getNumberCounter2(); + EFPCallerPlugin(address(account1)).setNumberCounter2(17); + EFPCallerPlugin(address(account1)).incrementCounter2(); + uint256 retrievedNumber = EFPCallerPlugin(address(account1)).getNumberCounter2(); assertEq(retrievedNumber, 18); } @@ -150,7 +134,7 @@ contract ExecuteFromPluginPermissionsTest is OptimizedTest { abi.encodeWithSelector(Counter.setNumber.selector, uint256(17)) ) ); - EFPCallerPlugin(address(account)).setNumberCounter3(17); + EFPCallerPlugin(address(account1)).setNumberCounter3(17); // Call to increment should fail vm.expectRevert( @@ -162,7 +146,7 @@ contract ExecuteFromPluginPermissionsTest is OptimizedTest { abi.encodePacked(Counter.increment.selector) ) ); - EFPCallerPlugin(address(account)).incrementCounter3(); + EFPCallerPlugin(address(account1)).incrementCounter3(); vm.expectRevert( abi.encodeWithSelector( @@ -173,7 +157,7 @@ contract ExecuteFromPluginPermissionsTest is OptimizedTest { abi.encodePacked(bytes4(keccak256("number()"))) ) ); - EFPCallerPlugin(address(account)).getNumberCounter3(); + EFPCallerPlugin(address(account1)).getNumberCounter3(); // Validate no state changes assert(counter3.number() == 0); @@ -182,19 +166,19 @@ contract ExecuteFromPluginPermissionsTest is OptimizedTest { function test_executeFromPluginExternal_Allowed_AnyContract() public { // Run full workflow for counter 1 - EFPCallerPluginAnyExternal(address(account)).passthroughExecute( + EFPCallerPluginAnyExternal(address(account1)).passthroughExecute( address(counter1), 0, abi.encodeCall(Counter.setNumber, (17)) ); uint256 retrievedNumber = counter1.number(); assertEq(retrievedNumber, 17); - EFPCallerPluginAnyExternal(address(account)).passthroughExecute( + EFPCallerPluginAnyExternal(address(account1)).passthroughExecute( address(counter1), 0, abi.encodeCall(Counter.increment, ()) ); retrievedNumber = counter1.number(); assertEq(retrievedNumber, 18); - bytes memory result = EFPCallerPluginAnyExternal(address(account)).passthroughExecute( + bytes memory result = EFPCallerPluginAnyExternal(address(account1)).passthroughExecute( address(counter1), 0, abi.encodePacked(bytes4(keccak256("number()"))) ); retrievedNumber = abi.decode(result, (uint256)); @@ -202,19 +186,19 @@ contract ExecuteFromPluginPermissionsTest is OptimizedTest { // Run full workflow for counter 2 - EFPCallerPluginAnyExternal(address(account)).passthroughExecute( + EFPCallerPluginAnyExternal(address(account1)).passthroughExecute( address(counter2), 0, abi.encodeCall(Counter.setNumber, (17)) ); retrievedNumber = counter2.number(); assertEq(retrievedNumber, 17); - EFPCallerPluginAnyExternal(address(account)).passthroughExecute( + EFPCallerPluginAnyExternal(address(account1)).passthroughExecute( address(counter2), 0, abi.encodeCall(Counter.increment, ()) ); retrievedNumber = counter2.number(); assertEq(retrievedNumber, 18); - result = EFPCallerPluginAnyExternal(address(account)).passthroughExecute( + result = EFPCallerPluginAnyExternal(address(account1)).passthroughExecute( address(counter2), 0, abi.encodePacked(bytes4(keccak256("number()"))) ); retrievedNumber = abi.decode(result, (uint256)); diff --git a/test/account/ManifestValidity.t.sol b/test/account/ManifestValidity.t.sol index 169d6c81..9de32960 100644 --- a/test/account/ManifestValidity.t.sol +++ b/test/account/ManifestValidity.t.sol @@ -1,15 +1,9 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.sol"; - -import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {PluginManagerInternals} from "../../src/account/PluginManagerInternals.sol"; -import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {FunctionReference} from "../../src/helpers/FunctionReferenceLib.sol"; -import {SingleOwnerPlugin} from "../../src/plugins/owner/SingleOwnerPlugin.sol"; -import {MSCAFactoryFixture} from "../mocks/MSCAFactoryFixture.sol"; import { BadValidationMagicValue_PreRuntimeValidationHook_Plugin, BadValidationMagicValue_PreUserOpValidationHook_Plugin, @@ -19,23 +13,11 @@ import { BadHookMagicValue_RuntimeValidationFunction_Plugin, BadHookMagicValue_PostExecHook_Plugin } from "../mocks/plugins/ManifestValidityMocks.sol"; -import {OptimizedTest} from "../utils/OptimizedTest.sol"; - -contract ManifestValidityTest is OptimizedTest { - EntryPoint public entryPoint; // Just to be able to construct the factory - SingleOwnerPlugin public singleOwnerPlugin; - MSCAFactoryFixture public factory; - - UpgradeableModularAccount public account; +import {AccountTestBase} from "../utils/AccountTestBase.sol"; +contract ManifestValidityTest is AccountTestBase { function setUp() public { - entryPoint = new EntryPoint(); - singleOwnerPlugin = _deploySingleOwnerPlugin(); - factory = new MSCAFactoryFixture(entryPoint, singleOwnerPlugin); - - // Create an account with "this" as the owner, so we can execute along the runtime path with regular - // solidity semantics - account = factory.createAccount(address(this), 0); + _transferOwnershipToTest(); } // Tests that the plugin manager rejects a plugin with a pre-runtime validation hook set to "validation always @@ -47,7 +29,7 @@ contract ManifestValidityTest is OptimizedTest { bytes32 manifestHash = keccak256(abi.encode(plugin.pluginManifest())); vm.expectRevert(abi.encodeWithSelector(PluginManagerInternals.InvalidPluginManifest.selector)); - account.installPlugin({ + account1.installPlugin({ plugin: address(plugin), manifestHash: manifestHash, pluginInstallData: "", @@ -64,7 +46,7 @@ contract ManifestValidityTest is OptimizedTest { bytes32 manifestHash = keccak256(abi.encode(plugin.pluginManifest())); vm.expectRevert(abi.encodeWithSelector(PluginManagerInternals.InvalidPluginManifest.selector)); - account.installPlugin({ + account1.installPlugin({ plugin: address(plugin), manifestHash: manifestHash, pluginInstallData: "", @@ -79,7 +61,7 @@ contract ManifestValidityTest is OptimizedTest { bytes32 manifestHash = keccak256(abi.encode(plugin.pluginManifest())); vm.expectRevert(abi.encodeWithSelector(PluginManagerInternals.InvalidPluginManifest.selector)); - account.installPlugin({ + account1.installPlugin({ plugin: address(plugin), manifestHash: manifestHash, pluginInstallData: "", @@ -94,7 +76,7 @@ contract ManifestValidityTest is OptimizedTest { bytes32 manifestHash = keccak256(abi.encode(plugin.pluginManifest())); vm.expectRevert(abi.encodeWithSelector(PluginManagerInternals.InvalidPluginManifest.selector)); - account.installPlugin({ + account1.installPlugin({ plugin: address(plugin), manifestHash: manifestHash, pluginInstallData: "", @@ -110,7 +92,7 @@ contract ManifestValidityTest is OptimizedTest { bytes32 manifestHash = keccak256(abi.encode(plugin.pluginManifest())); vm.expectRevert(abi.encodeWithSelector(PluginManagerInternals.InvalidPluginManifest.selector)); - account.installPlugin({ + account1.installPlugin({ plugin: address(plugin), manifestHash: manifestHash, pluginInstallData: "", @@ -126,7 +108,7 @@ contract ManifestValidityTest is OptimizedTest { bytes32 manifestHash = keccak256(abi.encode(plugin.pluginManifest())); vm.expectRevert(abi.encodeWithSelector(PluginManagerInternals.InvalidPluginManifest.selector)); - account.installPlugin({ + account1.installPlugin({ plugin: address(plugin), manifestHash: manifestHash, pluginInstallData: "", @@ -141,7 +123,7 @@ contract ManifestValidityTest is OptimizedTest { bytes32 manifestHash = keccak256(abi.encode(plugin.pluginManifest())); vm.expectRevert(abi.encodeWithSelector(PluginManagerInternals.InvalidPluginManifest.selector)); - account.installPlugin({ + account1.installPlugin({ plugin: address(plugin), manifestHash: manifestHash, pluginInstallData: "", diff --git a/test/account/UpgradeableModularAccount.t.sol b/test/account/UpgradeableModularAccount.t.sol index 31f9b8f5..486f283b 100644 --- a/test/account/UpgradeableModularAccount.t.sol +++ b/test/account/UpgradeableModularAccount.t.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.19; import {console} from "forge-std/Test.sol"; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.sol"; import {UserOperation} from "@eth-infinitism/account-abstraction/interfaces/UserOperation.sol"; import {PluginManagerInternals} from "../../src/account/PluginManagerInternals.sol"; @@ -18,24 +17,16 @@ import {SingleOwnerPlugin} from "../../src/plugins/owner/SingleOwnerPlugin.sol"; import {TokenReceiverPlugin} from "../../src/plugins/TokenReceiverPlugin.sol"; import {Counter} from "../mocks/Counter.sol"; -import {MSCAFactoryFixture} from "../mocks/MSCAFactoryFixture.sol"; import {ComprehensivePlugin} from "../mocks/plugins/ComprehensivePlugin.sol"; import {MockPlugin} from "../mocks/MockPlugin.sol"; -import {OptimizedTest} from "../utils/OptimizedTest.sol"; +import {AccountTestBase} from "../utils/AccountTestBase.sol"; -contract UpgradeableModularAccountTest is OptimizedTest { +contract UpgradeableModularAccountTest is AccountTestBase { using ECDSA for bytes32; - EntryPoint public entryPoint; - address payable public beneficiary; - SingleOwnerPlugin public singleOwnerPlugin; TokenReceiverPlugin public tokenReceiverPlugin; - MSCAFactoryFixture public factory; - - address public owner1; - uint256 public owner1Key; - UpgradeableModularAccount public account1; + // A separate account and owner that isn't deployed yet, used to test initcode address public owner2; uint256 public owner2Key; UpgradeableModularAccount public account2; @@ -52,47 +43,34 @@ contract UpgradeableModularAccountTest is OptimizedTest { event ReceivedCall(bytes msgData, uint256 msgValue); function setUp() public { - entryPoint = new EntryPoint(); - (owner1, owner1Key) = makeAddrAndKey("owner1"); - beneficiary = payable(makeAddr("beneficiary")); - vm.deal(beneficiary, 1 wei); - - singleOwnerPlugin = _deploySingleOwnerPlugin(); tokenReceiverPlugin = _deployTokenReceiverPlugin(); - factory = new MSCAFactoryFixture(entryPoint, singleOwnerPlugin); - - // Compute counterfactual address - account1 = UpgradeableModularAccount(payable(factory.getAddress(owner1, 0))); - vm.deal(address(account1), 100 ether); - // Pre-deploy account two for different gas estimates (owner2, owner2Key) = makeAddrAndKey("owner2"); - account2 = factory.createAccount(owner2, 0); + + // Compute counterfactual address + account2 = UpgradeableModularAccount(payable(factory.getAddress(owner2, 0))); vm.deal(address(account2), 100 ether); ethRecipient = makeAddr("ethRecipient"); vm.deal(ethRecipient, 1 wei); counter = new Counter(); counter.increment(); // amoritze away gas cost of zero->nonzero transition - - vm.deal(address(this), 100 ether); - entryPoint.depositTo{value: 1 wei}(address(account2)); } function test_deployAccount() public { - factory.createAccount(owner1, 0); + factory.createAccount(owner2, 0); } - function test_basicUserOp() public { + function test_postDeploy_ethSend() public { UserOperation memory userOp = UserOperation({ sender: address(account1), nonce: 0, - initCode: abi.encodePacked(address(factory), abi.encodeCall(factory.createAccount, (owner1, 0))), - callData: abi.encodeCall(SingleOwnerPlugin.transferOwnership, (owner2)), + initCode: "", + callData: abi.encodeCall(UpgradeableModularAccount.execute, (ethRecipient, 1 wei, "")), callGasLimit: CALL_GAS_LIMIT, verificationGasLimit: VERIFICATION_GAS_LIMIT, preVerificationGas: 0, - maxFeePerGas: 2, + maxFeePerGas: 1, maxPriorityFeePerGas: 1, paymasterAndData: "", signature: "" @@ -107,16 +85,16 @@ contract UpgradeableModularAccountTest is OptimizedTest { userOps[0] = userOp; entryPoint.handleOps(userOps, beneficiary); - } - function test_standardExecuteEthSend() public { - address payable recipient = payable(makeAddr("recipient")); + assertEq(ethRecipient.balance, 2 wei); + } + function test_basicUserOp_withInitCode() public { UserOperation memory userOp = UserOperation({ - sender: address(account1), + sender: address(account2), nonce: 0, - initCode: abi.encodePacked(address(factory), abi.encodeCall(factory.createAccount, (owner1, 0))), - callData: abi.encodeCall(UpgradeableModularAccount.execute, (recipient, 1 wei, "")), + initCode: abi.encodePacked(address(factory), abi.encodeCall(factory.createAccount, (owner2, 0))), + callData: abi.encodeCall(SingleOwnerPlugin.transferOwnership, (owner2)), callGasLimit: CALL_GAS_LIMIT, verificationGasLimit: VERIFICATION_GAS_LIMIT, preVerificationGas: 0, @@ -128,27 +106,27 @@ contract UpgradeableModularAccountTest is OptimizedTest { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); userOp.signature = abi.encodePacked(r, s, v); UserOperation[] memory userOps = new UserOperation[](1); userOps[0] = userOp; entryPoint.handleOps(userOps, beneficiary); - - assertEq(recipient.balance, 1 wei); } - function test_postDeploy_ethSend() public { + function test_standardExecuteEthSend_withInitcode() public { + address payable recipient = payable(makeAddr("recipient")); + UserOperation memory userOp = UserOperation({ sender: address(account2), nonce: 0, - initCode: "", - callData: abi.encodeCall(UpgradeableModularAccount.execute, (ethRecipient, 1 wei, "")), + initCode: abi.encodePacked(address(factory), abi.encodeCall(factory.createAccount, (owner2, 0))), + callData: abi.encodeCall(UpgradeableModularAccount.execute, (recipient, 1 wei, "")), callGasLimit: CALL_GAS_LIMIT, verificationGasLimit: VERIFICATION_GAS_LIMIT, preVerificationGas: 0, - maxFeePerGas: 1, + maxFeePerGas: 2, maxPriorityFeePerGas: 1, paymasterAndData: "", signature: "" @@ -164,12 +142,12 @@ contract UpgradeableModularAccountTest is OptimizedTest { entryPoint.handleOps(userOps, beneficiary); - assertEq(ethRecipient.balance, 2 wei); + assertEq(recipient.balance, 1 wei); } function test_debug_upgradeableModularAccount_storageAccesses() public { UserOperation memory userOp = UserOperation({ - sender: address(account2), + sender: address(account1), nonce: 0, initCode: "", callData: abi.encodeCall(UpgradeableModularAccount.execute, (ethRecipient, 1 wei, "")), @@ -184,7 +162,7 @@ contract UpgradeableModularAccountTest is OptimizedTest { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); userOp.signature = abi.encodePacked(r, s, v); UserOperation[] memory userOps = new UserOperation[](1); @@ -197,7 +175,7 @@ contract UpgradeableModularAccountTest is OptimizedTest { function test_contractInteraction() public { UserOperation memory userOp = UserOperation({ - sender: address(account2), + sender: address(account1), nonce: 0, initCode: "", callData: abi.encodeCall( @@ -214,7 +192,7 @@ contract UpgradeableModularAccountTest is OptimizedTest { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); userOp.signature = abi.encodePacked(r, s, v); UserOperation[] memory userOps = new UserOperation[](1); @@ -232,7 +210,7 @@ contract UpgradeableModularAccountTest is OptimizedTest { calls[1] = Call({target: address(counter), value: 0, data: abi.encodeCall(counter.increment, ())}); UserOperation memory userOp = UserOperation({ - sender: address(account2), + sender: address(account1), nonce: 0, initCode: "", callData: abi.encodeCall(UpgradeableModularAccount.executeBatch, (calls)), @@ -247,7 +225,7 @@ contract UpgradeableModularAccountTest is OptimizedTest { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); userOp.signature = abi.encodePacked(r, s, v); UserOperation[] memory userOps = new UserOperation[](1); @@ -260,27 +238,27 @@ contract UpgradeableModularAccountTest is OptimizedTest { } function test_installPlugin() public { - vm.startPrank(owner2); + vm.startPrank(owner1); bytes32 manifestHash = keccak256(abi.encode(tokenReceiverPlugin.pluginManifest())); vm.expectEmit(true, true, true, true); emit PluginInstalled(address(tokenReceiverPlugin), manifestHash, new FunctionReference[](0)); - IPluginManager(account2).installPlugin({ + IPluginManager(account1).installPlugin({ plugin: address(tokenReceiverPlugin), manifestHash: manifestHash, pluginInstallData: abi.encode(uint48(1 days)), dependencies: new FunctionReference[](0) }); - address[] memory plugins = IAccountLoupe(account2).getInstalledPlugins(); + address[] memory plugins = IAccountLoupe(account1).getInstalledPlugins(); assertEq(plugins.length, 2); assertEq(plugins[0], address(singleOwnerPlugin)); assertEq(plugins[1], address(tokenReceiverPlugin)); } function test_installPlugin_ExecuteFromPlugin_PermittedExecSelectorNotInstalled() public { - vm.startPrank(owner2); + vm.startPrank(owner1); PluginManifest memory m; m.permittedExecutionSelectors = new bytes4[](1); @@ -289,7 +267,7 @@ contract UpgradeableModularAccountTest is OptimizedTest { MockPlugin mockPluginWithBadPermittedExec = new MockPlugin(m); bytes32 manifestHash = keccak256(abi.encode(mockPluginWithBadPermittedExec.pluginManifest())); - IPluginManager(account2).installPlugin({ + IPluginManager(account1).installPlugin({ plugin: address(mockPluginWithBadPermittedExec), manifestHash: manifestHash, pluginInstallData: "", @@ -298,10 +276,10 @@ contract UpgradeableModularAccountTest is OptimizedTest { } function test_installPlugin_invalidManifest() public { - vm.startPrank(owner2); + vm.startPrank(owner1); vm.expectRevert(abi.encodeWithSelector(PluginManagerInternals.InvalidPluginManifest.selector)); - IPluginManager(account2).installPlugin({ + IPluginManager(account1).installPlugin({ plugin: address(tokenReceiverPlugin), manifestHash: bytes32(0), pluginInstallData: abi.encode(uint48(1 days)), @@ -310,13 +288,13 @@ contract UpgradeableModularAccountTest is OptimizedTest { } function test_installPlugin_interfaceNotSupported() public { - vm.startPrank(owner2); + vm.startPrank(owner1); address badPlugin = address(1); vm.expectRevert( abi.encodeWithSelector(PluginManagerInternals.PluginInterfaceNotSupported.selector, address(badPlugin)) ); - IPluginManager(account2).installPlugin({ + IPluginManager(account1).installPlugin({ plugin: address(badPlugin), manifestHash: bytes32(0), pluginInstallData: "", @@ -325,10 +303,10 @@ contract UpgradeableModularAccountTest is OptimizedTest { } function test_installPlugin_alreadyInstalled() public { - vm.startPrank(owner2); + vm.startPrank(owner1); bytes32 manifestHash = keccak256(abi.encode(tokenReceiverPlugin.pluginManifest())); - IPluginManager(account2).installPlugin({ + IPluginManager(account1).installPlugin({ plugin: address(tokenReceiverPlugin), manifestHash: manifestHash, pluginInstallData: abi.encode(uint48(1 days)), @@ -340,7 +318,7 @@ contract UpgradeableModularAccountTest is OptimizedTest { PluginManagerInternals.PluginAlreadyInstalled.selector, address(tokenReceiverPlugin) ) ); - IPluginManager(account2).installPlugin({ + IPluginManager(account1).installPlugin({ plugin: address(tokenReceiverPlugin), manifestHash: manifestHash, pluginInstallData: abi.encode(uint48(1 days)), @@ -349,11 +327,11 @@ contract UpgradeableModularAccountTest is OptimizedTest { } function test_uninstallPlugin_default() public { - vm.startPrank(owner2); + vm.startPrank(owner1); ComprehensivePlugin plugin = new ComprehensivePlugin(); bytes32 manifestHash = keccak256(abi.encode(plugin.pluginManifest())); - IPluginManager(account2).installPlugin({ + IPluginManager(account1).installPlugin({ plugin: address(plugin), manifestHash: manifestHash, pluginInstallData: "", @@ -362,19 +340,19 @@ contract UpgradeableModularAccountTest is OptimizedTest { vm.expectEmit(true, true, true, true); emit PluginUninstalled(address(plugin), true); - IPluginManager(account2).uninstallPlugin({plugin: address(plugin), config: "", pluginUninstallData: ""}); - address[] memory plugins = IAccountLoupe(account2).getInstalledPlugins(); + IPluginManager(account1).uninstallPlugin({plugin: address(plugin), config: "", pluginUninstallData: ""}); + address[] memory plugins = IAccountLoupe(account1).getInstalledPlugins(); assertEq(plugins.length, 1); assertEq(plugins[0], address(singleOwnerPlugin)); } function test_uninstallPlugin_manifestParameter() public { - vm.startPrank(owner2); + vm.startPrank(owner1); ComprehensivePlugin plugin = new ComprehensivePlugin(); bytes memory serializedManifest = abi.encode(plugin.pluginManifest()); bytes32 manifestHash = keccak256(serializedManifest); - IPluginManager(account2).installPlugin({ + IPluginManager(account1).installPlugin({ plugin: address(plugin), manifestHash: manifestHash, pluginInstallData: "", @@ -383,23 +361,23 @@ contract UpgradeableModularAccountTest is OptimizedTest { vm.expectEmit(true, true, true, true); emit PluginUninstalled(address(plugin), true); - IPluginManager(account2).uninstallPlugin({ + IPluginManager(account1).uninstallPlugin({ plugin: address(plugin), config: serializedManifest, pluginUninstallData: "" }); - address[] memory plugins = IAccountLoupe(account2).getInstalledPlugins(); + address[] memory plugins = IAccountLoupe(account1).getInstalledPlugins(); assertEq(plugins.length, 1); assertEq(plugins[0], address(singleOwnerPlugin)); } function test_uninstallPlugin_invalidManifestFails() public { - vm.startPrank(owner2); + vm.startPrank(owner1); ComprehensivePlugin plugin = new ComprehensivePlugin(); bytes memory serializedManifest = abi.encode(plugin.pluginManifest()); bytes32 manifestHash = keccak256(serializedManifest); - IPluginManager(account2).installPlugin({ + IPluginManager(account1).installPlugin({ plugin: address(plugin), manifestHash: manifestHash, pluginInstallData: "", @@ -410,12 +388,12 @@ contract UpgradeableModularAccountTest is OptimizedTest { PluginManifest memory blankManifest; vm.expectRevert(abi.encodeWithSelector(PluginManagerInternals.InvalidPluginManifest.selector)); - IPluginManager(account2).uninstallPlugin({ + IPluginManager(account1).uninstallPlugin({ plugin: address(plugin), config: abi.encode(blankManifest), pluginUninstallData: "" }); - address[] memory plugins = IAccountLoupe(account2).getInstalledPlugins(); + address[] memory plugins = IAccountLoupe(account1).getInstalledPlugins(); assertEq(plugins.length, 2); assertEq(plugins[0], address(singleOwnerPlugin)); assertEq(plugins[1], address(plugin)); @@ -427,7 +405,7 @@ contract UpgradeableModularAccountTest is OptimizedTest { plugin = new MockPlugin(manifest); bytes32 manifestHash = keccak256(abi.encode(plugin.pluginManifest())); - IPluginManager(account2).installPlugin({ + IPluginManager(account1).installPlugin({ plugin: address(plugin), manifestHash: manifestHash, pluginInstallData: "", diff --git a/test/account/ValidationIntersection.t.sol b/test/account/ValidationIntersection.t.sol index b5f001f7..0ed7329c 100644 --- a/test/account/ValidationIntersection.t.sol +++ b/test/account/ValidationIntersection.t.sol @@ -1,44 +1,27 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.sol"; import {UserOperation} from "@eth-infinitism/account-abstraction/interfaces/UserOperation.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {FunctionReference} from "../../src/helpers/FunctionReferenceLib.sol"; -import {SingleOwnerPlugin} from "../../src/plugins/owner/SingleOwnerPlugin.sol"; -import {MSCAFactoryFixture} from "../mocks/MSCAFactoryFixture.sol"; import { MockBaseUserOpValidationPlugin, MockUserOpValidation1HookPlugin, MockUserOpValidation2HookPlugin, MockUserOpValidationPlugin } from "../mocks/plugins/ValidationPluginMocks.sol"; -import {OptimizedTest} from "../utils/OptimizedTest.sol"; +import {AccountTestBase} from "../utils/AccountTestBase.sol"; -contract ValidationIntersectionTest is OptimizedTest { +contract ValidationIntersectionTest is AccountTestBase { uint256 internal constant _SIG_VALIDATION_FAILED = 1; - EntryPoint public entryPoint; - - address public owner1; - uint256 public owner1Key; - UpgradeableModularAccount public account1; MockUserOpValidationPlugin public noHookPlugin; MockUserOpValidation1HookPlugin public oneHookPlugin; MockUserOpValidation2HookPlugin public twoHookPlugin; function setUp() public { - entryPoint = new EntryPoint(); - owner1 = makeAddr("owner1"); - - SingleOwnerPlugin singleOwnerPlugin = _deploySingleOwnerPlugin(); - MSCAFactoryFixture factory = new MSCAFactoryFixture(entryPoint, singleOwnerPlugin); - - account1 = factory.createAccount(owner1, 0); - vm.deal(address(account1), 1 ether); - noHookPlugin = new MockUserOpValidationPlugin(); oneHookPlugin = new MockUserOpValidation1HookPlugin(); twoHookPlugin = new MockUserOpValidation2HookPlugin(); diff --git a/test/mocks/plugins/ExecFromPluginPermissionsMocks.sol b/test/mocks/plugins/ExecFromPluginPermissionsMocks.sol index 663e5f7f..72c812bb 100644 --- a/test/mocks/plugins/ExecFromPluginPermissionsMocks.sol +++ b/test/mocks/plugins/ExecFromPluginPermissionsMocks.sol @@ -14,18 +14,25 @@ import {BaseTestPlugin} from "./BaseTestPlugin.sol"; import {ResultCreatorPlugin} from "./ReturnDataPluginMocks.sol"; import {Counter} from "../Counter.sol"; -// Hardcode the counter addresses from ExecuteFromPluginPermissionsTest to be able to have a pure plugin manifest -// easily -address constant counter1 = 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f; -address constant counter2 = 0x2e234DAe75C793f67A35089C9d99245E1C58470b; -address constant counter3 = 0xF62849F9A0B5Bf2913b396098F7c7019b51A820a; - contract EFPCallerPlugin is BaseTestPlugin { + // Store the counters as immutables, and use the view -> pure cast to get the manifest + // solhint-disable private-vars-leading-underscore, immutable-vars-naming + address private immutable counter1; + address private immutable counter2; + address private immutable counter3; + // solhint-enable private-vars-leading-underscore, immutable-vars-naming + + constructor(address _counter1, address _counter2, address _counter3) { + counter1 = _counter1; + counter2 = _counter2; + counter3 = _counter3; + } + function onInstall(bytes calldata) external override {} function onUninstall(bytes calldata) external override {} - function pluginManifest() external pure override returns (PluginManifest memory) { + function _getManifest() internal view returns (PluginManifest memory) { PluginManifest memory manifest; manifest.executionFunctions = new bytes4[](11); @@ -86,6 +93,20 @@ contract EFPCallerPlugin is BaseTestPlugin { return manifest; } + function _castToPure(function() internal view returns (PluginManifest memory) fnIn) + internal + pure + returns (function() internal pure returns (PluginManifest memory) fnOut) + { + assembly ("memory-safe") { + fnOut := fnIn + } + } + + function pluginManifest() external pure override returns (PluginManifest memory) { + return _castToPure(_getManifest)(); + } + // The manifest requested access to use the plugin-defined method "foo" function useEFPPermissionAllowed() external returns (bytes memory) { return IPluginExecutor(msg.sender).executeFromPlugin(abi.encodeCall(ResultCreatorPlugin.foo, ())); diff --git a/test/utils/AccountTestBase.sol b/test/utils/AccountTestBase.sol new file mode 100644 index 00000000..9738d3ab --- /dev/null +++ b/test/utils/AccountTestBase.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.sol"; + +import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; +import {SingleOwnerPlugin} from "../../src/plugins/owner/SingleOwnerPlugin.sol"; + +import {OptimizedTest} from "./OptimizedTest.sol"; + +import {MSCAFactoryFixture} from "../mocks/MSCAFactoryFixture.sol"; + +/// @dev This contract handles common boilerplate setup for tests using UpgradeableModularAccount with +/// SingleOwnerPlugin. +abstract contract AccountTestBase is OptimizedTest { + EntryPoint public entryPoint; + address payable public beneficiary; + SingleOwnerPlugin public singleOwnerPlugin; + MSCAFactoryFixture public factory; + + address public owner1; + uint256 public owner1Key; + UpgradeableModularAccount public account1; + + constructor() { + entryPoint = new EntryPoint(); + (owner1, owner1Key) = makeAddrAndKey("owner1"); + beneficiary = payable(makeAddr("beneficiary")); + + singleOwnerPlugin = _deploySingleOwnerPlugin(); + factory = new MSCAFactoryFixture(entryPoint, singleOwnerPlugin); + + account1 = factory.createAccount(owner1, 0); + vm.deal(address(account1), 100 ether); + } + + function _transferOwnershipToTest() internal { + // Transfer ownership to test contract for easier invocation. + vm.prank(owner1); + SingleOwnerPlugin(address(account1)).transferOwnership(address(this)); + } +} From 8b5833031cc132456a73d1571e6bae9e8488528f Mon Sep 17 00:00:00 2001 From: adam-alchemy <127769144+adam-alchemy@users.noreply.github.com> Date: Thu, 18 Apr 2024 11:00:40 -0700 Subject: [PATCH 005/111] feat: [v0.8-develop] Bump solidity version and remove optimizations (#45) --- .vscode/settings.json | 2 +- foundry.toml | 2 +- src/account/AccountExecutor.sol | 2 +- src/account/AccountLoupe.sol | 19 +-- src/account/AccountStorage.sol | 8 +- src/account/AccountStorageInitializable.sol | 2 +- src/account/PluginManagerInternals.sol | 119 ++++-------------- src/account/UpgradeableModularAccount.sol | 46 ++----- src/helpers/FunctionReferenceLib.sol | 2 +- src/helpers/ValidationDataHelpers.sol | 2 +- src/interfaces/IAccountLoupe.sol | 2 +- src/interfaces/IPlugin.sol | 2 +- src/interfaces/IPluginExecutor.sol | 2 +- src/interfaces/IPluginManager.sol | 2 +- src/interfaces/IStandardExecutor.sol | 2 +- src/libraries/AssociatedLinkedListSetLib.sol | 2 +- src/libraries/PluginStorageLib.sol | 2 +- src/plugins/BasePlugin.sol | 2 +- src/plugins/TokenReceiverPlugin.sol | 2 +- src/plugins/owner/ISingleOwnerPlugin.sol | 2 +- src/plugins/owner/SingleOwnerPlugin.sol | 2 +- .../plugins/ModularSessionKeyPlugin.sol | 2 +- src/samples/plugins/TokenSessionKeyPlugin.sol | 2 +- .../plugins/interfaces/ISessionKeyPlugin.sol | 2 +- .../interfaces/ITokenSessionKeyPlugin.sol | 2 +- 25 files changed, 60 insertions(+), 174 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 03ec7c76..c47446a7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,7 @@ { "solidity.packageDefaultDependenciesContractsDirectory": "src", "solidity.packageDefaultDependenciesDirectory": "lib", - "solidity.compileUsingRemoteVersion": "v0.8.19", + "solidity.compileUsingRemoteVersion": "v0.8.25", "editor.formatOnSave": true, "[solidity]": { "editor.defaultFormatter": "JuanBlanco.solidity" diff --git a/foundry.toml b/foundry.toml index be310db9..5aad32a4 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,5 +1,5 @@ [profile.default] -solc = '0.8.19' +solc = '0.8.25' via_ir = false src = 'src' test = 'test' diff --git a/src/account/AccountExecutor.sol b/src/account/AccountExecutor.sol index de77512c..136676f6 100644 --- a/src/account/AccountExecutor.sol +++ b/src/account/AccountExecutor.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.19; +pragma solidity ^0.8.25; import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; import {IPlugin} from "../interfaces/IPlugin.sol"; diff --git a/src/account/AccountLoupe.sol b/src/account/AccountLoupe.sol index 8b6f42a2..8bff326c 100644 --- a/src/account/AccountLoupe.sol +++ b/src/account/AccountLoupe.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.19; +pragma solidity ^0.8.25; import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; @@ -14,8 +14,6 @@ abstract contract AccountLoupe is IAccountLoupe { using EnumerableMap for EnumerableMap.Bytes32ToUintMap; using EnumerableSet for EnumerableSet.AddressSet; - error ManifestDiscrepancy(address plugin); - /// @inheritdoc IAccountLoupe function getExecutionFunctionConfig(bytes4 selector) external @@ -47,11 +45,10 @@ abstract contract AccountLoupe is IAccountLoupe { uint256 maxExecHooksLength = postOnlyExecHooksLength; // There can only be as many associated post hooks to run as there are pre hooks. - for (uint256 i = 0; i < preExecHooksLength;) { + for (uint256 i = 0; i < preExecHooksLength; ++i) { (, uint256 count) = selectorData.preHooks.at(i); unchecked { maxExecHooksLength += (count + 1); - ++i; } } @@ -59,20 +56,19 @@ abstract contract AccountLoupe is IAccountLoupe { execHooks = new ExecutionHooks[](maxExecHooksLength); uint256 actualExecHooksLength; - for (uint256 i = 0; i < preExecHooksLength;) { + for (uint256 i = 0; i < preExecHooksLength; ++i) { (bytes32 key,) = selectorData.preHooks.at(i); FunctionReference preExecHook = FunctionReference.wrap(bytes21(key)); uint256 associatedPostExecHooksLength = selectorData.associatedPostHooks[preExecHook].length(); if (associatedPostExecHooksLength > 0) { - for (uint256 j = 0; j < associatedPostExecHooksLength;) { + for (uint256 j = 0; j < associatedPostExecHooksLength; ++j) { execHooks[actualExecHooksLength].preExecHook = preExecHook; (key,) = selectorData.associatedPostHooks[preExecHook].at(j); execHooks[actualExecHooksLength].postExecHook = FunctionReference.wrap(bytes21(key)); unchecked { ++actualExecHooksLength; - ++j; } } } else { @@ -82,19 +78,14 @@ abstract contract AccountLoupe is IAccountLoupe { ++actualExecHooksLength; } } - - unchecked { - ++i; - } } - for (uint256 i = 0; i < postOnlyExecHooksLength;) { + for (uint256 i = 0; i < postOnlyExecHooksLength; ++i) { (bytes32 key,) = selectorData.postOnlyHooks.at(i); execHooks[actualExecHooksLength].postExecHook = FunctionReference.wrap(bytes21(key)); unchecked { ++actualExecHooksLength; - ++i; } } diff --git a/src/account/AccountStorage.sol b/src/account/AccountStorage.sol index deb31228..35b3eabb 100644 --- a/src/account/AccountStorage.sol +++ b/src/account/AccountStorage.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.19; +pragma solidity ^0.8.25; import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; @@ -83,13 +83,9 @@ function toFunctionReferenceArray(EnumerableMap.Bytes32ToUintMap storage map) { uint256 length = map.length(); FunctionReference[] memory result = new FunctionReference[](length); - for (uint256 i = 0; i < length;) { + for (uint256 i = 0; i < length; ++i) { (bytes32 key,) = map.at(i); result[i] = FunctionReference.wrap(bytes21(key)); - - unchecked { - ++i; - } } return result; } diff --git a/src/account/AccountStorageInitializable.sol b/src/account/AccountStorageInitializable.sol index ab7617db..5131978b 100644 --- a/src/account/AccountStorageInitializable.sol +++ b/src/account/AccountStorageInitializable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.19; +pragma solidity ^0.8.25; import {Address} from "@openzeppelin/contracts/utils/Address.sol"; diff --git a/src/account/PluginManagerInternals.sol b/src/account/PluginManagerInternals.sol index dc5fbb8a..8257963d 100644 --- a/src/account/PluginManagerInternals.sol +++ b/src/account/PluginManagerInternals.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.19; +pragma solidity ^0.8.25; import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; @@ -209,7 +209,7 @@ abstract contract PluginManagerInternals is IPluginManager { } uint256 length = dependencies.length; - for (uint256 i = 0; i < length;) { + for (uint256 i = 0; i < length; ++i) { // Check the dependency interface id over the address of the dependency. (address dependencyAddr,) = dependencies[i].unpack(); @@ -225,10 +225,6 @@ abstract contract PluginManagerInternals is IPluginManager { // Increment the dependency's dependents counter. _storage.pluginData[dependencyAddr].dependentCount += 1; - - unchecked { - ++i; - } } // Add the plugin metadata to the account @@ -243,24 +239,16 @@ abstract contract PluginManagerInternals is IPluginManager { } length = manifest.executionFunctions.length; - for (uint256 i = 0; i < length;) { + for (uint256 i = 0; i < length; ++i) { _setExecutionFunction(manifest.executionFunctions[i], plugin); - - unchecked { - ++i; - } } // Add installed plugin and selectors this plugin can call length = manifest.permittedExecutionSelectors.length; - for (uint256 i = 0; i < length;) { + for (uint256 i = 0; i < length; ++i) { // If there are duplicates, this will just enable the flag again. This is not a problem, since the // boolean will be set to false twice during uninstall, which is fine. _storage.callPermitted[getPermittedCallKey(plugin, manifest.permittedExecutionSelectors[i])] = true; - - unchecked { - ++i; - } } // Add the permitted external calls to the account. @@ -269,7 +257,7 @@ abstract contract PluginManagerInternals is IPluginManager { } else { // Only store the specific permitted external calls if "permit any" flag was not set. length = manifest.permittedExternalCalls.length; - for (uint256 i = 0; i < length;) { + for (uint256 i = 0; i < length; ++i) { ManifestExternalCallPermission memory externalCallPermission = manifest.permittedExternalCalls[i]; PermittedExternalCallData storage permittedExternalCallData = @@ -281,23 +269,15 @@ abstract contract PluginManagerInternals is IPluginManager { permittedExternalCallData.anySelectorPermitted = true; } else { uint256 externalContractSelectorsLength = externalCallPermission.selectors.length; - for (uint256 j = 0; j < externalContractSelectorsLength;) { + for (uint256 j = 0; j < externalContractSelectorsLength; ++j) { permittedExternalCallData.permittedSelectors[externalCallPermission.selectors[j]] = true; - - unchecked { - ++j; - } } } - - unchecked { - ++i; - } } } length = manifest.validationFunctions.length; - for (uint256 i = 0; i < length;) { + for (uint256 i = 0; i < length; ++i) { ManifestAssociatedFunction memory mv = manifest.validationFunctions[i]; _addValidationFunction( mv.executionSelector, @@ -308,17 +288,13 @@ abstract contract PluginManagerInternals is IPluginManager { ManifestAssociatedFunctionType.RUNTIME_VALIDATION_ALWAYS_ALLOW ) ); - - unchecked { - ++i; - } } // Hooks are not allowed to be provided as dependencies, so we use an empty array for resolving them. FunctionReference[] memory emptyDependencies; length = manifest.preUserOpValidationHooks.length; - for (uint256 i = 0; i < length;) { + for (uint256 i = 0; i < length; ++i) { ManifestAssociatedFunction memory mh = manifest.preUserOpValidationHooks[i]; _addPreUserOpValidationHook( mh.executionSelector, @@ -329,14 +305,10 @@ abstract contract PluginManagerInternals is IPluginManager { ManifestAssociatedFunctionType.PRE_HOOK_ALWAYS_DENY ) ); - - unchecked { - ++i; - } } length = manifest.preRuntimeValidationHooks.length; - for (uint256 i = 0; i < length;) { + for (uint256 i = 0; i < length; ++i) { ManifestAssociatedFunction memory mh = manifest.preRuntimeValidationHooks[i]; _addPreRuntimeValidationHook( mh.executionSelector, @@ -347,13 +319,10 @@ abstract contract PluginManagerInternals is IPluginManager { ManifestAssociatedFunctionType.PRE_HOOK_ALWAYS_DENY ) ); - unchecked { - ++i; - } } length = manifest.executionHooks.length; - for (uint256 i = 0; i < length;) { + for (uint256 i = 0; i < length; ++i) { ManifestExecutionHook memory mh = manifest.executionHooks[i]; _addExecHooks( mh.executionSelector, @@ -364,18 +333,11 @@ abstract contract PluginManagerInternals is IPluginManager { mh.postExecHook, plugin, emptyDependencies, ManifestAssociatedFunctionType.NONE ) ); - - unchecked { - ++i; - } } length = manifest.interfaceIds.length; - for (uint256 i = 0; i < length;) { + for (uint256 i = 0; i < length; ++i) { _storage.supportedIfaces[manifest.interfaceIds[i]] += 1; - unchecked { - ++i; - } } // Initialize the plugin storage for the account. @@ -412,16 +374,12 @@ abstract contract PluginManagerInternals is IPluginManager { // Remove this plugin as a dependent from its dependencies. FunctionReference[] memory dependencies = _storage.pluginData[plugin].dependencies; uint256 length = dependencies.length; - for (uint256 i = 0; i < length;) { + for (uint256 i = 0; i < length; ++i) { FunctionReference dependency = dependencies[i]; (address dependencyAddr,) = dependency.unpack(); // Decrement the dependent count for the dependency function. _storage.pluginData[dependencyAddr].dependentCount -= 1; - - unchecked { - ++i; - } } // Remove components according to the manifest, in reverse order (by component type) of their installation. @@ -430,7 +388,7 @@ abstract contract PluginManagerInternals is IPluginManager { FunctionReference[] memory emptyDependencies; length = manifest.executionHooks.length; - for (uint256 i = 0; i < length;) { + for (uint256 i = 0; i < length; ++i) { ManifestExecutionHook memory mh = manifest.executionHooks[i]; _removeExecHooks( mh.executionSelector, @@ -441,14 +399,10 @@ abstract contract PluginManagerInternals is IPluginManager { mh.postExecHook, plugin, emptyDependencies, ManifestAssociatedFunctionType.NONE ) ); - - unchecked { - ++i; - } } length = manifest.preRuntimeValidationHooks.length; - for (uint256 i = 0; i < length;) { + for (uint256 i = 0; i < length; ++i) { ManifestAssociatedFunction memory mh = manifest.preRuntimeValidationHooks[i]; _removePreRuntimeValidationHook( mh.executionSelector, @@ -459,14 +413,10 @@ abstract contract PluginManagerInternals is IPluginManager { ManifestAssociatedFunctionType.PRE_HOOK_ALWAYS_DENY ) ); - - unchecked { - ++i; - } } length = manifest.preUserOpValidationHooks.length; - for (uint256 i = 0; i < length;) { + for (uint256 i = 0; i < length; ++i) { ManifestAssociatedFunction memory mh = manifest.preUserOpValidationHooks[i]; _removePreUserOpValidationHook( mh.executionSelector, @@ -477,14 +427,10 @@ abstract contract PluginManagerInternals is IPluginManager { ManifestAssociatedFunctionType.PRE_HOOK_ALWAYS_DENY ) ); - - unchecked { - ++i; - } } length = manifest.validationFunctions.length; - for (uint256 i = 0; i < length;) { + for (uint256 i = 0; i < length; ++i) { ManifestAssociatedFunction memory mv = manifest.validationFunctions[i]; _removeValidationFunction( mv.executionSelector, @@ -495,10 +441,6 @@ abstract contract PluginManagerInternals is IPluginManager { ManifestAssociatedFunctionType.RUNTIME_VALIDATION_ALWAYS_ALLOW ) ); - - unchecked { - ++i; - } } // remove external call permissions @@ -509,7 +451,7 @@ abstract contract PluginManagerInternals is IPluginManager { } else { // Only clear the specific permitted external calls if "permit any" flag was not set. length = manifest.permittedExternalCalls.length; - for (uint256 i = 0; i < length;) { + for (uint256 i = 0; i < length; ++i) { ManifestExternalCallPermission memory externalCallPermission = manifest.permittedExternalCalls[i]; PermittedExternalCallData storage permittedExternalCallData = @@ -522,45 +464,26 @@ abstract contract PluginManagerInternals is IPluginManager { permittedExternalCallData.anySelectorPermitted = false; } else { uint256 externalContractSelectorsLength = externalCallPermission.selectors.length; - for (uint256 j = 0; j < externalContractSelectorsLength;) { + for (uint256 j = 0; j < externalContractSelectorsLength; ++j) { permittedExternalCallData.permittedSelectors[externalCallPermission.selectors[j]] = false; - - unchecked { - ++j; - } } } - - unchecked { - ++i; - } } } length = manifest.permittedExecutionSelectors.length; - for (uint256 i = 0; i < length;) { + for (uint256 i = 0; i < length; ++i) { _storage.callPermitted[getPermittedCallKey(plugin, manifest.permittedExecutionSelectors[i])] = false; - - unchecked { - ++i; - } } length = manifest.executionFunctions.length; - for (uint256 i = 0; i < length;) { + for (uint256 i = 0; i < length; ++i) { _removeExecutionFunction(manifest.executionFunctions[i]); - - unchecked { - ++i; - } } length = manifest.interfaceIds.length; - for (uint256 i = 0; i < length;) { + for (uint256 i = 0; i < length; ++i) { _storage.supportedIfaces[manifest.interfaceIds[i]] -= 1; - unchecked { - ++i; - } } // Remove the plugin metadata from the account. diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 24a87ea1..1a7c0ac3 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.19; +pragma solidity ^0.8.25; import {BaseAccount} from "@eth-infinitism/account-abstraction/core/BaseAccount.sol"; import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; @@ -101,12 +101,8 @@ contract UpgradeableModularAccount is FunctionReference[] memory emptyDependencies = new FunctionReference[](0); - for (uint256 i = 0; i < length;) { + for (uint256 i = 0; i < length; ++i) { _installPlugin(plugins[i], manifestHashes[i], pluginInstallDatas[i], emptyDependencies); - - unchecked { - ++i; - } } emit ModularAccountInitialized(_ENTRY_POINT); @@ -166,12 +162,8 @@ contract UpgradeableModularAccount is uint256 callsLength = calls.length; results = new bytes[](callsLength); - for (uint256 i = 0; i < callsLength;) { + for (uint256 i = 0; i < callsLength; ++i) { results[i] = _exec(calls[i].target, calls[i].value, calls[i].data); - - unchecked { - ++i; - } } } @@ -359,7 +351,7 @@ contract UpgradeableModularAccount is getAccountStorage().selectorData[selector].preUserOpValidationHooks; uint256 preUserOpValidationHooksLength = preUserOpValidationHooks.length(); - for (uint256 i = 0; i < preUserOpValidationHooksLength;) { + for (uint256 i = 0; i < preUserOpValidationHooksLength; ++i) { (bytes32 key,) = preUserOpValidationHooks.at(i); FunctionReference preUserOpValidationHook = _toFunctionReference(key); @@ -376,10 +368,6 @@ contract UpgradeableModularAccount is // Function reference cannot be 0 and _RUNTIME_VALIDATION_ALWAYS_ALLOW is not permitted here. revert InvalidConfiguration(); } - - unchecked { - ++i; - } } // Run the user op validationFunction @@ -412,7 +400,7 @@ contract UpgradeableModularAccount is getAccountStorage().selectorData[msg.sig].preRuntimeValidationHooks; uint256 preRuntimeValidationHooksLength = preRuntimeValidationHooks.length(); - for (uint256 i = 0; i < preRuntimeValidationHooksLength;) { + for (uint256 i = 0; i < preRuntimeValidationHooksLength; ++i) { (bytes32 key,) = preRuntimeValidationHooks.at(i); FunctionReference preRuntimeValidationHook = _toFunctionReference(key); @@ -423,10 +411,6 @@ contract UpgradeableModularAccount is catch (bytes memory revertReason) { revert PreRuntimeValidationHookFailed(plugin, functionId, revertReason); } - - unchecked { - ++i; - } } else { if (preRuntimeValidationHook.eq(FunctionReferenceLib._PRE_HOOK_ALWAYS_DENY)) { revert AlwaysDenyRule(); @@ -466,11 +450,10 @@ contract UpgradeableModularAccount is uint256 maxPostExecHooksLength = postOnlyHooksLength; // There can only be as many associated post hooks to run as there are pre hooks. - for (uint256 i = 0; i < preExecHooksLength;) { + for (uint256 i = 0; i < preExecHooksLength; ++i) { (, uint256 count) = selectorData.preHooks.at(i); unchecked { maxPostExecHooksLength += (count + 1); - ++i; } } @@ -479,18 +462,17 @@ contract UpgradeableModularAccount is uint256 actualPostHooksToRunLength; // Copy post-only hooks to the array. - for (uint256 i = 0; i < postOnlyHooksLength;) { + for (uint256 i = 0; i < postOnlyHooksLength; ++i) { (bytes32 key,) = selectorData.postOnlyHooks.at(i); postHooksToRun[actualPostHooksToRunLength].postExecHook = _toFunctionReference(key); unchecked { ++actualPostHooksToRunLength; - ++i; } } // Then run the pre hooks and copy the associated post hooks (along with their pre hook's return data) to // the array. - for (uint256 i = 0; i < preExecHooksLength;) { + for (uint256 i = 0; i < preExecHooksLength; ++i) { (bytes32 key,) = selectorData.preHooks.at(i); FunctionReference preExecHook = _toFunctionReference(key); @@ -504,21 +486,16 @@ contract UpgradeableModularAccount is uint256 associatedPostExecHooksLength = selectorData.associatedPostHooks[preExecHook].length(); if (associatedPostExecHooksLength > 0) { - for (uint256 j = 0; j < associatedPostExecHooksLength;) { + for (uint256 j = 0; j < associatedPostExecHooksLength; ++j) { (key,) = selectorData.associatedPostHooks[preExecHook].at(j); postHooksToRun[actualPostHooksToRunLength].postExecHook = _toFunctionReference(key); postHooksToRun[actualPostHooksToRunLength].preExecHookReturnData = preExecHookReturnData; unchecked { ++actualPostHooksToRunLength; - ++j; } } } - - unchecked { - ++i; - } } // Trim the post hook array to the actual length, since we may have overallocated. @@ -545,9 +522,8 @@ contract UpgradeableModularAccount is function _doCachedPostExecHooks(PostExecToRun[] memory postHooksToRun) internal { uint256 postHooksToRunLength = postHooksToRun.length; for (uint256 i = postHooksToRunLength; i > 0;) { - unchecked { - --i; - } + // Decrement here, instead of in the loop body, to handle the case where length is 0. + --i; PostExecToRun memory postHookToRun = postHooksToRun[i]; (address plugin, uint8 functionId) = postHookToRun.postExecHook.unpack(); diff --git a/src/helpers/FunctionReferenceLib.sol b/src/helpers/FunctionReferenceLib.sol index 07a0abbc..a938eef5 100644 --- a/src/helpers/FunctionReferenceLib.sol +++ b/src/helpers/FunctionReferenceLib.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.19; +pragma solidity ^0.8.25; import {FunctionReference} from "../interfaces/IPluginManager.sol"; diff --git a/src/helpers/ValidationDataHelpers.sol b/src/helpers/ValidationDataHelpers.sol index 14e0131a..3f61b19c 100644 --- a/src/helpers/ValidationDataHelpers.sol +++ b/src/helpers/ValidationDataHelpers.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.19; +pragma solidity ^0.8.25; // solhint-disable-next-line private-vars-leading-underscore function _coalescePreValidation(uint256 validationData1, uint256 validationData2) diff --git a/src/interfaces/IAccountLoupe.sol b/src/interfaces/IAccountLoupe.sol index 300a81b1..9076b28d 100644 --- a/src/interfaces/IAccountLoupe.sol +++ b/src/interfaces/IAccountLoupe.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.19; +pragma solidity ^0.8.25; import {FunctionReference} from "../interfaces/IPluginManager.sol"; diff --git a/src/interfaces/IPlugin.sol b/src/interfaces/IPlugin.sol index e8edaf4a..5c4702d2 100644 --- a/src/interfaces/IPlugin.sol +++ b/src/interfaces/IPlugin.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.19; +pragma solidity ^0.8.25; import {UserOperation} from "@eth-infinitism/account-abstraction/interfaces/UserOperation.sol"; diff --git a/src/interfaces/IPluginExecutor.sol b/src/interfaces/IPluginExecutor.sol index d18ba665..e1989958 100644 --- a/src/interfaces/IPluginExecutor.sol +++ b/src/interfaces/IPluginExecutor.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.19; +pragma solidity ^0.8.25; interface IPluginExecutor { /// @notice Execute a call from a plugin to another plugin, via an execution function installed on the account. diff --git a/src/interfaces/IPluginManager.sol b/src/interfaces/IPluginManager.sol index 21054d27..dd4dc672 100644 --- a/src/interfaces/IPluginManager.sol +++ b/src/interfaces/IPluginManager.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.19; +pragma solidity ^0.8.25; type FunctionReference is bytes21; diff --git a/src/interfaces/IStandardExecutor.sol b/src/interfaces/IStandardExecutor.sol index a5618be6..30b6deee 100644 --- a/src/interfaces/IStandardExecutor.sol +++ b/src/interfaces/IStandardExecutor.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.19; +pragma solidity ^0.8.25; struct Call { // The target address for the account to call. diff --git a/src/libraries/AssociatedLinkedListSetLib.sol b/src/libraries/AssociatedLinkedListSetLib.sol index 97974b2c..4663e4ac 100644 --- a/src/libraries/AssociatedLinkedListSetLib.sol +++ b/src/libraries/AssociatedLinkedListSetLib.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.25; // type SetValue is bytes30; diff --git a/src/libraries/PluginStorageLib.sol b/src/libraries/PluginStorageLib.sol index 35aaa7bb..5811f8f8 100644 --- a/src/libraries/PluginStorageLib.sol +++ b/src/libraries/PluginStorageLib.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.25; // type StoragePointer is bytes32; diff --git a/src/plugins/BasePlugin.sol b/src/plugins/BasePlugin.sol index 5dbcb2b3..5a89b982 100644 --- a/src/plugins/BasePlugin.sol +++ b/src/plugins/BasePlugin.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.19; +pragma solidity ^0.8.25; import {UserOperation} from "@eth-infinitism/account-abstraction/interfaces/UserOperation.sol"; import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; diff --git a/src/plugins/TokenReceiverPlugin.sol b/src/plugins/TokenReceiverPlugin.sol index e138f8f3..f22ae3ba 100644 --- a/src/plugins/TokenReceiverPlugin.sol +++ b/src/plugins/TokenReceiverPlugin.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.19; +pragma solidity ^0.8.25; import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; import {IERC777Recipient} from "@openzeppelin/contracts/interfaces/IERC777Recipient.sol"; diff --git a/src/plugins/owner/ISingleOwnerPlugin.sol b/src/plugins/owner/ISingleOwnerPlugin.sol index 4f5257e5..d20296ef 100644 --- a/src/plugins/owner/ISingleOwnerPlugin.sol +++ b/src/plugins/owner/ISingleOwnerPlugin.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.19; +pragma solidity ^0.8.25; interface ISingleOwnerPlugin { enum FunctionId { diff --git a/src/plugins/owner/SingleOwnerPlugin.sol b/src/plugins/owner/SingleOwnerPlugin.sol index 3c3507e2..5e3b432a 100644 --- a/src/plugins/owner/SingleOwnerPlugin.sol +++ b/src/plugins/owner/SingleOwnerPlugin.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.19; +pragma solidity ^0.8.25; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; diff --git a/src/samples/plugins/ModularSessionKeyPlugin.sol b/src/samples/plugins/ModularSessionKeyPlugin.sol index 035b62fa..ceed7108 100644 --- a/src/samples/plugins/ModularSessionKeyPlugin.sol +++ b/src/samples/plugins/ModularSessionKeyPlugin.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.19; +pragma solidity ^0.8.25; // import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; // import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; diff --git a/src/samples/plugins/TokenSessionKeyPlugin.sol b/src/samples/plugins/TokenSessionKeyPlugin.sol index 82cc6d24..09275374 100644 --- a/src/samples/plugins/TokenSessionKeyPlugin.sol +++ b/src/samples/plugins/TokenSessionKeyPlugin.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.19; +pragma solidity ^0.8.25; // import { // ManifestFunction, diff --git a/src/samples/plugins/interfaces/ISessionKeyPlugin.sol b/src/samples/plugins/interfaces/ISessionKeyPlugin.sol index 3d378d29..99d94456 100644 --- a/src/samples/plugins/interfaces/ISessionKeyPlugin.sol +++ b/src/samples/plugins/interfaces/ISessionKeyPlugin.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.19; +pragma solidity ^0.8.25; // import {UserOperation} from "@eth-infinitism/account-abstraction/interfaces/UserOperation.sol"; diff --git a/src/samples/plugins/interfaces/ITokenSessionKeyPlugin.sol b/src/samples/plugins/interfaces/ITokenSessionKeyPlugin.sol index 2e990ef7..09a3bf84 100644 --- a/src/samples/plugins/interfaces/ITokenSessionKeyPlugin.sol +++ b/src/samples/plugins/interfaces/ITokenSessionKeyPlugin.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.19; +pragma solidity ^0.8.25; // import {UserOperation} from "@eth-infinitism/account-abstraction/interfaces/UserOperation.sol"; From d9bcff3a5fd8e163a520f675ad084af4c662aa8a Mon Sep 17 00:00:00 2001 From: adam-alchemy <127769144+adam-alchemy@users.noreply.github.com> Date: Fri, 26 Apr 2024 09:19:54 -0700 Subject: [PATCH 006/111] feat: [v0.8-develop] merge pre validation types (#46) --- src/account/AccountLoupe.sol | 11 +-- src/account/AccountStorage.sol | 3 +- src/account/PluginManagerInternals.sol | 75 ++++---------------- src/account/UpgradeableModularAccount.sol | 4 +- src/interfaces/IAccountLoupe.sol | 8 +-- src/interfaces/IPlugin.sol | 3 +- standard/ERCs/erc-6900.md | 11 +-- test/account/AccountLoupe.t.sol | 34 ++------- test/account/ManifestValidity.t.sol | 26 ++----- test/account/ValidationIntersection.t.sol | 2 +- test/mocks/plugins/ComprehensivePlugin.sol | 66 ++++------------- test/mocks/plugins/ManifestValidityMocks.sol | 47 +----------- test/mocks/plugins/ValidationPluginMocks.sol | 24 +++---- 13 files changed, 63 insertions(+), 251 deletions(-) diff --git a/src/account/AccountLoupe.sol b/src/account/AccountLoupe.sol index 8bff326c..d622efde 100644 --- a/src/account/AccountLoupe.sol +++ b/src/account/AccountLoupe.sol @@ -99,15 +99,10 @@ abstract contract AccountLoupe is IAccountLoupe { function getPreValidationHooks(bytes4 selector) external view - returns ( - FunctionReference[] memory preUserOpValidationHooks, - FunctionReference[] memory preRuntimeValidationHooks - ) + returns (FunctionReference[] memory preValidationHooks) { - preUserOpValidationHooks = - toFunctionReferenceArray(getAccountStorage().selectorData[selector].preUserOpValidationHooks); - preRuntimeValidationHooks = - toFunctionReferenceArray(getAccountStorage().selectorData[selector].preRuntimeValidationHooks); + preValidationHooks = + toFunctionReferenceArray(getAccountStorage().selectorData[selector].preValidationHooks); } /// @inheritdoc IAccountLoupe diff --git a/src/account/AccountStorage.sol b/src/account/AccountStorage.sol index 35b3eabb..35f4dc3d 100644 --- a/src/account/AccountStorage.sol +++ b/src/account/AccountStorage.sol @@ -38,8 +38,7 @@ struct SelectorData { // User operation validation and runtime validation share a function reference. FunctionReference validation; // The pre validation hooks for this function selector. - EnumerableMap.Bytes32ToUintMap preUserOpValidationHooks; - EnumerableMap.Bytes32ToUintMap preRuntimeValidationHooks; + EnumerableMap.Bytes32ToUintMap preValidationHooks; // The execution hooks for this function selector. EnumerableMap.Bytes32ToUintMap preHooks; // bytes21 key = pre hook function reference diff --git a/src/account/PluginManagerInternals.sol b/src/account/PluginManagerInternals.sol index 8257963d..a3734c4c 100644 --- a/src/account/PluginManagerInternals.sol +++ b/src/account/PluginManagerInternals.sol @@ -137,45 +137,22 @@ abstract contract PluginManagerInternals is IPluginManager { } } - function _addPreUserOpValidationHook(bytes4 selector, FunctionReference preUserOpValidationHook) + function _addPreValidationHook(bytes4 selector, FunctionReference preValidationHook) internal - notNullFunction(preUserOpValidationHook) + notNullFunction(preValidationHook) { _addOrIncrement( - getAccountStorage().selectorData[selector].preUserOpValidationHooks, - _toSetValue(preUserOpValidationHook) + getAccountStorage().selectorData[selector].preValidationHooks, _toSetValue(preValidationHook) ); } - function _removePreUserOpValidationHook(bytes4 selector, FunctionReference preUserOpValidationHook) + function _removePreValidationHook(bytes4 selector, FunctionReference preValidationHook) internal - notNullFunction(preUserOpValidationHook) + notNullFunction(preValidationHook) { // May ignore return value, as the manifest hash is validated to ensure that the hook exists. _removeOrDecrement( - getAccountStorage().selectorData[selector].preUserOpValidationHooks, - _toSetValue(preUserOpValidationHook) - ); - } - - function _addPreRuntimeValidationHook(bytes4 selector, FunctionReference preRuntimeValidationHook) - internal - notNullFunction(preRuntimeValidationHook) - { - _addOrIncrement( - getAccountStorage().selectorData[selector].preRuntimeValidationHooks, - _toSetValue(preRuntimeValidationHook) - ); - } - - function _removePreRuntimeValidationHook(bytes4 selector, FunctionReference preRuntimeValidationHook) - internal - notNullFunction(preRuntimeValidationHook) - { - // May ignore return value, as the manifest hash is validated to ensure that the hook exists. - _removeOrDecrement( - getAccountStorage().selectorData[selector].preRuntimeValidationHooks, - _toSetValue(preRuntimeValidationHook) + getAccountStorage().selectorData[selector].preValidationHooks, _toSetValue(preValidationHook) ); } @@ -293,24 +270,10 @@ abstract contract PluginManagerInternals is IPluginManager { // Hooks are not allowed to be provided as dependencies, so we use an empty array for resolving them. FunctionReference[] memory emptyDependencies; - length = manifest.preUserOpValidationHooks.length; + length = manifest.preValidationHooks.length; for (uint256 i = 0; i < length; ++i) { - ManifestAssociatedFunction memory mh = manifest.preUserOpValidationHooks[i]; - _addPreUserOpValidationHook( - mh.executionSelector, - _resolveManifestFunction( - mh.associatedFunction, - plugin, - emptyDependencies, - ManifestAssociatedFunctionType.PRE_HOOK_ALWAYS_DENY - ) - ); - } - - length = manifest.preRuntimeValidationHooks.length; - for (uint256 i = 0; i < length; ++i) { - ManifestAssociatedFunction memory mh = manifest.preRuntimeValidationHooks[i]; - _addPreRuntimeValidationHook( + ManifestAssociatedFunction memory mh = manifest.preValidationHooks[i]; + _addPreValidationHook( mh.executionSelector, _resolveManifestFunction( mh.associatedFunction, @@ -401,24 +364,10 @@ abstract contract PluginManagerInternals is IPluginManager { ); } - length = manifest.preRuntimeValidationHooks.length; - for (uint256 i = 0; i < length; ++i) { - ManifestAssociatedFunction memory mh = manifest.preRuntimeValidationHooks[i]; - _removePreRuntimeValidationHook( - mh.executionSelector, - _resolveManifestFunction( - mh.associatedFunction, - plugin, - emptyDependencies, - ManifestAssociatedFunctionType.PRE_HOOK_ALWAYS_DENY - ) - ); - } - - length = manifest.preUserOpValidationHooks.length; + length = manifest.preValidationHooks.length; for (uint256 i = 0; i < length; ++i) { - ManifestAssociatedFunction memory mh = manifest.preUserOpValidationHooks[i]; - _removePreUserOpValidationHook( + ManifestAssociatedFunction memory mh = manifest.preValidationHooks[i]; + _removePreValidationHook( mh.executionSelector, _resolveManifestFunction( mh.associatedFunction, diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 1a7c0ac3..b82f0ce6 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -348,7 +348,7 @@ contract UpgradeableModularAccount is // Do preUserOpValidation hooks EnumerableMap.Bytes32ToUintMap storage preUserOpValidationHooks = - getAccountStorage().selectorData[selector].preUserOpValidationHooks; + getAccountStorage().selectorData[selector].preValidationHooks; uint256 preUserOpValidationHooksLength = preUserOpValidationHooks.length(); for (uint256 i = 0; i < preUserOpValidationHooksLength; ++i) { @@ -397,7 +397,7 @@ contract UpgradeableModularAccount is FunctionReference runtimeValidationFunction = _storage.selectorData[msg.sig].validation; // run all preRuntimeValidation hooks EnumerableMap.Bytes32ToUintMap storage preRuntimeValidationHooks = - getAccountStorage().selectorData[msg.sig].preRuntimeValidationHooks; + getAccountStorage().selectorData[msg.sig].preValidationHooks; uint256 preRuntimeValidationHooksLength = preRuntimeValidationHooks.length(); for (uint256 i = 0; i < preRuntimeValidationHooksLength; ++i) { diff --git a/src/interfaces/IAccountLoupe.sol b/src/interfaces/IAccountLoupe.sol index 9076b28d..5b8a7516 100644 --- a/src/interfaces/IAccountLoupe.sol +++ b/src/interfaces/IAccountLoupe.sol @@ -30,15 +30,11 @@ interface IAccountLoupe { /// @notice Get the pre user op and runtime validation hooks associated with a selector. /// @param selector The selector to get the hooks for. - /// @return preUserOpValidationHooks The pre user op validation hooks for this selector. - /// @return preRuntimeValidationHooks The pre runtime validation hooks for this selector. + /// @return preValidationHooks The pre validation hooks for this selector. function getPreValidationHooks(bytes4 selector) external view - returns ( - FunctionReference[] memory preUserOpValidationHooks, - FunctionReference[] memory preRuntimeValidationHooks - ); + returns (FunctionReference[] memory preValidationHooks); /// @notice Get an array of all installed plugins. /// @return The addresses of all installed plugins. diff --git a/src/interfaces/IPlugin.sol b/src/interfaces/IPlugin.sol index 5c4702d2..fa40af12 100644 --- a/src/interfaces/IPlugin.sol +++ b/src/interfaces/IPlugin.sol @@ -91,8 +91,7 @@ struct PluginManifest { bool canSpendNativeToken; ManifestExternalCallPermission[] permittedExternalCalls; ManifestAssociatedFunction[] validationFunctions; - ManifestAssociatedFunction[] preUserOpValidationHooks; - ManifestAssociatedFunction[] preRuntimeValidationHooks; + ManifestAssociatedFunction[] preValidationHooks; ManifestExecutionHook[] executionHooks; } diff --git a/standard/ERCs/erc-6900.md b/standard/ERCs/erc-6900.md index ce775c15..44bdb891 100644 --- a/standard/ERCs/erc-6900.md +++ b/standard/ERCs/erc-6900.md @@ -238,15 +238,11 @@ interface IAccountLoupe { /// @notice Get the pre user op and runtime validation hooks associated with a selector. /// @param selector The selector to get the hooks for. - /// @return preUserOpValidationHooks The pre user op validation hooks for this selector. - /// @return preRuntimeValidationHooks The pre runtime validation hooks for this selector. + /// @return preValidationHooks The pre validation hooks for this selector. function getPreValidationHooks(bytes4 selector) external view - returns ( - FunctionReference[] memory preUserOpValidationHooks, - FunctionReference[] memory preRuntimeValidationHooks - ); + returns (FunctionReference[] memory preValidationHooks); /// @notice Get an array of all installed plugins. /// @return The addresses of all installed plugins. @@ -423,8 +419,7 @@ struct PluginManifest { bool canSpendNativeToken; ManifestExternalCallPermission[] permittedExternalCalls; ManifestAssociatedFunction[] validationFunctions; - ManifestAssociatedFunction[] preUserOpValidationHooks; - ManifestAssociatedFunction[] preRuntimeValidationHooks; + ManifestAssociatedFunction[] preValidationHooks; ManifestExecutionHook[] executionHooks; } diff --git a/test/account/AccountLoupe.t.sol b/test/account/AccountLoupe.t.sol index c487a39e..d56a8ff9 100644 --- a/test/account/AccountLoupe.t.sol +++ b/test/account/AccountLoupe.t.sol @@ -185,16 +185,15 @@ contract AccountLoupeTest is AccountTestBase { ); } - function test_pluginLoupe_getPreUserOpValidationHooks() public { - (FunctionReference[] memory hooks,) = account1.getPreValidationHooks(comprehensivePlugin.foo.selector); + function test_pluginLoupe_getValidationHooks() public { + FunctionReference[] memory hooks = account1.getPreValidationHooks(comprehensivePlugin.foo.selector); assertEq(hooks.length, 2); assertEq( FunctionReference.unwrap(hooks[0]), FunctionReference.unwrap( FunctionReferenceLib.pack( - address(comprehensivePlugin), - uint8(ComprehensivePlugin.FunctionId.PRE_USER_OP_VALIDATION_HOOK_1) + address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.PRE_VALIDATION_HOOK_1) ) ) ); @@ -202,32 +201,7 @@ contract AccountLoupeTest is AccountTestBase { FunctionReference.unwrap(hooks[1]), FunctionReference.unwrap( FunctionReferenceLib.pack( - address(comprehensivePlugin), - uint8(ComprehensivePlugin.FunctionId.PRE_USER_OP_VALIDATION_HOOK_2) - ) - ) - ); - } - - function test_pluginLoupe_getPreRuntimeValidationHooks() public { - (, FunctionReference[] memory hooks) = account1.getPreValidationHooks(comprehensivePlugin.foo.selector); - - assertEq(hooks.length, 2); - assertEq( - FunctionReference.unwrap(hooks[0]), - FunctionReference.unwrap( - FunctionReferenceLib.pack( - address(comprehensivePlugin), - uint8(ComprehensivePlugin.FunctionId.PRE_RUNTIME_VALIDATION_HOOK_1) - ) - ) - ); - assertEq( - FunctionReference.unwrap(hooks[1]), - FunctionReference.unwrap( - FunctionReferenceLib.pack( - address(comprehensivePlugin), - uint8(ComprehensivePlugin.FunctionId.PRE_RUNTIME_VALIDATION_HOOK_2) + address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.PRE_VALIDATION_HOOK_2) ) ) ); diff --git a/test/account/ManifestValidity.t.sol b/test/account/ManifestValidity.t.sol index 9de32960..2631cf1c 100644 --- a/test/account/ManifestValidity.t.sol +++ b/test/account/ManifestValidity.t.sol @@ -5,8 +5,7 @@ import {PluginManagerInternals} from "../../src/account/PluginManagerInternals.s import {FunctionReference} from "../../src/helpers/FunctionReferenceLib.sol"; import { - BadValidationMagicValue_PreRuntimeValidationHook_Plugin, - BadValidationMagicValue_PreUserOpValidationHook_Plugin, + BadValidationMagicValue_PreValidationHook_Plugin, BadValidationMagicValue_PreExecHook_Plugin, BadValidationMagicValue_PostExecHook_Plugin, BadHookMagicValue_UserOpValidationFunction_Plugin, @@ -22,26 +21,9 @@ contract ManifestValidityTest is AccountTestBase { // Tests that the plugin manager rejects a plugin with a pre-runtime validation hook set to "validation always // allow" - function test_ManifestValidity_invalid_ValidationAlwaysAllow_PreRuntimeValidationHook() public { - BadValidationMagicValue_PreRuntimeValidationHook_Plugin plugin = - new BadValidationMagicValue_PreRuntimeValidationHook_Plugin(); - - bytes32 manifestHash = keccak256(abi.encode(plugin.pluginManifest())); - - vm.expectRevert(abi.encodeWithSelector(PluginManagerInternals.InvalidPluginManifest.selector)); - account1.installPlugin({ - plugin: address(plugin), - manifestHash: manifestHash, - pluginInstallData: "", - dependencies: new FunctionReference[](0) - }); - } - - // Tests that the plugin manager rejects a plugin with a pre-user op validation hook set to "validation always - // allow" - function test_ManifestValidity_invalid_ValidationAlwaysAllow_PreUserOpValidationHook() public { - BadValidationMagicValue_PreUserOpValidationHook_Plugin plugin = - new BadValidationMagicValue_PreUserOpValidationHook_Plugin(); + function test_ManifestValidity_invalid_ValidationAlwaysAllow_PreValidationHook() public { + BadValidationMagicValue_PreValidationHook_Plugin plugin = + new BadValidationMagicValue_PreValidationHook_Plugin(); bytes32 manifestHash = keccak256(abi.encode(plugin.pluginManifest())); diff --git a/test/account/ValidationIntersection.t.sol b/test/account/ValidationIntersection.t.sol index 0ed7329c..f3b8e88a 100644 --- a/test/account/ValidationIntersection.t.sol +++ b/test/account/ValidationIntersection.t.sol @@ -155,7 +155,7 @@ contract ValidationIntersectionTest is AccountTestBase { abi.encodeWithSelector( UpgradeableModularAccount.UnexpectedAggregator.selector, address(oneHookPlugin), - MockBaseUserOpValidationPlugin.FunctionId.PRE_USER_OP_VALIDATION_HOOK_1, + MockBaseUserOpValidationPlugin.FunctionId.PRE_VALIDATION_HOOK_1, badAuthorizer ) ); diff --git a/test/mocks/plugins/ComprehensivePlugin.sol b/test/mocks/plugins/ComprehensivePlugin.sol index 442b39a6..d1d25004 100644 --- a/test/mocks/plugins/ComprehensivePlugin.sol +++ b/test/mocks/plugins/ComprehensivePlugin.sol @@ -16,10 +16,8 @@ import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; contract ComprehensivePlugin is BasePlugin { enum FunctionId { - PRE_USER_OP_VALIDATION_HOOK_1, - PRE_USER_OP_VALIDATION_HOOK_2, - PRE_RUNTIME_VALIDATION_HOOK_1, - PRE_RUNTIME_VALIDATION_HOOK_2, + PRE_VALIDATION_HOOK_1, + PRE_VALIDATION_HOOK_2, VALIDATION, PRE_EXECUTION_HOOK, PRE_PERMITTED_CALL_EXECUTION_HOOK, @@ -51,9 +49,9 @@ contract ComprehensivePlugin is BasePlugin { override returns (uint256) { - if (functionId == uint8(FunctionId.PRE_USER_OP_VALIDATION_HOOK_1)) { + if (functionId == uint8(FunctionId.PRE_VALIDATION_HOOK_1)) { return 0; - } else if (functionId == uint8(FunctionId.PRE_USER_OP_VALIDATION_HOOK_2)) { + } else if (functionId == uint8(FunctionId.PRE_VALIDATION_HOOK_2)) { return 0; } revert NotImplemented(); @@ -72,9 +70,9 @@ contract ComprehensivePlugin is BasePlugin { } function preRuntimeValidationHook(uint8 functionId, address, uint256, bytes calldata) external pure override { - if (functionId == uint8(FunctionId.PRE_RUNTIME_VALIDATION_HOOK_1)) { + if (functionId == uint8(FunctionId.PRE_VALIDATION_HOOK_1)) { return; - } else if (functionId == uint8(FunctionId.PRE_RUNTIME_VALIDATION_HOOK_2)) { + } else if (functionId == uint8(FunctionId.PRE_VALIDATION_HOOK_2)) { return; } revert NotImplemented(); @@ -131,70 +129,36 @@ contract ComprehensivePlugin is BasePlugin { associatedFunction: fooValidationFunction }); - manifest.preUserOpValidationHooks = new ManifestAssociatedFunction[](4); - manifest.preUserOpValidationHooks[0] = ManifestAssociatedFunction({ + manifest.preValidationHooks = new ManifestAssociatedFunction[](4); + manifest.preValidationHooks[0] = ManifestAssociatedFunction({ executionSelector: this.foo.selector, associatedFunction: ManifestFunction({ functionType: ManifestAssociatedFunctionType.SELF, - functionId: uint8(FunctionId.PRE_USER_OP_VALIDATION_HOOK_1), + functionId: uint8(FunctionId.PRE_VALIDATION_HOOK_1), dependencyIndex: 0 // Unused. }) }); - manifest.preUserOpValidationHooks[1] = ManifestAssociatedFunction({ + manifest.preValidationHooks[1] = ManifestAssociatedFunction({ executionSelector: this.foo.selector, associatedFunction: ManifestFunction({ functionType: ManifestAssociatedFunctionType.SELF, - functionId: uint8(FunctionId.PRE_USER_OP_VALIDATION_HOOK_2), + functionId: uint8(FunctionId.PRE_VALIDATION_HOOK_2), dependencyIndex: 0 // Unused. }) }); - manifest.preUserOpValidationHooks[2] = ManifestAssociatedFunction({ + manifest.preValidationHooks[2] = ManifestAssociatedFunction({ executionSelector: IStandardExecutor.execute.selector, associatedFunction: ManifestFunction({ functionType: ManifestAssociatedFunctionType.SELF, - functionId: uint8(FunctionId.PRE_USER_OP_VALIDATION_HOOK_1), + functionId: uint8(FunctionId.PRE_VALIDATION_HOOK_1), dependencyIndex: 0 // Unused. }) }); - manifest.preUserOpValidationHooks[3] = ManifestAssociatedFunction({ + manifest.preValidationHooks[3] = ManifestAssociatedFunction({ executionSelector: IStandardExecutor.execute.selector, associatedFunction: ManifestFunction({ functionType: ManifestAssociatedFunctionType.SELF, - functionId: uint8(FunctionId.PRE_USER_OP_VALIDATION_HOOK_2), - dependencyIndex: 0 // Unused. - }) - }); - - manifest.preRuntimeValidationHooks = new ManifestAssociatedFunction[](4); - manifest.preRuntimeValidationHooks[0] = ManifestAssociatedFunction({ - executionSelector: this.foo.selector, - associatedFunction: ManifestFunction({ - functionType: ManifestAssociatedFunctionType.SELF, - functionId: uint8(FunctionId.PRE_RUNTIME_VALIDATION_HOOK_1), - dependencyIndex: 0 // Unused. - }) - }); - manifest.preRuntimeValidationHooks[1] = ManifestAssociatedFunction({ - executionSelector: this.foo.selector, - associatedFunction: ManifestFunction({ - functionType: ManifestAssociatedFunctionType.SELF, - functionId: uint8(FunctionId.PRE_RUNTIME_VALIDATION_HOOK_2), - dependencyIndex: 0 // Unused. - }) - }); - manifest.preRuntimeValidationHooks[2] = ManifestAssociatedFunction({ - executionSelector: IStandardExecutor.execute.selector, - associatedFunction: ManifestFunction({ - functionType: ManifestAssociatedFunctionType.SELF, - functionId: uint8(FunctionId.PRE_RUNTIME_VALIDATION_HOOK_1), - dependencyIndex: 0 // Unused. - }) - }); - manifest.preRuntimeValidationHooks[3] = ManifestAssociatedFunction({ - executionSelector: IStandardExecutor.execute.selector, - associatedFunction: ManifestFunction({ - functionType: ManifestAssociatedFunctionType.SELF, - functionId: uint8(FunctionId.PRE_RUNTIME_VALIDATION_HOOK_2), + functionId: uint8(FunctionId.PRE_VALIDATION_HOOK_2), dependencyIndex: 0 // Unused. }) }); diff --git a/test/mocks/plugins/ManifestValidityMocks.sol b/test/mocks/plugins/ManifestValidityMocks.sol index d2083db6..c467f2ef 100644 --- a/test/mocks/plugins/ManifestValidityMocks.sol +++ b/test/mocks/plugins/ManifestValidityMocks.sol @@ -12,7 +12,7 @@ import { import {BaseTestPlugin} from "./BaseTestPlugin.sol"; // solhint-disable-next-line contract-name-camelcase -contract BadValidationMagicValue_PreRuntimeValidationHook_Plugin is BaseTestPlugin { +contract BadValidationMagicValue_PreValidationHook_Plugin is BaseTestPlugin { function onInstall(bytes calldata) external override {} function onUninstall(bytes calldata) external override {} @@ -37,50 +37,9 @@ contract BadValidationMagicValue_PreRuntimeValidationHook_Plugin is BaseTestPlug }) }); - manifest.preRuntimeValidationHooks = new ManifestAssociatedFunction[](1); + manifest.preValidationHooks = new ManifestAssociatedFunction[](1); // Illegal assignment: validation always allow only usable on runtime validation functions - manifest.preRuntimeValidationHooks[0] = ManifestAssociatedFunction({ - executionSelector: this.foo.selector, - associatedFunction: ManifestFunction({ - functionType: ManifestAssociatedFunctionType.RUNTIME_VALIDATION_ALWAYS_ALLOW, - functionId: 0, - dependencyIndex: 0 - }) - }); - - return manifest; - } -} - -// solhint-disable-next-line contract-name-camelcase -contract BadValidationMagicValue_PreUserOpValidationHook_Plugin is BaseTestPlugin { - function onInstall(bytes calldata) external override {} - - function onUninstall(bytes calldata) external override {} - - function foo() external pure returns (bytes32) { - return keccak256("bar"); - } - - function pluginManifest() external pure override returns (PluginManifest memory) { - PluginManifest memory manifest; - - manifest.executionFunctions = new bytes4[](1); - manifest.executionFunctions[0] = this.foo.selector; - - manifest.validationFunctions = new ManifestAssociatedFunction[](1); - manifest.validationFunctions[0] = ManifestAssociatedFunction({ - executionSelector: this.foo.selector, - associatedFunction: ManifestFunction({ - functionType: ManifestAssociatedFunctionType.SELF, - functionId: 0, - dependencyIndex: 0 - }) - }); - - manifest.preUserOpValidationHooks = new ManifestAssociatedFunction[](1); - // Illegal assignment: validation always allow only usable on runtime validation functions - manifest.preUserOpValidationHooks[0] = ManifestAssociatedFunction({ + manifest.preValidationHooks[0] = ManifestAssociatedFunction({ executionSelector: this.foo.selector, associatedFunction: ManifestFunction({ functionType: ManifestAssociatedFunctionType.RUNTIME_VALIDATION_ALWAYS_ALLOW, diff --git a/test/mocks/plugins/ValidationPluginMocks.sol b/test/mocks/plugins/ValidationPluginMocks.sol index 4859a7e9..7163c01b 100644 --- a/test/mocks/plugins/ValidationPluginMocks.sol +++ b/test/mocks/plugins/ValidationPluginMocks.sol @@ -14,8 +14,8 @@ import {BaseTestPlugin} from "./BaseTestPlugin.sol"; abstract contract MockBaseUserOpValidationPlugin is BaseTestPlugin { enum FunctionId { USER_OP_VALIDATION, - PRE_USER_OP_VALIDATION_HOOK_1, - PRE_USER_OP_VALIDATION_HOOK_2 + PRE_VALIDATION_HOOK_1, + PRE_VALIDATION_HOOK_2 } uint256 internal _userOpValidationFunctionData; @@ -36,9 +36,9 @@ abstract contract MockBaseUserOpValidationPlugin is BaseTestPlugin { override returns (uint256) { - if (functionId == uint8(FunctionId.PRE_USER_OP_VALIDATION_HOOK_1)) { + if (functionId == uint8(FunctionId.PRE_VALIDATION_HOOK_1)) { return _preUserOpValidationHook1Data; - } else if (functionId == uint8(FunctionId.PRE_USER_OP_VALIDATION_HOOK_2)) { + } else if (functionId == uint8(FunctionId.PRE_VALIDATION_HOOK_2)) { return _preUserOpValidationHook2Data; } revert NotImplemented(); @@ -127,12 +127,12 @@ contract MockUserOpValidation1HookPlugin is MockBaseUserOpValidationPlugin { associatedFunction: userOpValidationFunctionRef }); - manifest.preUserOpValidationHooks = new ManifestAssociatedFunction[](1); - manifest.preUserOpValidationHooks[0] = ManifestAssociatedFunction({ + manifest.preValidationHooks = new ManifestAssociatedFunction[](1); + manifest.preValidationHooks[0] = ManifestAssociatedFunction({ executionSelector: this.bar.selector, associatedFunction: ManifestFunction({ functionType: ManifestAssociatedFunctionType.SELF, - functionId: uint8(FunctionId.PRE_USER_OP_VALIDATION_HOOK_1), + functionId: uint8(FunctionId.PRE_VALIDATION_HOOK_1), dependencyIndex: 0 // Unused. }) }); @@ -179,20 +179,20 @@ contract MockUserOpValidation2HookPlugin is MockBaseUserOpValidationPlugin { associatedFunction: userOpValidationFunctionRef }); - manifest.preUserOpValidationHooks = new ManifestAssociatedFunction[](2); - manifest.preUserOpValidationHooks[0] = ManifestAssociatedFunction({ + manifest.preValidationHooks = new ManifestAssociatedFunction[](2); + manifest.preValidationHooks[0] = ManifestAssociatedFunction({ executionSelector: this.baz.selector, associatedFunction: ManifestFunction({ functionType: ManifestAssociatedFunctionType.SELF, - functionId: uint8(FunctionId.PRE_USER_OP_VALIDATION_HOOK_1), + functionId: uint8(FunctionId.PRE_VALIDATION_HOOK_1), dependencyIndex: 0 // Unused. }) }); - manifest.preUserOpValidationHooks[1] = ManifestAssociatedFunction({ + manifest.preValidationHooks[1] = ManifestAssociatedFunction({ executionSelector: this.baz.selector, associatedFunction: ManifestFunction({ functionType: ManifestAssociatedFunctionType.SELF, - functionId: uint8(FunctionId.PRE_USER_OP_VALIDATION_HOOK_2), + functionId: uint8(FunctionId.PRE_VALIDATION_HOOK_2), dependencyIndex: 0 // Unused. }) }); From 3b1e32efb3334765826b0c162cf913d7d3dc1022 Mon Sep 17 00:00:00 2001 From: howy <132113803+howydev@users.noreply.github.com> Date: Wed, 1 May 2024 14:39:46 -0400 Subject: [PATCH 007/111] chore: update to 4337 v0.7 (1/n) (#48) Co-authored-by: adam-alchemy <127769144+adam-alchemy@users.noreply.github.com> --- lib/account-abstraction | 2 +- lib/openzeppelin-contracts | 2 +- src/account/AccountLoupe.sol | 1 - src/account/AccountStorageInitializable.sol | 72 ++++++++++------ src/account/UpgradeableModularAccount.sol | 13 +-- src/interfaces/IPlugin.sol | 6 +- src/plugins/BasePlugin.sol | 6 +- src/plugins/TokenReceiverPlugin.sol | 34 +++----- src/plugins/owner/SingleOwnerPlugin.sol | 16 ++-- .../plugins/ModularSessionKeyPlugin.sol | 4 +- .../plugins/interfaces/ISessionKeyPlugin.sol | 2 +- .../interfaces/ITokenSessionKeyPlugin.sol | 2 +- standard/ERCs/erc-6900.md | 4 +- test/account/UpgradeableModularAccount.t.sol | 78 +++++++++--------- test/account/ValidationIntersection.t.sol | 22 ++--- test/comparison/CompareSimpleAccount.t.sol | 52 ++++++------ test/libraries/AccountStorage.t.sol | 2 +- test/mocks/MSCAFactoryFixture.sol | 2 +- test/mocks/MockERC721.sol | 12 +++ test/mocks/MockERC777.sol | 51 ------------ test/mocks/plugins/ComprehensivePlugin.sol | 6 +- test/mocks/plugins/ValidationPluginMocks.sol | 6 +- test/plugin/SingleOwnerPlugin.t.sol | 6 +- test/plugin/TokenReceiverPlugin.t.sol | 82 +++++++------------ .../plugins/ModularSessionKeyPlugin.t.sol | 14 ++-- test/utils/AccountTestBase.sol | 5 ++ 26 files changed, 228 insertions(+), 274 deletions(-) create mode 100644 test/mocks/MockERC721.sol delete mode 100644 test/mocks/MockERC777.sol diff --git a/lib/account-abstraction b/lib/account-abstraction index 187613b0..7af70c89 160000 --- a/lib/account-abstraction +++ b/lib/account-abstraction @@ -1 +1 @@ -Subproject commit 187613b0172c3a21cf3496e12cdfa24af04fb510 +Subproject commit 7af70c8993a6f42973f520ae0752386a5032abe7 diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts index e1b3d8c7..932fddf6 160000 --- a/lib/openzeppelin-contracts +++ b/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit e1b3d8c7ee2c97868f4ab107fe8b7e19f0a8db9f +Subproject commit 932fddf69a699a9a80fd2396fd1a2ab91cdda123 diff --git a/src/account/AccountLoupe.sol b/src/account/AccountLoupe.sol index d622efde..007036da 100644 --- a/src/account/AccountLoupe.sol +++ b/src/account/AccountLoupe.sol @@ -24,7 +24,6 @@ abstract contract AccountLoupe is IAccountLoupe { if ( selector == IStandardExecutor.execute.selector || selector == IStandardExecutor.executeBatch.selector - || selector == UUPSUpgradeable.upgradeTo.selector || selector == UUPSUpgradeable.upgradeToAndCall.selector || selector == IPluginManager.installPlugin.selector || selector == IPluginManager.uninstallPlugin.selector diff --git a/src/account/AccountStorageInitializable.sol b/src/account/AccountStorageInitializable.sol index 5131978b..debcee40 100644 --- a/src/account/AccountStorageInitializable.sol +++ b/src/account/AccountStorageInitializable.sol @@ -1,45 +1,67 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.25; -import {Address} from "@openzeppelin/contracts/utils/Address.sol"; - import {AccountStorage, getAccountStorage} from "./AccountStorage.sol"; +/// @title AccountStorageInitializable +/// @dev Bulk of the impl is lifted from OZ 5.0 Initializible abstract contract AccountStorageInitializable { - error AlreadyInitialized(); - error AlreadyInitializing(); + /** + * @dev Triggered when the contract has been initialized or reinitialized. + */ + event Initialized(uint64 version); + + /** + * @dev The contract is already initialized. + */ + error InvalidInitialization(); + + /** + * @dev The contract is not initializing. + */ + error NotInitializing(); /// @notice Modifier to put on function intended to be called only once per implementation /// @dev Reverts if the contract has already been initialized modifier initializer() { - AccountStorage storage _storage = getAccountStorage(); - bool isTopLevelCall = !_storage.initializing; - if ( - isTopLevelCall && _storage.initialized < 1 - || !Address.isContract(address(this)) && _storage.initialized == 1 - ) { - _storage.initialized = 1; - if (isTopLevelCall) { - _storage.initializing = true; - } - _; - if (isTopLevelCall) { - _storage.initializing = false; - } - } else { - revert AlreadyInitialized(); + AccountStorage storage $ = getAccountStorage(); + + // Cache values to avoid duplicated sloads + bool isTopLevelCall = !$.initializing; + uint64 initialized = $.initialized; + + // Allowed calls: + // - initialSetup: the contract is not in the initializing state and no previous version was + // initialized + // - construction: the contract is initialized at version 1 (no reininitialization) and the + // current contract is just being deployed + bool initialSetup = initialized == 0 && isTopLevelCall; + bool construction = initialized == 1 && address(this).code.length == 0; + + if (!initialSetup && !construction) { + revert InvalidInitialization(); + } + $.initialized = 1; + if (isTopLevelCall) { + $.initializing = true; + } + _; + if (isTopLevelCall) { + $.initializing = false; + emit Initialized(1); } } /// @notice Internal function to disable calls to initialization functions /// @dev Reverts if the contract has already been initialized function _disableInitializers() internal virtual { - AccountStorage storage _storage = getAccountStorage(); - if (_storage.initializing) { - revert AlreadyInitializing(); + AccountStorage storage $ = getAccountStorage(); + if ($.initializing) { + revert InvalidInitialization(); } - if (_storage.initialized != type(uint8).max) { - _storage.initialized = type(uint8).max; + if ($.initialized != type(uint8).max) { + $.initialized = type(uint8).max; + emit Initialized(type(uint8).max); } } } diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index b82f0ce6..cf9af40f 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.25; import {BaseAccount} from "@eth-infinitism/account-abstraction/core/BaseAccount.sol"; import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; -import {UserOperation} from "@eth-infinitism/account-abstraction/interfaces/UserOperation.sol"; +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; @@ -292,11 +292,6 @@ contract UpgradeableModularAccount is return getAccountStorage().supportedIfaces[interfaceId] > 0; } - /// @inheritdoc UUPSUpgradeable - function upgradeTo(address newImplementation) public override onlyProxy wrapNativeFunction { - _upgradeToAndCallUUPS(newImplementation, new bytes(0), false); - } - /// @inheritdoc UUPSUpgradeable function upgradeToAndCall(address newImplementation, bytes memory data) public @@ -305,7 +300,7 @@ contract UpgradeableModularAccount is onlyProxy wrapNativeFunction { - _upgradeToAndCallUUPS(newImplementation, data, true); + super.upgradeToAndCall(newImplementation, data); } /// @notice Gets the entry point for this account @@ -317,7 +312,7 @@ contract UpgradeableModularAccount is // INTERNAL FUNCTIONS // Parent function validateUserOp enforces that this call can only be made by the EntryPoint - function _validateSignature(UserOperation calldata userOp, bytes32 userOpHash) + function _validateSignature(PackedUserOperation calldata userOp, bytes32 userOpHash) internal virtual override @@ -337,7 +332,7 @@ contract UpgradeableModularAccount is function _doUserOpValidation( bytes4 selector, FunctionReference userOpValidationFunction, - UserOperation calldata userOp, + PackedUserOperation calldata userOp, bytes32 userOpHash ) internal returns (uint256 validationData) { if (userOpValidationFunction.isEmpty()) { diff --git a/src/interfaces/IPlugin.sol b/src/interfaces/IPlugin.sol index fa40af12..5049d5cd 100644 --- a/src/interfaces/IPlugin.sol +++ b/src/interfaces/IPlugin.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: CC0-1.0 pragma solidity ^0.8.25; -import {UserOperation} from "@eth-infinitism/account-abstraction/interfaces/UserOperation.sol"; +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; // Forge formatter will displace the first comment for the enum field out of the enum itself, // so annotating here to prevent that. @@ -115,7 +115,7 @@ interface IPlugin { /// @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(uint8 functionId, UserOperation calldata userOp, bytes32 userOpHash) + function preUserOpValidationHook(uint8 functionId, PackedUserOperation calldata userOp, bytes32 userOpHash) external returns (uint256); @@ -125,7 +125,7 @@ interface IPlugin { /// @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 userOpValidationFunction(uint8 functionId, UserOperation calldata userOp, bytes32 userOpHash) + function userOpValidationFunction(uint8 functionId, PackedUserOperation calldata userOp, bytes32 userOpHash) external returns (uint256); diff --git a/src/plugins/BasePlugin.sol b/src/plugins/BasePlugin.sol index 5a89b982..44145286 100644 --- a/src/plugins/BasePlugin.sol +++ b/src/plugins/BasePlugin.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.25; -import {UserOperation} from "@eth-infinitism/account-abstraction/interfaces/UserOperation.sol"; +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; import {IPlugin, PluginManifest, PluginMetadata} from "../interfaces/IPlugin.sol"; @@ -38,7 +38,7 @@ abstract contract BasePlugin is ERC165, IPlugin { /// @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(uint8 functionId, UserOperation calldata userOp, bytes32 userOpHash) + function preUserOpValidationHook(uint8 functionId, PackedUserOperation calldata userOp, bytes32 userOpHash) external virtual returns (uint256) @@ -53,7 +53,7 @@ abstract contract BasePlugin is ERC165, IPlugin { /// @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 userOpValidationFunction(uint8 functionId, UserOperation calldata userOp, bytes32 userOpHash) + function userOpValidationFunction(uint8 functionId, PackedUserOperation calldata userOp, bytes32 userOpHash) external virtual returns (uint256) diff --git a/src/plugins/TokenReceiverPlugin.sol b/src/plugins/TokenReceiverPlugin.sol index f22ae3ba..980d7397 100644 --- a/src/plugins/TokenReceiverPlugin.sol +++ b/src/plugins/TokenReceiverPlugin.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.25; import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; -import {IERC777Recipient} from "@openzeppelin/contracts/interfaces/IERC777Recipient.sol"; import {IERC1155Receiver} from "@openzeppelin/contracts/interfaces/IERC1155Receiver.sol"; import { @@ -18,7 +17,7 @@ import {BasePlugin} from "./BasePlugin.sol"; /// @author ERC-6900 Authors /// @notice This plugin allows modular accounts to receive various types of tokens by implementing /// required token receiver interfaces. -contract TokenReceiverPlugin is BasePlugin, IERC721Receiver, IERC777Recipient, IERC1155Receiver { +contract TokenReceiverPlugin is BasePlugin, IERC721Receiver, IERC1155Receiver { string public constant NAME = "Token Receiver Plugin"; string public constant VERSION = "1.0.0"; string public constant AUTHOR = "ERC-6900 Authors"; @@ -27,13 +26,6 @@ contract TokenReceiverPlugin is BasePlugin, IERC721Receiver, IERC777Recipient, I // ┃ Execution functions ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - function tokensReceived(address, address, address, uint256, bytes calldata, bytes calldata) - external - pure - override - // solhint-disable-next-line no-empty-blocks - {} - function onERC721Received(address, address, uint256, bytes calldata) external pure override returns (bytes4) { return IERC721Receiver.onERC721Received.selector; } @@ -72,11 +64,10 @@ contract TokenReceiverPlugin is BasePlugin, IERC721Receiver, IERC777Recipient, I function pluginManifest() external pure override returns (PluginManifest memory) { PluginManifest memory manifest; - manifest.executionFunctions = new bytes4[](4); - manifest.executionFunctions[0] = this.tokensReceived.selector; - manifest.executionFunctions[1] = this.onERC721Received.selector; - manifest.executionFunctions[2] = this.onERC1155Received.selector; - manifest.executionFunctions[3] = this.onERC1155BatchReceived.selector; + manifest.executionFunctions = new bytes4[](3); + manifest.executionFunctions[0] = this.onERC721Received.selector; + manifest.executionFunctions[1] = this.onERC1155Received.selector; + manifest.executionFunctions[2] = this.onERC1155BatchReceived.selector; // Only runtime validationFunction is needed since callbacks come from token contracts only ManifestFunction memory alwaysAllowRuntime = ManifestFunction({ @@ -84,28 +75,23 @@ contract TokenReceiverPlugin is BasePlugin, IERC721Receiver, IERC777Recipient, I functionId: 0, // Unused. dependencyIndex: 0 // Unused. }); - manifest.validationFunctions = new ManifestAssociatedFunction[](4); + manifest.validationFunctions = new ManifestAssociatedFunction[](3); manifest.validationFunctions[0] = ManifestAssociatedFunction({ - executionSelector: this.tokensReceived.selector, - associatedFunction: alwaysAllowRuntime - }); - manifest.validationFunctions[1] = ManifestAssociatedFunction({ executionSelector: this.onERC721Received.selector, associatedFunction: alwaysAllowRuntime }); - manifest.validationFunctions[2] = ManifestAssociatedFunction({ + manifest.validationFunctions[1] = ManifestAssociatedFunction({ executionSelector: this.onERC1155Received.selector, associatedFunction: alwaysAllowRuntime }); - manifest.validationFunctions[3] = ManifestAssociatedFunction({ + manifest.validationFunctions[2] = ManifestAssociatedFunction({ executionSelector: this.onERC1155BatchReceived.selector, associatedFunction: alwaysAllowRuntime }); - manifest.interfaceIds = new bytes4[](3); + manifest.interfaceIds = new bytes4[](2); manifest.interfaceIds[0] = type(IERC721Receiver).interfaceId; - manifest.interfaceIds[1] = type(IERC777Recipient).interfaceId; - manifest.interfaceIds[2] = type(IERC1155Receiver).interfaceId; + manifest.interfaceIds[1] = type(IERC1155Receiver).interfaceId; return manifest; } diff --git a/src/plugins/owner/SingleOwnerPlugin.sol b/src/plugins/owner/SingleOwnerPlugin.sol index 5e3b432a..b1d5d5e1 100644 --- a/src/plugins/owner/SingleOwnerPlugin.sol +++ b/src/plugins/owner/SingleOwnerPlugin.sol @@ -4,7 +4,8 @@ pragma solidity ^0.8.25; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; -import {UserOperation} from "@eth-infinitism/account-abstraction/interfaces/UserOperation.sol"; +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; +import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; import {IPluginManager} from "../../interfaces/IPluginManager.sol"; @@ -38,6 +39,7 @@ import {ISingleOwnerPlugin} from "./ISingleOwnerPlugin.sol"; /// send user operations through a bundler. contract SingleOwnerPlugin is BasePlugin, ISingleOwnerPlugin, IERC1271 { using ECDSA for bytes32; + using MessageHashUtils for bytes32; string public constant NAME = "Single Owner Plugin"; string public constant VERSION = "1.0.0"; @@ -91,7 +93,7 @@ contract SingleOwnerPlugin is BasePlugin, ISingleOwnerPlugin, IERC1271 { } /// @inheritdoc BasePlugin - function userOpValidationFunction(uint8 functionId, UserOperation calldata userOp, bytes32 userOpHash) + function userOpValidationFunction(uint8 functionId, PackedUserOperation calldata userOp, bytes32 userOpHash) external view override @@ -99,7 +101,7 @@ contract SingleOwnerPlugin is BasePlugin, ISingleOwnerPlugin, IERC1271 { { if (functionId == uint8(FunctionId.VALIDATION_OWNER_OR_SELF)) { // Validate the user op signature against the owner. - (address signer,) = (userOpHash.toEthSignedMessageHash()).tryRecover(userOp.signature); + (address signer,,) = (userOpHash.toEthSignedMessageHash()).tryRecover(userOp.signature); if (signer == address(0) || signer != _owners[msg.sender]) { return _SIG_VALIDATION_FAILED; } @@ -154,7 +156,7 @@ contract SingleOwnerPlugin is BasePlugin, ISingleOwnerPlugin, IERC1271 { functionId: uint8(FunctionId.VALIDATION_OWNER_OR_SELF), dependencyIndex: 0 // Unused. }); - manifest.validationFunctions = new ManifestAssociatedFunction[](8); + manifest.validationFunctions = new ManifestAssociatedFunction[](7); manifest.validationFunctions[0] = ManifestAssociatedFunction({ executionSelector: this.transferOwnership.selector, associatedFunction: ownerValidationFunction @@ -176,10 +178,6 @@ contract SingleOwnerPlugin is BasePlugin, ISingleOwnerPlugin, IERC1271 { associatedFunction: ownerValidationFunction }); manifest.validationFunctions[5] = ManifestAssociatedFunction({ - executionSelector: UUPSUpgradeable.upgradeTo.selector, - associatedFunction: ownerValidationFunction - }); - manifest.validationFunctions[6] = ManifestAssociatedFunction({ executionSelector: UUPSUpgradeable.upgradeToAndCall.selector, associatedFunction: ownerValidationFunction }); @@ -189,7 +187,7 @@ contract SingleOwnerPlugin is BasePlugin, ISingleOwnerPlugin, IERC1271 { functionId: 0, // Unused. dependencyIndex: 0 // Unused. }); - manifest.validationFunctions[7] = ManifestAssociatedFunction({ + manifest.validationFunctions[6] = ManifestAssociatedFunction({ executionSelector: this.isValidSignature.selector, associatedFunction: alwaysAllowRuntime }); diff --git a/src/samples/plugins/ModularSessionKeyPlugin.sol b/src/samples/plugins/ModularSessionKeyPlugin.sol index ceed7108..53b4c0eb 100644 --- a/src/samples/plugins/ModularSessionKeyPlugin.sol +++ b/src/samples/plugins/ModularSessionKeyPlugin.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.25; // import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; // import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -// import {UserOperation} from "@eth-infinitism/account-abstraction/interfaces/UserOperation.sol"; +// import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; // import {UpgradeableModularAccount} from "../../account/UpgradeableModularAccount.sol"; // import { // ManifestFunction, @@ -188,7 +188,7 @@ pragma solidity ^0.8.25; // } // /// @inheritdoc BasePlugin -// function userOpValidationFunction(uint8 functionId, UserOperation calldata userOp, bytes32 userOpHash) +// function userOpValidationFunction(uint8 functionId, PackedUserOperation calldata userOp, bytes32 userOpHash) // external // view // override diff --git a/src/samples/plugins/interfaces/ISessionKeyPlugin.sol b/src/samples/plugins/interfaces/ISessionKeyPlugin.sol index 99d94456..fd1b9bd0 100644 --- a/src/samples/plugins/interfaces/ISessionKeyPlugin.sol +++ b/src/samples/plugins/interfaces/ISessionKeyPlugin.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.25; -// import {UserOperation} from "@eth-infinitism/account-abstraction/interfaces/UserOperation.sol"; +// import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; // interface IModularSessionKeyPlugin { // enum FunctionId { diff --git a/src/samples/plugins/interfaces/ITokenSessionKeyPlugin.sol b/src/samples/plugins/interfaces/ITokenSessionKeyPlugin.sol index 09a3bf84..2de98c0a 100644 --- a/src/samples/plugins/interfaces/ITokenSessionKeyPlugin.sol +++ b/src/samples/plugins/interfaces/ITokenSessionKeyPlugin.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.25; -// import {UserOperation} from "@eth-infinitism/account-abstraction/interfaces/UserOperation.sol"; +// import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; // interface ITokenSessionKeyPlugin { // error NotAuthorized(); diff --git a/standard/ERCs/erc-6900.md b/standard/ERCs/erc-6900.md index 44bdb891..a9c86dcd 100644 --- a/standard/ERCs/erc-6900.md +++ b/standard/ERCs/erc-6900.md @@ -272,7 +272,7 @@ interface IPlugin { /// @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(uint8 functionId, UserOperation memory userOp, bytes32 userOpHash) external returns (uint256); + function preUserOpValidationHook(uint8 functionId, PackedUserOperation memory userOp, bytes32 userOpHash) external returns (uint256); /// @notice Run the user operation validationFunction specified by the `functionId`. /// @param functionId An identifier that routes the call to different internal implementations, should there be @@ -280,7 +280,7 @@ interface IPlugin { /// @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 userOpValidationFunction(uint8 functionId, UserOperation calldata userOp, bytes32 userOpHash) + function userOpValidationFunction(uint8 functionId, PackedUserOperation calldata userOp, bytes32 userOpHash) external returns (uint256); diff --git a/test/account/UpgradeableModularAccount.t.sol b/test/account/UpgradeableModularAccount.t.sol index 486f283b..3ef6f107 100644 --- a/test/account/UpgradeableModularAccount.t.sol +++ b/test/account/UpgradeableModularAccount.t.sol @@ -4,7 +4,8 @@ pragma solidity ^0.8.19; import {console} from "forge-std/Test.sol"; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import {UserOperation} from "@eth-infinitism/account-abstraction/interfaces/UserOperation.sol"; +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; +import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {PluginManagerInternals} from "../../src/account/PluginManagerInternals.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; @@ -23,6 +24,7 @@ import {AccountTestBase} from "../utils/AccountTestBase.sol"; contract UpgradeableModularAccountTest is AccountTestBase { using ECDSA for bytes32; + using MessageHashUtils for bytes32; TokenReceiverPlugin public tokenReceiverPlugin; @@ -62,16 +64,14 @@ contract UpgradeableModularAccountTest is AccountTestBase { } function test_postDeploy_ethSend() public { - UserOperation memory userOp = UserOperation({ + PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), nonce: 0, initCode: "", callData: abi.encodeCall(UpgradeableModularAccount.execute, (ethRecipient, 1 wei, "")), - callGasLimit: CALL_GAS_LIMIT, - verificationGasLimit: VERIFICATION_GAS_LIMIT, + accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), preVerificationGas: 0, - maxFeePerGas: 1, - maxPriorityFeePerGas: 1, + gasFees: _encodeGas(1, 1), paymasterAndData: "", signature: "" }); @@ -81,7 +81,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); userOp.signature = abi.encodePacked(r, s, v); - UserOperation[] memory userOps = new UserOperation[](1); + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; entryPoint.handleOps(userOps, beneficiary); @@ -90,16 +90,14 @@ contract UpgradeableModularAccountTest is AccountTestBase { } function test_basicUserOp_withInitCode() public { - UserOperation memory userOp = UserOperation({ + PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account2), nonce: 0, initCode: abi.encodePacked(address(factory), abi.encodeCall(factory.createAccount, (owner2, 0))), callData: abi.encodeCall(SingleOwnerPlugin.transferOwnership, (owner2)), - callGasLimit: CALL_GAS_LIMIT, - verificationGasLimit: VERIFICATION_GAS_LIMIT, + accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), preVerificationGas: 0, - maxFeePerGas: 2, - maxPriorityFeePerGas: 1, + gasFees: _encodeGas(1, 2), paymasterAndData: "", signature: "" }); @@ -109,7 +107,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); userOp.signature = abi.encodePacked(r, s, v); - UserOperation[] memory userOps = new UserOperation[](1); + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; entryPoint.handleOps(userOps, beneficiary); @@ -118,16 +116,14 @@ contract UpgradeableModularAccountTest is AccountTestBase { function test_standardExecuteEthSend_withInitcode() public { address payable recipient = payable(makeAddr("recipient")); - UserOperation memory userOp = UserOperation({ + PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account2), nonce: 0, initCode: abi.encodePacked(address(factory), abi.encodeCall(factory.createAccount, (owner2, 0))), callData: abi.encodeCall(UpgradeableModularAccount.execute, (recipient, 1 wei, "")), - callGasLimit: CALL_GAS_LIMIT, - verificationGasLimit: VERIFICATION_GAS_LIMIT, + accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), preVerificationGas: 0, - maxFeePerGas: 2, - maxPriorityFeePerGas: 1, + gasFees: _encodeGas(1, 1), paymasterAndData: "", signature: "" }); @@ -137,7 +133,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); userOp.signature = abi.encodePacked(r, s, v); - UserOperation[] memory userOps = new UserOperation[](1); + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; entryPoint.handleOps(userOps, beneficiary); @@ -146,16 +142,14 @@ contract UpgradeableModularAccountTest is AccountTestBase { } function test_debug_upgradeableModularAccount_storageAccesses() public { - UserOperation memory userOp = UserOperation({ + PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), nonce: 0, initCode: "", callData: abi.encodeCall(UpgradeableModularAccount.execute, (ethRecipient, 1 wei, "")), - callGasLimit: CALL_GAS_LIMIT, - verificationGasLimit: VERIFICATION_GAS_LIMIT, + accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), preVerificationGas: 0, - maxFeePerGas: 1, - maxPriorityFeePerGas: 1, + gasFees: _encodeGas(1, 1), paymasterAndData: "", signature: "" }); @@ -165,7 +159,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); userOp.signature = abi.encodePacked(r, s, v); - UserOperation[] memory userOps = new UserOperation[](1); + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; vm.record(); @@ -174,18 +168,16 @@ contract UpgradeableModularAccountTest is AccountTestBase { } function test_contractInteraction() public { - UserOperation memory userOp = UserOperation({ + PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), nonce: 0, initCode: "", callData: abi.encodeCall( UpgradeableModularAccount.execute, (address(counter), 0, abi.encodeCall(counter.increment, ())) ), - callGasLimit: CALL_GAS_LIMIT, - verificationGasLimit: VERIFICATION_GAS_LIMIT, + accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), preVerificationGas: 0, - maxFeePerGas: 1, - maxPriorityFeePerGas: 1, + gasFees: _encodeGas(1, 1), paymasterAndData: "", signature: "" }); @@ -195,7 +187,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); userOp.signature = abi.encodePacked(r, s, v); - UserOperation[] memory userOps = new UserOperation[](1); + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; entryPoint.handleOps(userOps, beneficiary); @@ -209,16 +201,14 @@ contract UpgradeableModularAccountTest is AccountTestBase { calls[0] = Call({target: ethRecipient, value: 1 wei, data: ""}); calls[1] = Call({target: address(counter), value: 0, data: abi.encodeCall(counter.increment, ())}); - UserOperation memory userOp = UserOperation({ + PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), nonce: 0, initCode: "", callData: abi.encodeCall(UpgradeableModularAccount.executeBatch, (calls)), - callGasLimit: CALL_GAS_LIMIT, - verificationGasLimit: VERIFICATION_GAS_LIMIT, + accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), preVerificationGas: 0, - maxFeePerGas: 1, - maxPriorityFeePerGas: 1, + gasFees: _encodeGas(1, 1), paymasterAndData: "", signature: "" }); @@ -228,7 +218,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); userOp.signature = abi.encodePacked(r, s, v); - UserOperation[] memory userOps = new UserOperation[](1); + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; entryPoint.handleOps(userOps, beneficiary); @@ -415,6 +405,20 @@ contract UpgradeableModularAccountTest is AccountTestBase { vm.stopPrank(); } + function test_upgradeToAndCall() public { + vm.startPrank(owner1); + UpgradeableModularAccount account3 = new UpgradeableModularAccount(entryPoint); + bytes32 slot = account3.proxiableUUID(); + + // account has impl from factory + assertEq( + address(factory.accountImplementation()), address(uint160(uint256(vm.load(address(account1), slot)))) + ); + account1.upgradeToAndCall(address(account3), bytes("")); + // account has new impl + assertEq(address(account3), address(uint160(uint256(vm.load(address(account1), slot))))); + } + // Internal Functions function _printStorageReadsAndWrites(address addr) internal { diff --git a/test/account/ValidationIntersection.t.sol b/test/account/ValidationIntersection.t.sol index f3b8e88a..7d451730 100644 --- a/test/account/ValidationIntersection.t.sol +++ b/test/account/ValidationIntersection.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {UserOperation} from "@eth-infinitism/account-abstraction/interfaces/UserOperation.sol"; +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {FunctionReference} from "../../src/helpers/FunctionReferenceLib.sol"; @@ -51,7 +51,7 @@ contract ValidationIntersectionTest is AccountTestBase { function testFuzz_validationIntersect_single(uint256 validationData) public { noHookPlugin.setValidationData(validationData); - UserOperation memory userOp; + PackedUserOperation memory userOp; userOp.callData = bytes.concat(noHookPlugin.foo.selector); bytes32 uoHash = entryPoint.getUserOpHash(userOp); @@ -67,7 +67,7 @@ contract ValidationIntersectionTest is AccountTestBase { 0 // returns OK ); - UserOperation memory userOp; + PackedUserOperation memory userOp; userOp.callData = bytes.concat(oneHookPlugin.bar.selector); bytes32 uoHash = entryPoint.getUserOpHash(userOp); @@ -84,7 +84,7 @@ contract ValidationIntersectionTest is AccountTestBase { _SIG_VALIDATION_FAILED ); - UserOperation memory userOp; + PackedUserOperation memory userOp; userOp.callData = bytes.concat(oneHookPlugin.bar.selector); bytes32 uoHash = entryPoint.getUserOpHash(userOp); @@ -106,7 +106,7 @@ contract ValidationIntersectionTest is AccountTestBase { _packValidationData(address(0), start1, end1), _packValidationData(address(0), start2, end2) ); - UserOperation memory userOp; + PackedUserOperation memory userOp; userOp.callData = bytes.concat(oneHookPlugin.bar.selector); bytes32 uoHash = entryPoint.getUserOpHash(userOp); @@ -127,7 +127,7 @@ contract ValidationIntersectionTest is AccountTestBase { _packValidationData(address(0), start2, end2), _packValidationData(address(0), start1, end1) ); - UserOperation memory userOp; + PackedUserOperation memory userOp; userOp.callData = bytes.concat(oneHookPlugin.bar.selector); bytes32 uoHash = entryPoint.getUserOpHash(userOp); @@ -146,7 +146,7 @@ contract ValidationIntersectionTest is AccountTestBase { // do. ); - UserOperation memory userOp; + PackedUserOperation memory userOp; userOp.callData = bytes.concat(oneHookPlugin.bar.selector); bytes32 uoHash = entryPoint.getUserOpHash(userOp); @@ -170,7 +170,7 @@ contract ValidationIntersectionTest is AccountTestBase { 0 // returns OK ); - UserOperation memory userOp; + PackedUserOperation memory userOp; userOp.callData = bytes.concat(oneHookPlugin.bar.selector); bytes32 uoHash = entryPoint.getUserOpHash(userOp); @@ -193,7 +193,7 @@ contract ValidationIntersectionTest is AccountTestBase { _packValidationData(goodAuthorizer, start1, end1), _packValidationData(address(0), start2, end2) ); - UserOperation memory userOp; + PackedUserOperation memory userOp; userOp.callData = bytes.concat(oneHookPlugin.bar.selector); bytes32 uoHash = entryPoint.getUserOpHash(userOp); @@ -216,7 +216,7 @@ contract ValidationIntersectionTest is AccountTestBase { _packValidationData(address(0), start2, end2) ); - UserOperation memory userOp; + PackedUserOperation memory userOp; userOp.callData = bytes.concat(twoHookPlugin.baz.selector); bytes32 uoHash = entryPoint.getUserOpHash(userOp); @@ -233,7 +233,7 @@ contract ValidationIntersectionTest is AccountTestBase { _SIG_VALIDATION_FAILED ); - UserOperation memory userOp; + PackedUserOperation memory userOp; userOp.callData = bytes.concat(twoHookPlugin.baz.selector); bytes32 uoHash = entryPoint.getUserOpHash(userOp); diff --git a/test/comparison/CompareSimpleAccount.t.sol b/test/comparison/CompareSimpleAccount.t.sol index e9ea4cc2..93b75ebe 100644 --- a/test/comparison/CompareSimpleAccount.t.sol +++ b/test/comparison/CompareSimpleAccount.t.sol @@ -4,8 +4,9 @@ pragma solidity ^0.8.19; import {Test} from "forge-std/Test.sol"; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.sol"; -import {UserOperation} from "@eth-infinitism/account-abstraction/interfaces/UserOperation.sol"; +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {SimpleAccount} from "@eth-infinitism/account-abstraction/samples/SimpleAccount.sol"; import {SimpleAccountFactory} from "@eth-infinitism/account-abstraction/samples/SimpleAccountFactory.sol"; @@ -14,6 +15,7 @@ import {Counter} from "../mocks/Counter.sol"; contract CompareSimpleAccountTest is Test { using ECDSA for bytes32; + using MessageHashUtils for bytes32; EntryPoint public entryPoint; address payable public beneficiary; @@ -32,6 +34,14 @@ contract CompareSimpleAccountTest is Test { Counter public counter; + uint256 public constant CALL_GAS_LIMIT = 500000; + uint256 public constant VERIFICATION_GAS_LIMIT = 500000; + + // helper function to compress 2 gas values into a single bytes32 + function _encodeGas(uint256 g1, uint256 g2) internal pure returns (bytes32) { + return bytes32(uint256((g1 << 128) + uint128(g2))); + } + function setUp() public { entryPoint = new EntryPoint(); (owner1, owner1Key) = makeAddrAndKey("owner1"); @@ -54,16 +64,14 @@ contract CompareSimpleAccountTest is Test { } function test_SimpleAccount_deploy_basicSend() public { - UserOperation memory userOp = UserOperation({ + PackedUserOperation memory userOp = PackedUserOperation({ sender: account1, nonce: 0, initCode: abi.encodePacked(address(factory), abi.encodeCall(factory.createAccount, (owner1, 0))), callData: abi.encodeCall(SimpleAccount.execute, (beneficiary, 1, "")), - callGasLimit: 5000000, - verificationGasLimit: 5000000, + accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), preVerificationGas: 0, - maxFeePerGas: 2, - maxPriorityFeePerGas: 1, + gasFees: _encodeGas(1, 2), paymasterAndData: "", signature: "" }); @@ -72,23 +80,21 @@ contract CompareSimpleAccountTest is Test { (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); userOp.signature = abi.encodePacked(r, s, v); - UserOperation[] memory userOps = new UserOperation[](1); + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; entryPoint.handleOps(userOps, beneficiary); } function test_SimpleAccount_deploy_empty() public { - UserOperation memory userOp = UserOperation({ + PackedUserOperation memory userOp = PackedUserOperation({ sender: account1, nonce: 0, initCode: abi.encodePacked(address(factory), abi.encodeCall(factory.createAccount, (owner1, 0))), callData: "", - callGasLimit: 5000000, - verificationGasLimit: 5000000, + accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), preVerificationGas: 0, - maxFeePerGas: 2, - maxPriorityFeePerGas: 1, + gasFees: _encodeGas(1, 2), paymasterAndData: "", signature: "" }); @@ -97,23 +103,21 @@ contract CompareSimpleAccountTest is Test { (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); userOp.signature = abi.encodePacked(r, s, v); - UserOperation[] memory userOps = new UserOperation[](1); + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; entryPoint.handleOps(userOps, beneficiary); } function test_SimpleAccount_postDeploy_basicSend() public { - UserOperation memory userOp = UserOperation({ + PackedUserOperation memory userOp = PackedUserOperation({ sender: account2, nonce: 0, initCode: "", callData: abi.encodeCall(SimpleAccount.execute, (beneficiary, 1, "")), - callGasLimit: 5000000, - verificationGasLimit: 5000000, + accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), preVerificationGas: 0, - maxFeePerGas: 2, - maxPriorityFeePerGas: 1, + gasFees: _encodeGas(1, 2), paymasterAndData: "", signature: "" }); @@ -122,25 +126,23 @@ contract CompareSimpleAccountTest is Test { (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); userOp.signature = abi.encodePacked(r, s, v); - UserOperation[] memory userOps = new UserOperation[](1); + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; entryPoint.handleOps(userOps, beneficiary); } function test_SimpleAccount_postDeploy_contractInteraction() public { - UserOperation memory userOp = UserOperation({ + PackedUserOperation memory userOp = PackedUserOperation({ sender: account2, nonce: 0, initCode: "", callData: abi.encodeCall( SimpleAccount.execute, (address(counter), 0, abi.encodeCall(Counter.increment, ())) ), - callGasLimit: 5000000, - verificationGasLimit: 5000000, + accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), preVerificationGas: 0, - maxFeePerGas: 2, - maxPriorityFeePerGas: 1, + gasFees: _encodeGas(1, 2), paymasterAndData: "", signature: "" }); @@ -149,7 +151,7 @@ contract CompareSimpleAccountTest is Test { (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); userOp.signature = abi.encodePacked(r, s, v); - UserOperation[] memory userOps = new UserOperation[](1); + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; entryPoint.handleOps(userOps, beneficiary); diff --git a/test/libraries/AccountStorage.t.sol b/test/libraries/AccountStorage.t.sol index 7cab9991..f59049a3 100644 --- a/test/libraries/AccountStorage.t.sol +++ b/test/libraries/AccountStorage.t.sol @@ -22,7 +22,7 @@ contract AccountStorageTest is Test { assertEq(uint256(vm.load(address(impl), _ACCOUNT_STORAGE_SLOT)), type(uint8).max); // should revert if we try to initialize again - vm.expectRevert(AccountStorageInitializable.AlreadyInitialized.selector); + vm.expectRevert(AccountStorageInitializable.InvalidInitialization.selector); impl.initialize(); } diff --git a/test/mocks/MSCAFactoryFixture.sol b/test/mocks/MSCAFactoryFixture.sol index 17be2cc0..cc8ae60a 100644 --- a/test/mocks/MSCAFactoryFixture.sol +++ b/test/mocks/MSCAFactoryFixture.sol @@ -44,7 +44,7 @@ contract MSCAFactoryFixture is OptimizedTest { /** * create an account, and return its address. * returns the address even if the account is already deployed. - * Note that during UserOperation execution, this method is called only if the account is not deployed. + * Note that during user operation execution, this method is called only if the account is not deployed. * This method returns an existing account address so that entryPoint.getSenderAddress() would work even after * account creation */ diff --git a/test/mocks/MockERC721.sol b/test/mocks/MockERC721.sol new file mode 100644 index 00000000..1139adf2 --- /dev/null +++ b/test/mocks/MockERC721.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; + +contract MockERC721 is ERC721 { + constructor(string memory name, string memory symbol) ERC721(name, symbol) {} + + function mint(address account, uint256 tokenId) external { + _mint(account, tokenId); + } +} diff --git a/test/mocks/MockERC777.sol b/test/mocks/MockERC777.sol deleted file mode 100644 index 18840db2..00000000 --- a/test/mocks/MockERC777.sol +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; - -import {IERC777} from "@openzeppelin/contracts/token/ERC777/IERC777.sol"; -import {IERC777Recipient} from "@openzeppelin/contracts/token/ERC777/IERC777Recipient.sol"; - -contract MockERC777 is IERC777 { - string public override name; - string public override symbol; - uint256 public override granularity; - uint256 public override totalSupply; - mapping(address => uint256) public override balanceOf; - - function mint(address to, uint256 amount) public { - balanceOf[to] += amount; - } - - function transfer(address to, uint256 amount) public returns (bool) { - return transferFrom(msg.sender, to, amount); - } - - function transferFrom(address from, address to, uint256 amount) public returns (bool) { - IERC777Recipient(to).tokensReceived(msg.sender, from, to, amount, bytes(""), bytes("")); - balanceOf[from] -= amount; - balanceOf[to] += amount; - return true; - } - - function send(address to, uint256 amount, bytes calldata) public override { - transferFrom(msg.sender, to, amount); - } - - function burn(uint256 amount, bytes calldata) external { - transferFrom(msg.sender, address(0), amount); - } - - function isOperatorFor(address, address) external pure returns (bool) { - return false; - } - - // solhint-disable-next-line no-empty-blocks - function authorizeOperator(address) external {} - // solhint-disable-next-line no-empty-blocks - function revokeOperator(address) external {} - // solhint-disable-next-line no-empty-blocks - function defaultOperators() external view returns (address[] memory a) {} - // solhint-disable-next-line no-empty-blocks - function operatorSend(address, address, uint256, bytes calldata, bytes calldata) external {} - // solhint-disable-next-line no-empty-blocks - function operatorBurn(address, uint256, bytes calldata, bytes calldata) external {} -} diff --git a/test/mocks/plugins/ComprehensivePlugin.sol b/test/mocks/plugins/ComprehensivePlugin.sol index d1d25004..2e2f2441 100644 --- a/test/mocks/plugins/ComprehensivePlugin.sol +++ b/test/mocks/plugins/ComprehensivePlugin.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {UserOperation} from "@eth-infinitism/account-abstraction/interfaces/UserOperation.sol"; +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import { ManifestExecutionHook, @@ -43,7 +43,7 @@ contract ComprehensivePlugin is BasePlugin { function onUninstall(bytes calldata) external override {} - function preUserOpValidationHook(uint8 functionId, UserOperation calldata, bytes32) + function preUserOpValidationHook(uint8 functionId, PackedUserOperation calldata, bytes32) external pure override @@ -57,7 +57,7 @@ contract ComprehensivePlugin is BasePlugin { revert NotImplemented(); } - function userOpValidationFunction(uint8 functionId, UserOperation calldata, bytes32) + function userOpValidationFunction(uint8 functionId, PackedUserOperation calldata, bytes32) external pure override diff --git a/test/mocks/plugins/ValidationPluginMocks.sol b/test/mocks/plugins/ValidationPluginMocks.sol index 7163c01b..937e5c65 100644 --- a/test/mocks/plugins/ValidationPluginMocks.sol +++ b/test/mocks/plugins/ValidationPluginMocks.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {UserOperation} from "@eth-infinitism/account-abstraction/interfaces/UserOperation.sol"; +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import { ManifestFunction, @@ -30,7 +30,7 @@ abstract contract MockBaseUserOpValidationPlugin is BaseTestPlugin { function onUninstall(bytes calldata) external override {} - function preUserOpValidationHook(uint8 functionId, UserOperation calldata, bytes32) + function preUserOpValidationHook(uint8 functionId, PackedUserOperation calldata, bytes32) external view override @@ -44,7 +44,7 @@ abstract contract MockBaseUserOpValidationPlugin is BaseTestPlugin { revert NotImplemented(); } - function userOpValidationFunction(uint8 functionId, UserOperation calldata, bytes32) + function userOpValidationFunction(uint8 functionId, PackedUserOperation calldata, bytes32) external view override diff --git a/test/plugin/SingleOwnerPlugin.t.sol b/test/plugin/SingleOwnerPlugin.t.sol index 5784059e..dea5b2f9 100644 --- a/test/plugin/SingleOwnerPlugin.t.sol +++ b/test/plugin/SingleOwnerPlugin.t.sol @@ -2,8 +2,9 @@ pragma solidity ^0.8.19; import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.sol"; -import {UserOperation} from "@eth-infinitism/account-abstraction/interfaces/UserOperation.sol"; +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {SingleOwnerPlugin} from "../../src/plugins/owner/SingleOwnerPlugin.sol"; import {ISingleOwnerPlugin} from "../../src/plugins/owner/ISingleOwnerPlugin.sol"; @@ -12,6 +13,7 @@ import {OptimizedTest} from "../utils/OptimizedTest.sol"; contract SingleOwnerPluginTest is OptimizedTest { using ECDSA for bytes32; + using MessageHashUtils for bytes32; SingleOwnerPlugin public plugin; EntryPoint public entryPoint; @@ -123,7 +125,7 @@ contract SingleOwnerPluginTest is OptimizedTest { ); } - function testFuzz_validateUserOpSig(string memory salt, UserOperation memory userOp) public { + function testFuzz_validateUserOpSig(string memory salt, PackedUserOperation memory userOp) public { // range bound the possible set of priv keys (address signer, uint256 privateKey) = makeAddrAndKey(salt); diff --git a/test/plugin/TokenReceiverPlugin.t.sol b/test/plugin/TokenReceiverPlugin.t.sol index 49692e34..7a7433af 100644 --- a/test/plugin/TokenReceiverPlugin.t.sol +++ b/test/plugin/TokenReceiverPlugin.t.sol @@ -3,9 +3,6 @@ pragma solidity ^0.8.19; import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; -import {ERC721PresetMinterPauserAutoId} from - "@openzeppelin/contracts/token/ERC721/presets/ERC721PresetMinterPauserAutoId.sol"; -import {IERC777Recipient} from "@openzeppelin/contracts/token/ERC777/IERC777Recipient.sol"; import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; @@ -13,7 +10,7 @@ import {FunctionReference} from "../../src/helpers/FunctionReferenceLib.sol"; import {TokenReceiverPlugin} from "../../src/plugins/TokenReceiverPlugin.sol"; import {MSCAFactoryFixture} from "../mocks/MSCAFactoryFixture.sol"; -import {MockERC777} from "../mocks/MockERC777.sol"; +import {MockERC721} from "../mocks/MockERC721.sol"; import {MockERC1155} from "../mocks/MockERC1155.sol"; import {OptimizedTest} from "../utils/OptimizedTest.sol"; @@ -21,9 +18,8 @@ contract TokenReceiverPluginTest is OptimizedTest, IERC1155Receiver { UpgradeableModularAccount public acct; TokenReceiverPlugin public plugin; - ERC721PresetMinterPauserAutoId public t0; - MockERC777 public t1; - MockERC1155 public t2; + MockERC721 public t0; + MockERC1155 public t1; // init dynamic length arrays for use in args address[] public defaultOperators; @@ -41,16 +37,13 @@ contract TokenReceiverPluginTest is OptimizedTest, IERC1155Receiver { acct = factory.createAccount(address(this), 0); plugin = _deployTokenReceiverPlugin(); - t0 = new ERC721PresetMinterPauserAutoId("t0", "t0", ""); - t0.mint(address(this)); + t0 = new MockERC721("t0", "t0"); + t0.mint(address(this), _TOKEN_ID); - t1 = new MockERC777(); - t1.mint(address(this), _TOKEN_AMOUNT); - - t2 = new MockERC1155(); - t2.mint(address(this), _TOKEN_ID, _TOKEN_AMOUNT); + t1 = new MockERC1155(); + t1.mint(address(this), _TOKEN_ID, _TOKEN_AMOUNT); for (uint256 i = 1; i < _BATCH_TOKEN_IDS; i++) { - t2.mint(address(this), i, _TOKEN_AMOUNT); + t1.mint(address(this), i, _TOKEN_AMOUNT); tokenIds.push(i); tokenAmts.push(_TOKEN_AMOUNT); zeroTokenAmts.push(0); @@ -81,54 +74,45 @@ contract TokenReceiverPluginTest is OptimizedTest, IERC1155Receiver { assertEq(t0.ownerOf(_TOKEN_ID), address(acct)); } - function test_failERC777Transfer() public { + function test_failERC1155Transfer() public { + // for 1155, reverts are caught in a try catch and bubbled up with the reason from the account vm.expectRevert( abi.encodePacked( UpgradeableModularAccount.UnrecognizedFunction.selector, - IERC777Recipient.tokensReceived.selector, + IERC1155Receiver.onERC1155Received.selector, bytes28(0) ) ); - t1.transfer(address(acct), _TOKEN_AMOUNT); - } - - function test_passERC777Transfer() public { - _initPlugin(); - - assertEq(t1.balanceOf(address(this)), _TOKEN_AMOUNT); - assertEq(t1.balanceOf(address(acct)), 0); - t1.transfer(address(acct), _TOKEN_AMOUNT); - assertEq(t1.balanceOf(address(this)), 0); - assertEq(t1.balanceOf(address(acct)), _TOKEN_AMOUNT); - } - - function test_failERC1155Transfer() public { - // for 1155, reverts are caught in a try catch and bubbled up with a diff reason - vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); - t2.safeTransferFrom(address(this), address(acct), _TOKEN_ID, _TOKEN_AMOUNT, ""); + t1.safeTransferFrom(address(this), address(acct), _TOKEN_ID, _TOKEN_AMOUNT, ""); - // for 1155, reverts are caught in a try catch and bubbled up with a diff reason - vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); - t2.safeBatchTransferFrom(address(this), address(acct), tokenIds, tokenAmts, ""); + // for 1155, reverts are caught in a try catch and bubbled up with the reason from the account + vm.expectRevert( + abi.encodePacked( + UpgradeableModularAccount.UnrecognizedFunction.selector, + IERC1155Receiver.onERC1155BatchReceived.selector, + bytes28(0) + ) + ); + t1.safeBatchTransferFrom(address(this), address(acct), tokenIds, tokenAmts, ""); } function test_passERC1155Transfer() public { _initPlugin(); - assertEq(t2.balanceOf(address(this), _TOKEN_ID), _TOKEN_AMOUNT); - assertEq(t2.balanceOf(address(acct), _TOKEN_ID), 0); - t2.safeTransferFrom(address(this), address(acct), _TOKEN_ID, _TOKEN_AMOUNT, ""); - assertEq(t2.balanceOf(address(this), _TOKEN_ID), 0); - assertEq(t2.balanceOf(address(acct), _TOKEN_ID), _TOKEN_AMOUNT); + assertEq(t1.balanceOf(address(this), _TOKEN_ID), _TOKEN_AMOUNT); + assertEq(t1.balanceOf(address(acct), _TOKEN_ID), 0); + t1.safeTransferFrom(address(this), address(acct), _TOKEN_ID, _TOKEN_AMOUNT, ""); + assertEq(t1.balanceOf(address(this), _TOKEN_ID), 0); + assertEq(t1.balanceOf(address(acct), _TOKEN_ID), _TOKEN_AMOUNT); for (uint256 i = 1; i < _BATCH_TOKEN_IDS; i++) { - assertEq(t2.balanceOf(address(this), i), _TOKEN_AMOUNT); - assertEq(t2.balanceOf(address(acct), i), 0); + assertEq(t1.balanceOf(address(this), i), _TOKEN_AMOUNT); + assertEq(t1.balanceOf(address(acct), i), 0); } - t2.safeBatchTransferFrom(address(this), address(acct), tokenIds, tokenAmts, ""); + t1.safeBatchTransferFrom(address(this), address(acct), tokenIds, tokenAmts, ""); for (uint256 i = 1; i < _BATCH_TOKEN_IDS; i++) { - assertEq(t2.balanceOf(address(this), i), 0); - assertEq(t2.balanceOf(address(acct), i), _TOKEN_AMOUNT); + assertEq(t1.balanceOf(address(this), i), 0); + assertEq(t1.balanceOf(address(acct), i), _TOKEN_AMOUNT); } } @@ -137,8 +121,6 @@ contract TokenReceiverPluginTest is OptimizedTest, IERC1155Receiver { isSupported = acct.supportsInterface(type(IERC721Receiver).interfaceId); assertEq(isSupported, false); - isSupported = acct.supportsInterface(type(IERC777Recipient).interfaceId); - assertEq(isSupported, false); isSupported = acct.supportsInterface(type(IERC1155Receiver).interfaceId); assertEq(isSupported, false); } @@ -150,8 +132,6 @@ contract TokenReceiverPluginTest is OptimizedTest, IERC1155Receiver { isSupported = acct.supportsInterface(type(IERC721Receiver).interfaceId); assertEq(isSupported, true); - isSupported = acct.supportsInterface(type(IERC777Recipient).interfaceId); - assertEq(isSupported, true); isSupported = acct.supportsInterface(type(IERC1155Receiver).interfaceId); assertEq(isSupported, true); } diff --git a/test/samples/plugins/ModularSessionKeyPlugin.t.sol b/test/samples/plugins/ModularSessionKeyPlugin.t.sol index 5001e56f..92ee87f6 100644 --- a/test/samples/plugins/ModularSessionKeyPlugin.t.sol +++ b/test/samples/plugins/ModularSessionKeyPlugin.t.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.19; // import {Test} from "forge-std/Test.sol"; // import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.sol"; -// import {UserOperation} from "@eth-infinitism/account-abstraction/interfaces/UserOperation.sol"; +// import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; // import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; // import {SingleOwnerPlugin} from "../../../src/plugins/owner/SingleOwnerPlugin.sol"; @@ -196,9 +196,9 @@ pragma solidity ^0.8.19; // } // function test_sessionKey_userOp() public { -// UserOperation[] memory userOps = new UserOperation[](1); +// PackedUserOperation[] memory userOps = new PackedUserOperation[](1); -// (, UserOperation memory userOp) = _constructUserOp(address(mockERC20), address(account), target, 1 +// (, PackedUserOperation memory userOp) = _constructUserOp(address(mockERC20), address(account), target, 1 // ether); // userOps[0] = userOp; @@ -252,10 +252,10 @@ pragma solidity ^0.8.19; // function test_sessionKey_invalidContractFails() public { // address wrongERC20Contract = makeAddr("wrongERC20Contract"); -// (bytes32 userOpHash, UserOperation memory userOp) = +// (bytes32 userOpHash, PackedUserOperation memory userOp) = // _constructUserOp(address(wrongERC20Contract), address(account), target, 1 ether); -// UserOperation[] memory userOps = new UserOperation[](1); +// PackedUserOperation[] memory userOps = new PackedUserOperation[](1); // userOps[0] = userOp; // bytes memory revertCallData = abi.encodeWithSelector( @@ -346,12 +346,12 @@ pragma solidity ^0.8.19; // function _constructUserOp(address targetContract, address from, address to, uint256 amount) // internal // view -// returns (bytes32, UserOperation memory) +// returns (bytes32, PackedUserOperation memory) // { // bytes memory userOpCallData = // abi.encodeCall(TokenSessionKeyPlugin.transferFromSessionKey, (targetContract, from, to, amount)); -// UserOperation memory userOp = UserOperation({ +// PackedUserOperation memory userOp = PackedUserOperation({ // sender: address(account), // nonce: 0, // initCode: "", diff --git a/test/utils/AccountTestBase.sol b/test/utils/AccountTestBase.sol index 9738d3ab..5d9880f9 100644 --- a/test/utils/AccountTestBase.sol +++ b/test/utils/AccountTestBase.sol @@ -39,4 +39,9 @@ abstract contract AccountTestBase is OptimizedTest { vm.prank(owner1); SingleOwnerPlugin(address(account1)).transferOwnership(address(this)); } + + // helper function to compress 2 gas values into a single bytes32 + function _encodeGas(uint256 g1, uint256 g2) internal pure returns (bytes32) { + return bytes32(uint256((g1 << 128) + uint128(g2))); + } } From f00dba4893e09b9855f16eaa21bcf371f9aee7b2 Mon Sep 17 00:00:00 2001 From: adam-alchemy <127769144+adam-alchemy@users.noreply.github.com> Date: Fri, 3 May 2024 12:41:40 -0700 Subject: [PATCH 008/111] feat: [v0.8-develop] remove hook overlap support (#47) --- src/account/AccountLoupe.sol | 52 ++----- src/account/AccountStorage.sol | 24 ++-- src/account/PluginManagerInternals.sol | 89 ++++++------ src/account/UpgradeableModularAccount.sol | 157 ++++++++++------------ src/helpers/FunctionReferenceLib.sol | 4 + src/interfaces/IPlugin.sol | 7 +- standard/ERCs/erc-6900.md | 11 +- test/account/AccountExecHooks.t.sol | 93 ++++++++++--- 8 files changed, 218 insertions(+), 219 deletions(-) diff --git a/src/account/AccountLoupe.sol b/src/account/AccountLoupe.sol index 007036da..d84b6d20 100644 --- a/src/account/AccountLoupe.sol +++ b/src/account/AccountLoupe.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.25; import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; -import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {IAccountLoupe} from "../interfaces/IAccountLoupe.sol"; @@ -11,7 +10,7 @@ import {IStandardExecutor} from "../interfaces/IStandardExecutor.sol"; import {AccountStorage, getAccountStorage, SelectorData, toFunctionReferenceArray} from "./AccountStorage.sol"; abstract contract AccountLoupe is IAccountLoupe { - using EnumerableMap for EnumerableMap.Bytes32ToUintMap; + using EnumerableSet for EnumerableSet.Bytes32Set; using EnumerableSet for EnumerableSet.AddressSet; /// @inheritdoc IAccountLoupe @@ -41,56 +40,21 @@ abstract contract AccountLoupe is IAccountLoupe { SelectorData storage selectorData = getAccountStorage().selectorData[selector]; uint256 preExecHooksLength = selectorData.preHooks.length(); uint256 postOnlyExecHooksLength = selectorData.postOnlyHooks.length(); - uint256 maxExecHooksLength = postOnlyExecHooksLength; - // There can only be as many associated post hooks to run as there are pre hooks. - for (uint256 i = 0; i < preExecHooksLength; ++i) { - (, uint256 count) = selectorData.preHooks.at(i); - unchecked { - maxExecHooksLength += (count + 1); - } - } - - // Overallocate on length - not all of this may get filled up. We set the correct length later. - execHooks = new ExecutionHooks[](maxExecHooksLength); - uint256 actualExecHooksLength; + execHooks = new ExecutionHooks[](preExecHooksLength + postOnlyExecHooksLength); for (uint256 i = 0; i < preExecHooksLength; ++i) { - (bytes32 key,) = selectorData.preHooks.at(i); + bytes32 key = selectorData.preHooks.at(i); FunctionReference preExecHook = FunctionReference.wrap(bytes21(key)); + FunctionReference associatedPostExecHook = selectorData.associatedPostHooks[preExecHook]; - uint256 associatedPostExecHooksLength = selectorData.associatedPostHooks[preExecHook].length(); - if (associatedPostExecHooksLength > 0) { - for (uint256 j = 0; j < associatedPostExecHooksLength; ++j) { - execHooks[actualExecHooksLength].preExecHook = preExecHook; - (key,) = selectorData.associatedPostHooks[preExecHook].at(j); - execHooks[actualExecHooksLength].postExecHook = FunctionReference.wrap(bytes21(key)); - - unchecked { - ++actualExecHooksLength; - } - } - } else { - execHooks[actualExecHooksLength].preExecHook = preExecHook; - - unchecked { - ++actualExecHooksLength; - } - } + execHooks[i].preExecHook = preExecHook; + execHooks[i].postExecHook = associatedPostExecHook; } for (uint256 i = 0; i < postOnlyExecHooksLength; ++i) { - (bytes32 key,) = selectorData.postOnlyHooks.at(i); - execHooks[actualExecHooksLength].postExecHook = FunctionReference.wrap(bytes21(key)); - - unchecked { - ++actualExecHooksLength; - } - } - - // Trim the exec hooks array to the actual length, since we may have overallocated. - assembly ("memory-safe") { - mstore(execHooks, actualExecHooksLength) + bytes32 key = selectorData.postOnlyHooks.at(i); + execHooks[preExecHooksLength + i].postExecHook = FunctionReference.wrap(bytes21(key)); } } diff --git a/src/account/AccountStorage.sol b/src/account/AccountStorage.sol index 35f4dc3d..728f7f5c 100644 --- a/src/account/AccountStorage.sol +++ b/src/account/AccountStorage.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.25; -import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {IPlugin} from "../interfaces/IPlugin.sol"; @@ -35,15 +34,20 @@ struct SelectorData { // The plugin that implements this execution function. // If this is a native function, the address must remain address(0). address plugin; + // How many times a `PRE_HOOK_ALWAYS_DENY` has been added for this function. + // Since that is the only type of hook that may overlap, we can use this to track the number of times it has + // been applied, and whether or not the deny should apply. The size `uint48` was chosen somewhat arbitrarily, + // but it packs alongside `plugin` while still leaving some other space in the slot for future packing. + uint48 denyExecutionCount; // User operation validation and runtime validation share a function reference. FunctionReference validation; // The pre validation hooks for this function selector. - EnumerableMap.Bytes32ToUintMap preValidationHooks; + EnumerableSet.Bytes32Set preValidationHooks; // The execution hooks for this function selector. - EnumerableMap.Bytes32ToUintMap preHooks; + EnumerableSet.Bytes32Set preHooks; // bytes21 key = pre hook function reference - mapping(FunctionReference => EnumerableMap.Bytes32ToUintMap) associatedPostHooks; - EnumerableMap.Bytes32ToUintMap postOnlyHooks; + mapping(FunctionReference => FunctionReference) associatedPostHooks; + EnumerableSet.Bytes32Set postOnlyHooks; } struct AccountStorage { @@ -73,17 +77,17 @@ function getPermittedCallKey(address addr, bytes4 selector) pure returns (bytes2 return bytes24(bytes20(addr)) | (bytes24(selector) >> 160); } -// Helper function to get all elements of a set into memory. -using EnumerableMap for EnumerableMap.Bytes32ToUintMap; +using EnumerableSet for EnumerableSet.Bytes32Set; -function toFunctionReferenceArray(EnumerableMap.Bytes32ToUintMap storage map) +/// @dev Helper function to get all elements of a set into memory. +function toFunctionReferenceArray(EnumerableSet.Bytes32Set storage set) view returns (FunctionReference[] memory) { - uint256 length = map.length(); + uint256 length = set.length(); FunctionReference[] memory result = new FunctionReference[](length); for (uint256 i = 0; i < length; ++i) { - (bytes32 key,) = map.at(i); + bytes32 key = set.at(i); result[i] = FunctionReference.wrap(bytes21(key)); } return result; diff --git a/src/account/PluginManagerInternals.sol b/src/account/PluginManagerInternals.sol index a3734c4c..1d2ee5b8 100644 --- a/src/account/PluginManagerInternals.sol +++ b/src/account/PluginManagerInternals.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.25; import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; -import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {FunctionReferenceLib} from "../helpers/FunctionReferenceLib.sol"; @@ -25,7 +24,7 @@ import { } from "./AccountStorage.sol"; abstract contract PluginManagerInternals is IPluginManager { - using EnumerableMap for EnumerableMap.Bytes32ToUintMap; + using EnumerableSet for EnumerableSet.Bytes32Set; using EnumerableSet for EnumerableSet.AddressSet; using FunctionReferenceLib for FunctionReference; @@ -81,7 +80,7 @@ abstract contract PluginManagerInternals is IPluginManager { { SelectorData storage _selectorData = getAccountStorage().selectorData[selector]; - if (!_selectorData.validation.isEmpty()) { + if (_selectorData.validation.notEmpty()) { revert ValidationFunctionAlreadySet(selector, validationFunction); } @@ -102,20 +101,23 @@ abstract contract PluginManagerInternals is IPluginManager { { SelectorData storage _selectorData = getAccountStorage().selectorData[selector]; - if (!preExecHook.isEmpty()) { - _addOrIncrement(_selectorData.preHooks, _toSetValue(preExecHook)); + if (preExecHook.notEmpty()) { + // Don't need to check for duplicates, as the hook can be run at most once. + _selectorData.preHooks.add(_toSetValue(preExecHook)); - if (!postExecHook.isEmpty()) { - _addOrIncrement(_selectorData.associatedPostHooks[preExecHook], _toSetValue(postExecHook)); - } - } else { - if (postExecHook.isEmpty()) { - // both pre and post hooks cannot be null - revert NullFunctionReference(); + if (postExecHook.notEmpty()) { + _selectorData.associatedPostHooks[preExecHook] = postExecHook; } - _addOrIncrement(_selectorData.postOnlyHooks, _toSetValue(postExecHook)); + return; + } + + if (postExecHook.isEmpty()) { + // both pre and post hooks cannot be null + revert NullFunctionReference(); } + + _selectorData.postOnlyHooks.add(_toSetValue(postExecHook)); } function _removeExecHooks(bytes4 selector, FunctionReference preExecHook, FunctionReference postExecHook) @@ -123,37 +125,47 @@ abstract contract PluginManagerInternals is IPluginManager { { SelectorData storage _selectorData = getAccountStorage().selectorData[selector]; - if (!preExecHook.isEmpty()) { - _removeOrDecrement(_selectorData.preHooks, _toSetValue(preExecHook)); + if (preExecHook.notEmpty()) { + _selectorData.preHooks.remove(_toSetValue(preExecHook)); - if (!postExecHook.isEmpty()) { - _removeOrDecrement(_selectorData.associatedPostHooks[preExecHook], _toSetValue(postExecHook)); + if (postExecHook.notEmpty()) { + _selectorData.associatedPostHooks[preExecHook] = FunctionReferenceLib._EMPTY_FUNCTION_REFERENCE; } - } else { - // The case where both pre and post hooks are null was checked during installation. - // May ignore return value, as the manifest hash is validated to ensure that the hook exists. - _removeOrDecrement(_selectorData.postOnlyHooks, _toSetValue(postExecHook)); + return; } + + // The case where both pre and post hooks are null was checked during installation. + + // May ignore return value, as the manifest hash is validated to ensure that the hook exists. + _selectorData.postOnlyHooks.remove(_toSetValue(postExecHook)); } function _addPreValidationHook(bytes4 selector, FunctionReference preValidationHook) internal notNullFunction(preValidationHook) { - _addOrIncrement( - getAccountStorage().selectorData[selector].preValidationHooks, _toSetValue(preValidationHook) - ); + SelectorData storage _selectorData = getAccountStorage().selectorData[selector]; + if (preValidationHook.eq(FunctionReferenceLib._PRE_HOOK_ALWAYS_DENY)) { + // Increment `denyExecutionCount`, because this pre validation hook may be applied multiple times. + _selectorData.denyExecutionCount += 1; + return; + } + _selectorData.preValidationHooks.add(_toSetValue(preValidationHook)); } function _removePreValidationHook(bytes4 selector, FunctionReference preValidationHook) internal notNullFunction(preValidationHook) { + SelectorData storage _selectorData = getAccountStorage().selectorData[selector]; + if (preValidationHook.eq(FunctionReferenceLib._PRE_HOOK_ALWAYS_DENY)) { + // Decrement `denyExecutionCount`, because this pre exec hook may be applied multiple times. + _selectorData.denyExecutionCount -= 1; + return; + } // May ignore return value, as the manifest hash is validated to ensure that the hook exists. - _removeOrDecrement( - getAccountStorage().selectorData[selector].preValidationHooks, _toSetValue(preValidationHook) - ); + _selectorData.preValidationHooks.remove(_toSetValue(preValidationHook)); } function _installPlugin( @@ -290,7 +302,7 @@ abstract contract PluginManagerInternals is IPluginManager { _addExecHooks( mh.executionSelector, _resolveManifestFunction( - mh.preExecHook, plugin, emptyDependencies, ManifestAssociatedFunctionType.PRE_HOOK_ALWAYS_DENY + mh.preExecHook, plugin, emptyDependencies, ManifestAssociatedFunctionType.NONE ), _resolveManifestFunction( mh.postExecHook, plugin, emptyDependencies, ManifestAssociatedFunctionType.NONE @@ -356,7 +368,7 @@ abstract contract PluginManagerInternals is IPluginManager { _removeExecHooks( mh.executionSelector, _resolveManifestFunction( - mh.preExecHook, plugin, emptyDependencies, ManifestAssociatedFunctionType.PRE_HOOK_ALWAYS_DENY + mh.preExecHook, plugin, emptyDependencies, ManifestAssociatedFunctionType.NONE ), _resolveManifestFunction( mh.postExecHook, plugin, emptyDependencies, ManifestAssociatedFunctionType.NONE @@ -449,25 +461,6 @@ abstract contract PluginManagerInternals is IPluginManager { emit PluginUninstalled(plugin, onUninstallSuccess); } - function _addOrIncrement(EnumerableMap.Bytes32ToUintMap storage map, bytes32 key) internal { - (bool success, uint256 value) = map.tryGet(key); - map.set(key, success ? value + 1 : 0); - } - - /// @return True if the key was removed or its value was decremented, false if the key was not found. - function _removeOrDecrement(EnumerableMap.Bytes32ToUintMap storage map, bytes32 key) internal returns (bool) { - (bool success, uint256 value) = map.tryGet(key); - if (!success) { - return false; - } - if (value == 0) { - map.remove(key); - } else { - map.set(key, value - 1); - } - return true; - } - function _toSetValue(FunctionReference functionReference) internal pure returns (bytes32) { return bytes32(FunctionReference.unwrap(functionReference)); } diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index cf9af40f..3161eb45 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -6,7 +6,6 @@ import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntry import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {FunctionReferenceLib} from "../helpers/FunctionReferenceLib.sol"; @@ -32,7 +31,6 @@ contract UpgradeableModularAccount is PluginManagerInternals, UUPSUpgradeable { - using EnumerableMap for EnumerableMap.Bytes32ToUintMap; using EnumerableSet for EnumerableSet.Bytes32Set; using FunctionReferenceLib for FunctionReference; @@ -323,6 +321,12 @@ contract UpgradeableModularAccount is } bytes4 selector = bytes4(userOp.callData); + AccountStorage storage _storage = getAccountStorage(); + + if (_storage.selectorData[selector].denyExecutionCount > 0) { + revert AlwaysDenyRule(); + } + FunctionReference userOpValidationFunction = getAccountStorage().selectorData[selector].validation; validationData = _doUserOpValidation(selector, userOpValidationFunction, userOp, userOpHash); @@ -335,83 +339,72 @@ contract UpgradeableModularAccount is PackedUserOperation calldata userOp, bytes32 userOpHash ) internal returns (uint256 validationData) { - if (userOpValidationFunction.isEmpty()) { + if (userOpValidationFunction.isEmptyOrMagicValue()) { + // If the validation function is empty, then the call cannot proceed. + // Alternatively, the validation function may be set to the RUNTIME_VALIDATION_ALWAYS_ALLOW magic + // value, in which case we also revert. revert UserOpValidationFunctionMissing(selector); } uint256 currentValidationData; // Do preUserOpValidation hooks - EnumerableMap.Bytes32ToUintMap storage preUserOpValidationHooks = + EnumerableSet.Bytes32Set storage preUserOpValidationHooks = getAccountStorage().selectorData[selector].preValidationHooks; uint256 preUserOpValidationHooksLength = preUserOpValidationHooks.length(); for (uint256 i = 0; i < preUserOpValidationHooksLength; ++i) { - (bytes32 key,) = preUserOpValidationHooks.at(i); + bytes32 key = preUserOpValidationHooks.at(i); FunctionReference preUserOpValidationHook = _toFunctionReference(key); - if (!preUserOpValidationHook.isEmptyOrMagicValue()) { - (address plugin, uint8 functionId) = preUserOpValidationHook.unpack(); - currentValidationData = IPlugin(plugin).preUserOpValidationHook(functionId, userOp, userOpHash); + (address plugin, uint8 functionId) = preUserOpValidationHook.unpack(); + currentValidationData = IPlugin(plugin).preUserOpValidationHook(functionId, userOp, userOpHash); - if (uint160(currentValidationData) > 1) { - // If the aggregator is not 0 or 1, it is an unexpected value - revert UnexpectedAggregator(plugin, functionId, address(uint160(currentValidationData))); - } - validationData = _coalescePreValidation(validationData, currentValidationData); - } else { - // Function reference cannot be 0 and _RUNTIME_VALIDATION_ALWAYS_ALLOW is not permitted here. - revert InvalidConfiguration(); + if (uint160(currentValidationData) > 1) { + // If the aggregator is not 0 or 1, it is an unexpected value + revert UnexpectedAggregator(plugin, functionId, address(uint160(currentValidationData))); } + validationData = _coalescePreValidation(validationData, currentValidationData); } // Run the user op validationFunction { - if (!userOpValidationFunction.isEmptyOrMagicValue()) { - (address plugin, uint8 functionId) = userOpValidationFunction.unpack(); - currentValidationData = IPlugin(plugin).userOpValidationFunction(functionId, userOp, userOpHash); - - if (preUserOpValidationHooksLength != 0) { - // If we have other validation data we need to coalesce with - validationData = _coalesceValidation(validationData, currentValidationData); - } else { - validationData = currentValidationData; - } + (address plugin, uint8 functionId) = userOpValidationFunction.unpack(); + currentValidationData = IPlugin(plugin).userOpValidationFunction(functionId, userOp, userOpHash); + + if (preUserOpValidationHooksLength != 0) { + // If we have other validation data we need to coalesce with + validationData = _coalesceValidation(validationData, currentValidationData); } else { - // _PRE_HOOK_ALWAYS_DENY is not permitted here. - // If this is _RUNTIME_VALIDATION_ALWAYS_ALLOW, the call should revert. - revert InvalidConfiguration(); + validationData = currentValidationData; } } } function _doRuntimeValidationIfNotFromEP() internal { + AccountStorage storage _storage = getAccountStorage(); + + if (_storage.selectorData[msg.sig].denyExecutionCount > 0) { + revert AlwaysDenyRule(); + } + if (msg.sender == address(_ENTRY_POINT)) return; - AccountStorage storage _storage = getAccountStorage(); FunctionReference runtimeValidationFunction = _storage.selectorData[msg.sig].validation; // run all preRuntimeValidation hooks - EnumerableMap.Bytes32ToUintMap storage preRuntimeValidationHooks = + EnumerableSet.Bytes32Set storage preRuntimeValidationHooks = getAccountStorage().selectorData[msg.sig].preValidationHooks; uint256 preRuntimeValidationHooksLength = preRuntimeValidationHooks.length(); for (uint256 i = 0; i < preRuntimeValidationHooksLength; ++i) { - (bytes32 key,) = preRuntimeValidationHooks.at(i); + bytes32 key = preRuntimeValidationHooks.at(i); FunctionReference preRuntimeValidationHook = _toFunctionReference(key); - if (!preRuntimeValidationHook.isEmptyOrMagicValue()) { - (address plugin, uint8 functionId) = preRuntimeValidationHook.unpack(); - // solhint-disable-next-line no-empty-blocks - try IPlugin(plugin).preRuntimeValidationHook(functionId, msg.sender, msg.value, msg.data) {} - catch (bytes memory revertReason) { - revert PreRuntimeValidationHookFailed(plugin, functionId, revertReason); - } - } else { - if (preRuntimeValidationHook.eq(FunctionReferenceLib._PRE_HOOK_ALWAYS_DENY)) { - revert AlwaysDenyRule(); - } - // Function reference cannot be 0 or _RUNTIME_VALIDATION_ALWAYS_ALLOW. - revert InvalidConfiguration(); + (address plugin, uint8 functionId) = preRuntimeValidationHook.unpack(); + // solhint-disable-next-line no-empty-blocks + try IPlugin(plugin).preRuntimeValidationHook(functionId, msg.sender, msg.value, msg.data) {} + catch (bytes memory revertReason) { + revert PreRuntimeValidationHookFailed(plugin, functionId, revertReason); } } @@ -427,8 +420,6 @@ contract UpgradeableModularAccount is } else { if (runtimeValidationFunction.isEmpty()) { revert RuntimeValidationFunctionMissing(msg.sig); - } else if (runtimeValidationFunction.eq(FunctionReferenceLib._PRE_HOOK_ALWAYS_DENY)) { - revert InvalidConfiguration(); } // If _RUNTIME_VALIDATION_ALWAYS_ALLOW, just let the function finish. } @@ -440,63 +431,47 @@ contract UpgradeableModularAccount is returns (PostExecToRun[] memory postHooksToRun) { SelectorData storage selectorData = getAccountStorage().selectorData[selector]; + uint256 preExecHooksLength = selectorData.preHooks.length(); uint256 postOnlyHooksLength = selectorData.postOnlyHooks.length(); - uint256 maxPostExecHooksLength = postOnlyHooksLength; - - // There can only be as many associated post hooks to run as there are pre hooks. - for (uint256 i = 0; i < preExecHooksLength; ++i) { - (, uint256 count) = selectorData.preHooks.at(i); - unchecked { - maxPostExecHooksLength += (count + 1); - } - } // Overallocate on length - not all of this may get filled up. We set the correct length later. - postHooksToRun = new PostExecToRun[](maxPostExecHooksLength); - uint256 actualPostHooksToRunLength; + postHooksToRun = new PostExecToRun[](preExecHooksLength + postOnlyHooksLength); - // Copy post-only hooks to the array. + // Copy all post hooks to the array. This happens before any pre hooks are run, so we can + // be sure that the set of hooks to run will not be affected by state changes mid-execution. + + // Copy post-only hooks. for (uint256 i = 0; i < postOnlyHooksLength; ++i) { - (bytes32 key,) = selectorData.postOnlyHooks.at(i); - postHooksToRun[actualPostHooksToRunLength].postExecHook = _toFunctionReference(key); - unchecked { - ++actualPostHooksToRunLength; - } + bytes32 key = selectorData.postOnlyHooks.at(i); + postHooksToRun[i].postExecHook = _toFunctionReference(key); } - // Then run the pre hooks and copy the associated post hooks (along with their pre hook's return data) to - // the array. + // Copy associated post hooks to the array. for (uint256 i = 0; i < preExecHooksLength; ++i) { - (bytes32 key,) = selectorData.preHooks.at(i); - FunctionReference preExecHook = _toFunctionReference(key); + FunctionReference preExecHook = _toFunctionReference(selectorData.preHooks.at(i)); - if (preExecHook.isEmptyOrMagicValue()) { - // The function reference must be PRE_HOOK_ALWAYS_DENY in this case, because zero and any other - // magic value is unassignable here. - revert AlwaysDenyRule(); + FunctionReference associatedPostExecHook = selectorData.associatedPostHooks[preExecHook]; + + if (associatedPostExecHook.notEmpty()) { + postHooksToRun[i + postOnlyHooksLength].postExecHook = associatedPostExecHook; } + } - bytes memory preExecHookReturnData = _runPreExecHook(preExecHook, data); + // Run the pre hooks and copy their return data to the post hooks array, if an associated post-exec hook + // exists. + for (uint256 i = 0; i < preExecHooksLength; ++i) { + bytes32 key = selectorData.preHooks.at(i); + FunctionReference preExecHook = _toFunctionReference(key); - uint256 associatedPostExecHooksLength = selectorData.associatedPostHooks[preExecHook].length(); - if (associatedPostExecHooksLength > 0) { - for (uint256 j = 0; j < associatedPostExecHooksLength; ++j) { - (key,) = selectorData.associatedPostHooks[preExecHook].at(j); - postHooksToRun[actualPostHooksToRunLength].postExecHook = _toFunctionReference(key); - postHooksToRun[actualPostHooksToRunLength].preExecHookReturnData = preExecHookReturnData; + bytes memory preExecHookReturnData = _runPreExecHook(preExecHook, data); - unchecked { - ++actualPostHooksToRunLength; - } - } + // If there is an associated post-exec hook, save the return data. + PostExecToRun memory postExecToRun = postHooksToRun[i + postOnlyHooksLength]; + if (postExecToRun.postExecHook.notEmpty()) { + postExecToRun.preExecHookReturnData = preExecHookReturnData; } } - - // Trim the post hook array to the actual length, since we may have overallocated. - assembly ("memory-safe") { - mstore(postHooksToRun, actualPostHooksToRunLength) - } } function _runPreExecHook(FunctionReference preExecHook, bytes calldata data) @@ -521,6 +496,12 @@ contract UpgradeableModularAccount is --i; PostExecToRun memory postHookToRun = postHooksToRun[i]; + + if (postHookToRun.postExecHook.isEmpty()) { + // This is an empty post hook, from a pre-only hook, so we skip it. + continue; + } + (address plugin, uint8 functionId) = postHookToRun.postExecHook.unpack(); // solhint-disable-next-line no-empty-blocks try IPlugin(plugin).postExecutionHook(functionId, postHookToRun.preExecHookReturnData) {} diff --git a/src/helpers/FunctionReferenceLib.sol b/src/helpers/FunctionReferenceLib.sol index a938eef5..e80992c9 100644 --- a/src/helpers/FunctionReferenceLib.sol +++ b/src/helpers/FunctionReferenceLib.sol @@ -26,6 +26,10 @@ library FunctionReferenceLib { return FunctionReference.unwrap(fr) == bytes21(0); } + function notEmpty(FunctionReference fr) internal pure returns (bool) { + return FunctionReference.unwrap(fr) != bytes21(0); + } + function isEmptyOrMagicValue(FunctionReference fr) internal pure returns (bool) { return FunctionReference.unwrap(fr) <= bytes21(uint168(2)); } diff --git a/src/interfaces/IPlugin.sol b/src/interfaces/IPlugin.sol index 5049d5cd..ca5c7ff7 100644 --- a/src/interfaces/IPlugin.sol +++ b/src/interfaces/IPlugin.sol @@ -19,10 +19,9 @@ enum ManifestAssociatedFunctionType { // setting a hook and is therefore disallowed. RUNTIME_VALIDATION_ALWAYS_ALLOW, // Resolves to a magic value to always fail in a hook for a given function. - // This is only assignable to pre hooks (pre validation and pre execution). It should not be used on - // validation functions themselves, because this is equivalent to leaving the validation functions unset. - // It should not be used in post-exec hooks, because if it is known to always revert, that should happen - // as early as possible to save gas. + // This is only assignable to pre execution hooks. It should not be used on validation functions themselves, because + // this is equivalent to leaving the validation functions unset. It should not be used in post-exec hooks, because + // if it is known to always revert, that should happen as early as possible to save gas. PRE_HOOK_ALWAYS_DENY } // forgefmt: disable-end diff --git a/standard/ERCs/erc-6900.md b/standard/ERCs/erc-6900.md index a9c86dcd..f4be1e0d 100644 --- a/standard/ERCs/erc-6900.md +++ b/standard/ERCs/erc-6900.md @@ -348,10 +348,9 @@ enum ManifestAssociatedFunctionType { // setting a hook and is therefore disallowed. RUNTIME_VALIDATION_ALWAYS_ALLOW, // Resolves to a magic value to always fail in a hook for a given function. - // This is only assignable to pre hooks (pre validation and pre execution). It should not be used on - // validation functions themselves, because this is equivalent to leaving the validation functions unset. - // It should not be used in post-exec hooks, because if it is known to always revert, that should happen - // as early as possible to save gas. + // This is only assignable to pre execution hooks. It should not be used on validation functions themselves, because + // this is equivalent to leaving the validation functions unset. It should not be used in post-exec hooks, because + // if it is known to always revert, that should happen as early as possible to save gas. PRE_HOOK_ALWAYS_DENY } @@ -498,7 +497,7 @@ Finally, the function MUST emit the event `PluginUninstalled` with the plugin's When the function `validateUserOp` is called on modular account by the `EntryPoint`, it MUST find the user operation validation function associated to the function selector in the first four bytes of `userOp.callData`. If there is no function defined for the selector, or if `userOp.callData.length < 4`, then execution MUST revert. -If the function selector has associated pre user operation validation hooks, then those hooks MUST be run sequentially. If any revert, the outer call MUST revert. If any are set to `PRE_HOOK_ALWAYS_DENY`, the call MUST revert. If any return an `authorizer` value other than 0 or 1, execution MUST revert. If any return an `authorizer` value of 1, indicating an invalid signature, the returned validation data of the outer call MUST also be 1. If any return time-bounded validation by specifying either a `validUntil` or `validBefore` value, the resulting validation data MUST be the intersection of all time bounds provided. +If the function selector has associated pre user operation validation hooks, then those hooks MUST be run sequentially. If any revert, the outer call MUST revert. If the selector has any pre execution hooks set to `PRE_HOOK_ALWAYS_DENY`, the call MUST revert. If any return an `authorizer` value other than 0 or 1, execution MUST revert. If any return an `authorizer` value of 1, indicating an invalid signature, the returned validation data of the outer call MUST also be 1. If any return time-bounded validation by specifying either a `validUntil` or `validBefore` value, the resulting validation data MUST be the intersection of all time bounds provided. Then, the modular account MUST execute the validation function with the user operation and its hash as parameters using the `call` opcode. The returned validation data from the user operation validation function MUST be updated, if necessary, by the return values of any pre user operation validation hooks, then returned by `validateUserOp`. @@ -510,7 +509,7 @@ Additionally, when the modular account natively implements functions in `IPlugin The steps to perform are: -- If the call is not from the `EntryPoint`, then find an associated runtime validation function. If one does not exist, execution MUST revert. The modular account MUST execute all pre runtime validation hooks, then the runtime validation function, with the `call` opcode. All of these functions MUST receive the caller, value, and execution function's calldata as parameters. If any of these functions revert, execution MUST revert. If any pre runtime validation hooks are set to `PRE_HOOK_ALWAYS_DENY`, execution MUST revert. If the runtime validation function is set to `RUNTIME_VALIDATION_ALWAYS_ALLOW`, the validation function MUST be bypassed. +- If the call is not from the `EntryPoint`, then find an associated runtime validation function. If one does not exist, execution MUST revert. The modular account MUST execute all pre runtime validation hooks, then the runtime validation function, with the `call` opcode. All of these functions MUST receive the caller, value, and execution function's calldata as parameters. If any of these functions revert, execution MUST revert. If any pre execution hooks are set to `PRE_HOOK_ALWAYS_DENY`, execution MUST revert. If the validation function is set to `RUNTIME_VALIDATION_ALWAYS_ALLOW`, the runtime validation function MUST be bypassed. - If there are pre execution hooks defined for the execution function, execute those hooks with the caller, value, and execution function's calldata as parameters. If any of these hooks returns data, it MUST be preserved until the call to the post execution hook. The operation MUST be done with the `call` opcode. If there are duplicate pre execution hooks (i.e., hooks with identical `FunctionReference`s), run the hook only once. If any of these functions revert, execution MUST revert. - Run the execution function. - If any post execution hooks are defined, run the functions. If a pre execution hook returned data to the account, that data MUST be passed as a parameter to the associated post execution hook. The operation MUST be done with the `call` opcode. If there are duplicate post execution hooks, run them once for each unique associated pre execution hook. For post execution hooks without an associated pre execution hook, run the hook only once. If any of these functions revert, execution MUST revert. diff --git a/test/account/AccountExecHooks.t.sol b/test/account/AccountExecHooks.t.sol index db5df7eb..8e1fef94 100644 --- a/test/account/AccountExecHooks.t.sol +++ b/test/account/AccountExecHooks.t.sol @@ -11,6 +11,7 @@ import { } from "../../src/interfaces/IPlugin.sol"; import {PluginManagerInternals} from "../../src/account/PluginManagerInternals.sol"; import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; +import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {MockPlugin} from "../mocks/MockPlugin.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; @@ -177,54 +178,62 @@ contract AccountExecHooksTest is AccountTestBase { _uninstallPlugin(mockPlugin1); } - function test_overlappingPreExecHooks_install() public { + function test_overlappingPreValidationHooks_install() public { // Install the first plugin. - _installPlugin1WithHooks( + _installPlugin1WithPreValidationHook( _EXEC_SELECTOR, ManifestFunction({ functionType: ManifestAssociatedFunctionType.PRE_HOOK_ALWAYS_DENY, functionId: 0, dependencyIndex: 0 - }), - ManifestFunction(ManifestAssociatedFunctionType.NONE, 0, 0) + }) ); + // Expect the call to fail due to the "always deny" pre hook. + vm.breakpoint("a"); + (bool success, bytes memory retData) = address(account1).call(abi.encodeWithSelector(_EXEC_SELECTOR)); + assertFalse(success); + assertEq(retData, abi.encodeWithSelector(UpgradeableModularAccount.AlwaysDenyRule.selector)); + // Install a second plugin that applies the same pre hook on the same selector. - _installPlugin2WithHooksExpectSuccess( + _installPlugin2WithPreValidationHook( _EXEC_SELECTOR, ManifestFunction({ functionType: ManifestAssociatedFunctionType.PRE_HOOK_ALWAYS_DENY, functionId: 0, dependencyIndex: 0 - }), - ManifestFunction(ManifestAssociatedFunctionType.NONE, 0, 0), - new FunctionReference[](0) + }) ); - vm.stopPrank(); - } - - function test_overlappingPreExecHooks_run() public { - (bool success,) = address(account1).call(abi.encodeWithSelector(_EXEC_SELECTOR)); + // Still expect the call to fail. + (success, retData) = address(account1).call(abi.encodeWithSelector(_EXEC_SELECTOR)); assertFalse(success); + assertEq(retData, abi.encodeWithSelector(UpgradeableModularAccount.AlwaysDenyRule.selector)); + + vm.stopPrank(); } - function test_overlappingPreExecHooks_uninstall() public { - test_overlappingPreExecHooks_install(); + function test_overlappingPreValidationHooks_uninstall() public { + test_overlappingPreValidationHooks_install(); // Uninstall the second plugin. _uninstallPlugin(mockPlugin2); - // Expect the pre hook to still exist. - (bool success,) = address(account1).call(abi.encodeWithSelector(_EXEC_SELECTOR)); + // Expect the pre validation hook of "always deny" to still exist. + (bool success, bytes memory retData) = address(account1).call(abi.encodeWithSelector(_EXEC_SELECTOR)); assertFalse(success); + assertEq(retData, abi.encodeWithSelector(UpgradeableModularAccount.AlwaysDenyRule.selector)); // Uninstall the first plugin. _uninstallPlugin(mockPlugin1); - // Execution selector should no longer exist. - (success,) = address(account1).call(abi.encodeWithSelector(_EXEC_SELECTOR)); + // // Execution selector should no longer exist. + (success, retData) = address(account1).call(abi.encodeWithSelector(_EXEC_SELECTOR)); assertFalse(success); + assertEq( + retData, + abi.encodeWithSelector(UpgradeableModularAccount.UnrecognizedFunction.selector, _EXEC_SELECTOR) + ); } function test_execHookDependencies_failInstall() public { @@ -290,6 +299,29 @@ contract AccountExecHooksTest is AccountTestBase { }); } + function _installPlugin1WithPreValidationHook(bytes4 selector, ManifestFunction memory preValidationHook) + internal + { + m1.preValidationHooks.push( + ManifestAssociatedFunction({executionSelector: selector, associatedFunction: preValidationHook}) + ); + + mockPlugin1 = new MockPlugin(m1); + manifestHash1 = keccak256(abi.encode(mockPlugin1.pluginManifest())); + + vm.expectEmit(true, true, true, true); + emit ReceivedCall(abi.encodeCall(IPlugin.onInstall, (bytes(""))), 0); + vm.expectEmit(true, true, true, true); + emit PluginInstalled(address(mockPlugin1), manifestHash1, new FunctionReference[](0)); + + account1.installPlugin({ + plugin: address(mockPlugin1), + manifestHash: manifestHash1, + pluginInstallData: bytes(""), + dependencies: new FunctionReference[](0) + }); + } + function _installPlugin2WithHooksExpectSuccess( bytes4 selector, ManifestFunction memory preHook, @@ -349,6 +381,29 @@ contract AccountExecHooksTest is AccountTestBase { }); } + function _installPlugin2WithPreValidationHook(bytes4 selector, ManifestFunction memory preValidationHook) + internal + { + m2.preValidationHooks.push( + ManifestAssociatedFunction({executionSelector: selector, associatedFunction: preValidationHook}) + ); + + mockPlugin2 = new MockPlugin(m2); + manifestHash2 = keccak256(abi.encode(mockPlugin2.pluginManifest())); + + vm.expectEmit(true, true, true, true); + emit ReceivedCall(abi.encodeCall(IPlugin.onInstall, (bytes(""))), 0); + vm.expectEmit(true, true, true, true); + emit PluginInstalled(address(mockPlugin2), manifestHash2, new FunctionReference[](0)); + + account1.installPlugin({ + plugin: address(mockPlugin2), + manifestHash: manifestHash2, + pluginInstallData: bytes(""), + dependencies: new FunctionReference[](0) + }); + } + function _uninstallPlugin(MockPlugin plugin) internal { vm.expectEmit(true, true, true, true); emit ReceivedCall(abi.encodeCall(IPlugin.onUninstall, (bytes(""))), 0); From 0f28a1330f95075640a3f87ddf383201d2531e99 Mon Sep 17 00:00:00 2001 From: adam-alchemy <127769144+adam-alchemy@users.noreply.github.com> Date: Wed, 8 May 2024 16:10:02 -0700 Subject: [PATCH 009/111] feat: [v0.8-develop] Simplify hook declaration and storage (#51) --- src/account/AccountLoupe.sol | 33 ++-- src/account/AccountStorage.sol | 34 ++++- src/account/PluginManagerInternals.sol | 99 ++++-------- src/account/UpgradeableModularAccount.sol | 56 ++++--- src/interfaces/IAccountLoupe.sol | 17 ++- src/interfaces/IPlugin.sol | 6 +- standard/ERCs/erc-6900.md | 13 +- test/account/AccountExecHooks.t.sol | 153 +++---------------- test/account/AccountLoupe.t.sol | 111 ++++---------- test/account/ManifestValidity.t.sol | 50 +----- test/mocks/plugins/ComprehensivePlugin.sol | 36 +++-- test/mocks/plugins/ManifestValidityMocks.sol | 110 ------------- 12 files changed, 194 insertions(+), 524 deletions(-) diff --git a/src/account/AccountLoupe.sol b/src/account/AccountLoupe.sol index d84b6d20..ca4da1dd 100644 --- a/src/account/AccountLoupe.sol +++ b/src/account/AccountLoupe.sol @@ -4,10 +4,16 @@ pragma solidity ^0.8.25; import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import {IAccountLoupe} from "../interfaces/IAccountLoupe.sol"; +import {IAccountLoupe, ExecutionHook} from "../interfaces/IAccountLoupe.sol"; import {FunctionReference, IPluginManager} from "../interfaces/IPluginManager.sol"; import {IStandardExecutor} from "../interfaces/IStandardExecutor.sol"; -import {AccountStorage, getAccountStorage, SelectorData, toFunctionReferenceArray} from "./AccountStorage.sol"; +import { + AccountStorage, + getAccountStorage, + SelectorData, + toFunctionReferenceArray, + toExecutionHook +} from "./AccountStorage.sol"; abstract contract AccountLoupe is IAccountLoupe { using EnumerableSet for EnumerableSet.Bytes32Set; @@ -36,25 +42,16 @@ abstract contract AccountLoupe is IAccountLoupe { } /// @inheritdoc IAccountLoupe - function getExecutionHooks(bytes4 selector) external view returns (ExecutionHooks[] memory execHooks) { + function getExecutionHooks(bytes4 selector) external view returns (ExecutionHook[] memory execHooks) { SelectorData storage selectorData = getAccountStorage().selectorData[selector]; - uint256 preExecHooksLength = selectorData.preHooks.length(); - uint256 postOnlyExecHooksLength = selectorData.postOnlyHooks.length(); + uint256 executionHooksLength = selectorData.executionHooks.length(); - execHooks = new ExecutionHooks[](preExecHooksLength + postOnlyExecHooksLength); + execHooks = new ExecutionHook[](executionHooksLength); - for (uint256 i = 0; i < preExecHooksLength; ++i) { - bytes32 key = selectorData.preHooks.at(i); - FunctionReference preExecHook = FunctionReference.wrap(bytes21(key)); - FunctionReference associatedPostExecHook = selectorData.associatedPostHooks[preExecHook]; - - execHooks[i].preExecHook = preExecHook; - execHooks[i].postExecHook = associatedPostExecHook; - } - - for (uint256 i = 0; i < postOnlyExecHooksLength; ++i) { - bytes32 key = selectorData.postOnlyHooks.at(i); - execHooks[preExecHooksLength + i].postExecHook = FunctionReference.wrap(bytes21(key)); + for (uint256 i = 0; i < executionHooksLength; ++i) { + bytes32 key = selectorData.executionHooks.at(i); + ExecutionHook memory execHook = execHooks[i]; + (execHook.hookFunction, execHook.isPreHook, execHook.isPostHook) = toExecutionHook(key); } } diff --git a/src/account/AccountStorage.sol b/src/account/AccountStorage.sol index 728f7f5c..5562fdcd 100644 --- a/src/account/AccountStorage.sol +++ b/src/account/AccountStorage.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.25; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {IPlugin} from "../interfaces/IPlugin.sol"; +import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; import {FunctionReference} from "../interfaces/IPluginManager.sol"; // bytes = keccak256("ERC6900.UpgradeableModularAccount.Storage") @@ -44,10 +45,7 @@ struct SelectorData { // The pre validation hooks for this function selector. EnumerableSet.Bytes32Set preValidationHooks; // The execution hooks for this function selector. - EnumerableSet.Bytes32Set preHooks; - // bytes21 key = pre hook function reference - mapping(FunctionReference => FunctionReference) associatedPostHooks; - EnumerableSet.Bytes32Set postOnlyHooks; + EnumerableSet.Bytes32Set executionHooks; } struct AccountStorage { @@ -79,6 +77,34 @@ function getPermittedCallKey(address addr, bytes4 selector) pure returns (bytes2 using EnumerableSet for EnumerableSet.Bytes32Set; +function toSetValue(FunctionReference functionReference) pure returns (bytes32) { + return bytes32(FunctionReference.unwrap(functionReference)); +} + +function toFunctionReference(bytes32 setValue) pure returns (FunctionReference) { + return FunctionReference.wrap(bytes21(setValue)); +} + +// ExecutionHook layout: +// 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF______________________ Hook Function Reference +// 0x__________________________________________AA____________________ is pre hook +// 0x____________________________________________BB__________________ is post hook + +function toSetValue(ExecutionHook memory executionHook) pure returns (bytes32) { + return bytes32(FunctionReference.unwrap(executionHook.hookFunction)) + | bytes32(executionHook.isPreHook ? uint256(1) << 80 : 0) + | bytes32(executionHook.isPostHook ? uint256(1) << 72 : 0); +} + +function toExecutionHook(bytes32 setValue) + pure + returns (FunctionReference hookFunction, bool isPreHook, bool isPostHook) +{ + hookFunction = FunctionReference.wrap(bytes21(setValue)); + isPreHook = (uint256(setValue) >> 80) & 0xFF == 1; + isPostHook = (uint256(setValue) >> 72) & 0xFF == 1; +} + /// @dev Helper function to get all elements of a set into memory. function toFunctionReferenceArray(EnumerableSet.Bytes32Set storage set) view diff --git a/src/account/PluginManagerInternals.sol b/src/account/PluginManagerInternals.sol index 1d2ee5b8..8f228932 100644 --- a/src/account/PluginManagerInternals.sol +++ b/src/account/PluginManagerInternals.sol @@ -14,11 +14,13 @@ import { ManifestExternalCallPermission, PluginManifest } from "../interfaces/IPlugin.sol"; +import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; import {FunctionReference, IPluginManager} from "../interfaces/IPluginManager.sol"; import { AccountStorage, getAccountStorage, SelectorData, + toSetValue, getPermittedCallKey, PermittedExternalCallData } from "./AccountStorage.sol"; @@ -96,49 +98,30 @@ abstract contract PluginManagerInternals is IPluginManager { _selectorData.validation = FunctionReferenceLib._EMPTY_FUNCTION_REFERENCE; } - function _addExecHooks(bytes4 selector, FunctionReference preExecHook, FunctionReference postExecHook) - internal - { - SelectorData storage _selectorData = getAccountStorage().selectorData[selector]; - - if (preExecHook.notEmpty()) { - // Don't need to check for duplicates, as the hook can be run at most once. - _selectorData.preHooks.add(_toSetValue(preExecHook)); - - if (postExecHook.notEmpty()) { - _selectorData.associatedPostHooks[preExecHook] = postExecHook; - } - - return; - } - - if (postExecHook.isEmpty()) { - // both pre and post hooks cannot be null - revert NullFunctionReference(); - } - - _selectorData.postOnlyHooks.add(_toSetValue(postExecHook)); + function _addExecHooks( + bytes4 selector, + FunctionReference hookFunction, + bool isPreExecHook, + bool isPostExecHook + ) internal { + getAccountStorage().selectorData[selector].executionHooks.add( + toSetValue( + ExecutionHook({hookFunction: hookFunction, isPreHook: isPreExecHook, isPostHook: isPostExecHook}) + ) + ); } - function _removeExecHooks(bytes4 selector, FunctionReference preExecHook, FunctionReference postExecHook) - internal - { - SelectorData storage _selectorData = getAccountStorage().selectorData[selector]; - - if (preExecHook.notEmpty()) { - _selectorData.preHooks.remove(_toSetValue(preExecHook)); - - if (postExecHook.notEmpty()) { - _selectorData.associatedPostHooks[preExecHook] = FunctionReferenceLib._EMPTY_FUNCTION_REFERENCE; - } - - return; - } - - // The case where both pre and post hooks are null was checked during installation. - - // May ignore return value, as the manifest hash is validated to ensure that the hook exists. - _selectorData.postOnlyHooks.remove(_toSetValue(postExecHook)); + function _removeExecHooks( + bytes4 selector, + FunctionReference hookFunction, + bool isPreExecHook, + bool isPostExecHook + ) internal { + getAccountStorage().selectorData[selector].executionHooks.remove( + toSetValue( + ExecutionHook({hookFunction: hookFunction, isPreHook: isPreExecHook, isPostHook: isPostExecHook}) + ) + ); } function _addPreValidationHook(bytes4 selector, FunctionReference preValidationHook) @@ -151,7 +134,7 @@ abstract contract PluginManagerInternals is IPluginManager { _selectorData.denyExecutionCount += 1; return; } - _selectorData.preValidationHooks.add(_toSetValue(preValidationHook)); + _selectorData.preValidationHooks.add(toSetValue(preValidationHook)); } function _removePreValidationHook(bytes4 selector, FunctionReference preValidationHook) @@ -165,7 +148,7 @@ abstract contract PluginManagerInternals is IPluginManager { return; } // May ignore return value, as the manifest hash is validated to ensure that the hook exists. - _selectorData.preValidationHooks.remove(_toSetValue(preValidationHook)); + _selectorData.preValidationHooks.remove(toSetValue(preValidationHook)); } function _installPlugin( @@ -299,15 +282,8 @@ abstract contract PluginManagerInternals is IPluginManager { length = manifest.executionHooks.length; for (uint256 i = 0; i < length; ++i) { ManifestExecutionHook memory mh = manifest.executionHooks[i]; - _addExecHooks( - mh.executionSelector, - _resolveManifestFunction( - mh.preExecHook, plugin, emptyDependencies, ManifestAssociatedFunctionType.NONE - ), - _resolveManifestFunction( - mh.postExecHook, plugin, emptyDependencies, ManifestAssociatedFunctionType.NONE - ) - ); + FunctionReference hookFunction = FunctionReferenceLib.pack(plugin, mh.functionId); + _addExecHooks(mh.executionSelector, hookFunction, mh.isPreHook, mh.isPostHook); } length = manifest.interfaceIds.length; @@ -365,15 +341,8 @@ abstract contract PluginManagerInternals is IPluginManager { length = manifest.executionHooks.length; for (uint256 i = 0; i < length; ++i) { ManifestExecutionHook memory mh = manifest.executionHooks[i]; - _removeExecHooks( - mh.executionSelector, - _resolveManifestFunction( - mh.preExecHook, plugin, emptyDependencies, ManifestAssociatedFunctionType.NONE - ), - _resolveManifestFunction( - mh.postExecHook, plugin, emptyDependencies, ManifestAssociatedFunctionType.NONE - ) - ); + FunctionReference hookFunction = FunctionReferenceLib.pack(plugin, mh.functionId); + _removeExecHooks(mh.executionSelector, hookFunction, mh.isPreHook, mh.isPostHook); } length = manifest.preValidationHooks.length; @@ -461,14 +430,6 @@ abstract contract PluginManagerInternals is IPluginManager { emit PluginUninstalled(plugin, onUninstallSuccess); } - function _toSetValue(FunctionReference functionReference) internal pure returns (bytes32) { - return bytes32(FunctionReference.unwrap(functionReference)); - } - - function _toFunctionReference(bytes32 setValue) internal pure returns (FunctionReference) { - return FunctionReference.wrap(bytes21(setValue)); - } - function _isValidPluginManifest(PluginManifest memory manifest, bytes32 manifestHash) internal pure diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 3161eb45..47192abd 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -16,7 +16,14 @@ import {FunctionReference, IPluginManager} from "../interfaces/IPluginManager.so import {IStandardExecutor, Call} from "../interfaces/IStandardExecutor.sol"; import {AccountExecutor} from "./AccountExecutor.sol"; import {AccountLoupe} from "./AccountLoupe.sol"; -import {AccountStorage, getAccountStorage, getPermittedCallKey, SelectorData} from "./AccountStorage.sol"; +import { + AccountStorage, + getAccountStorage, + getPermittedCallKey, + SelectorData, + toFunctionReference, + toExecutionHook +} from "./AccountStorage.sol"; import {AccountStorageInitializable} from "./AccountStorageInitializable.sol"; import {PluginManagerInternals} from "./PluginManagerInternals.sol"; @@ -51,7 +58,6 @@ contract UpgradeableModularAccount is error AuthorizeUpgradeReverted(bytes revertReason); error ExecFromPluginNotPermitted(address plugin, bytes4 selector); error ExecFromPluginExternalNotPermitted(address plugin, address target, uint256 value, bytes data); - error InvalidConfiguration(); error NativeTokenSpendingNotPermitted(address plugin); error PostExecHookReverted(address plugin, uint8 functionId, bytes revertReason); error PreExecHookReverted(address plugin, uint8 functionId, bytes revertReason); @@ -355,7 +361,7 @@ contract UpgradeableModularAccount is uint256 preUserOpValidationHooksLength = preUserOpValidationHooks.length(); for (uint256 i = 0; i < preUserOpValidationHooksLength; ++i) { bytes32 key = preUserOpValidationHooks.at(i); - FunctionReference preUserOpValidationHook = _toFunctionReference(key); + FunctionReference preUserOpValidationHook = toFunctionReference(key); (address plugin, uint8 functionId) = preUserOpValidationHook.unpack(); currentValidationData = IPlugin(plugin).preUserOpValidationHook(functionId, userOp, userOpHash); @@ -398,7 +404,7 @@ contract UpgradeableModularAccount is uint256 preRuntimeValidationHooksLength = preRuntimeValidationHooks.length(); for (uint256 i = 0; i < preRuntimeValidationHooksLength; ++i) { bytes32 key = preRuntimeValidationHooks.at(i); - FunctionReference preRuntimeValidationHook = _toFunctionReference(key); + FunctionReference preRuntimeValidationHook = toFunctionReference(key); (address plugin, uint8 functionId) = preRuntimeValidationHook.unpack(); // solhint-disable-next-line no-empty-blocks @@ -432,44 +438,34 @@ contract UpgradeableModularAccount is { SelectorData storage selectorData = getAccountStorage().selectorData[selector]; - uint256 preExecHooksLength = selectorData.preHooks.length(); - uint256 postOnlyHooksLength = selectorData.postOnlyHooks.length(); + uint256 hooksLength = selectorData.executionHooks.length(); // Overallocate on length - not all of this may get filled up. We set the correct length later. - postHooksToRun = new PostExecToRun[](preExecHooksLength + postOnlyHooksLength); + postHooksToRun = new PostExecToRun[](hooksLength); // Copy all post hooks to the array. This happens before any pre hooks are run, so we can // be sure that the set of hooks to run will not be affected by state changes mid-execution. - - // Copy post-only hooks. - for (uint256 i = 0; i < postOnlyHooksLength; ++i) { - bytes32 key = selectorData.postOnlyHooks.at(i); - postHooksToRun[i].postExecHook = _toFunctionReference(key); - } - - // Copy associated post hooks to the array. - for (uint256 i = 0; i < preExecHooksLength; ++i) { - FunctionReference preExecHook = _toFunctionReference(selectorData.preHooks.at(i)); - - FunctionReference associatedPostExecHook = selectorData.associatedPostHooks[preExecHook]; - - if (associatedPostExecHook.notEmpty()) { - postHooksToRun[i + postOnlyHooksLength].postExecHook = associatedPostExecHook; + for (uint256 i = 0; i < hooksLength; ++i) { + bytes32 key = selectorData.executionHooks.at(i); + (FunctionReference hookFunction,, bool isPostHook) = toExecutionHook(key); + if (isPostHook) { + postHooksToRun[i].postExecHook = hookFunction; } } // Run the pre hooks and copy their return data to the post hooks array, if an associated post-exec hook // exists. - for (uint256 i = 0; i < preExecHooksLength; ++i) { - bytes32 key = selectorData.preHooks.at(i); - FunctionReference preExecHook = _toFunctionReference(key); + for (uint256 i = 0; i < hooksLength; ++i) { + bytes32 key = selectorData.executionHooks.at(i); + (FunctionReference hookFunction, bool isPreHook, bool isPostHook) = toExecutionHook(key); - bytes memory preExecHookReturnData = _runPreExecHook(preExecHook, data); + if (isPreHook) { + bytes memory preExecHookReturnData = _runPreExecHook(hookFunction, data); - // If there is an associated post-exec hook, save the return data. - PostExecToRun memory postExecToRun = postHooksToRun[i + postOnlyHooksLength]; - if (postExecToRun.postExecHook.notEmpty()) { - postExecToRun.preExecHookReturnData = preExecHookReturnData; + // If there is an associated post-exec hook, save the return data. + if (isPostHook) { + postHooksToRun[i].preExecHookReturnData = preExecHookReturnData; + } } } } diff --git a/src/interfaces/IAccountLoupe.sol b/src/interfaces/IAccountLoupe.sol index 5b8a7516..a1b3c15f 100644 --- a/src/interfaces/IAccountLoupe.sol +++ b/src/interfaces/IAccountLoupe.sol @@ -3,6 +3,14 @@ pragma solidity ^0.8.25; import {FunctionReference} from "../interfaces/IPluginManager.sol"; +/// @notice Pre and post hooks for a given selector. +/// @dev It's possible for one of either `preExecHook` or `postExecHook` to be empty. +struct ExecutionHook { + FunctionReference hookFunction; + bool isPreHook; + bool isPostHook; +} + interface IAccountLoupe { /// @notice Config for an execution function, given a selector. struct ExecutionFunctionConfig { @@ -10,13 +18,6 @@ interface IAccountLoupe { FunctionReference validationFunction; } - /// @notice Pre and post hooks for a given selector. - /// @dev It's possible for one of either `preExecHook` or `postExecHook` to be empty. - struct ExecutionHooks { - FunctionReference preExecHook; - FunctionReference postExecHook; - } - /// @notice Get the validation functions and plugin address for a selector. /// @dev If the selector is a native function, the plugin address will be the address of the account. /// @param selector The selector to get the configuration for. @@ -26,7 +27,7 @@ interface IAccountLoupe { /// @notice Get the pre and post execution hooks for a selector. /// @param selector The selector to get the hooks for. /// @return The pre and post execution hooks for this selector. - function getExecutionHooks(bytes4 selector) external view returns (ExecutionHooks[] memory); + function getExecutionHooks(bytes4 selector) external view returns (ExecutionHook[] memory); /// @notice Get the pre user op and runtime validation hooks associated with a selector. /// @param selector The selector to get the hooks for. diff --git a/src/interfaces/IPlugin.sol b/src/interfaces/IPlugin.sol index ca5c7ff7..d3163730 100644 --- a/src/interfaces/IPlugin.sol +++ b/src/interfaces/IPlugin.sol @@ -40,9 +40,11 @@ struct ManifestAssociatedFunction { } struct ManifestExecutionHook { + // TODO(erc6900 spec): These fields can be packed into a single word bytes4 executionSelector; - ManifestFunction preExecHook; - ManifestFunction postExecHook; + uint8 functionId; + bool isPreHook; + bool isPostHook; } struct ManifestExternalCallPermission { diff --git a/standard/ERCs/erc-6900.md b/standard/ERCs/erc-6900.md index f4be1e0d..06b51e4d 100644 --- a/standard/ERCs/erc-6900.md +++ b/standard/ERCs/erc-6900.md @@ -221,8 +221,9 @@ interface IAccountLoupe { /// @notice Pre and post hooks for a given selector. /// @dev It's possible for one of either `preExecHook` or `postExecHook` to be empty. struct ExecutionHooks { - FunctionReference preExecHook; - FunctionReference postExecHook; + FunctionReference hookFunction; + bool isPreHook; + bool isPostHook; } /// @notice Get the validation functions and plugin address for a selector. @@ -368,9 +369,11 @@ struct ManifestAssociatedFunction { } struct ManifestExecutionHook { - bytes4 selector; - ManifestFunction preExecHook; - ManifestFunction postExecHook; + // TODO(erc6900 spec): These fields can be packed into a single word + bytes4 executionSelector; + uint8 functionId; + bool isPreHook; + bool isPostHook; } struct ManifestExternalCallPermission { diff --git a/test/account/AccountExecHooks.t.sol b/test/account/AccountExecHooks.t.sol index 8e1fef94..9b7cde45 100644 --- a/test/account/AccountExecHooks.t.sol +++ b/test/account/AccountExecHooks.t.sol @@ -9,8 +9,7 @@ import { ManifestFunction, PluginManifest } from "../../src/interfaces/IPlugin.sol"; -import {PluginManagerInternals} from "../../src/account/PluginManagerInternals.sol"; -import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; +import {FunctionReference} from "../../src/helpers/FunctionReferenceLib.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {MockPlugin} from "../mocks/MockPlugin.sol"; @@ -25,8 +24,7 @@ contract AccountExecHooksTest is AccountTestBase { bytes4 internal constant _EXEC_SELECTOR = bytes4(uint32(1)); uint8 internal constant _PRE_HOOK_FUNCTION_ID_1 = 1; uint8 internal constant _POST_HOOK_FUNCTION_ID_2 = 2; - uint8 internal constant _PRE_HOOK_FUNCTION_ID_3 = 3; - uint8 internal constant _POST_HOOK_FUNCTION_ID_4 = 4; + uint8 internal constant _BOTH_HOOKS_FUNCTION_ID_3 = 3; PluginManifest public m1; PluginManifest public m2; @@ -55,13 +53,12 @@ contract AccountExecHooksTest is AccountTestBase { function test_preExecHook_install() public { _installPlugin1WithHooks( - _EXEC_SELECTOR, - ManifestFunction({ - functionType: ManifestAssociatedFunctionType.SELF, + ManifestExecutionHook({ + executionSelector: _EXEC_SELECTOR, functionId: _PRE_HOOK_FUNCTION_ID_1, - dependencyIndex: 0 - }), - ManifestFunction({functionType: ManifestAssociatedFunctionType.NONE, functionId: 0, dependencyIndex: 0}) + isPreHook: true, + isPostHook: false + }) ); } @@ -94,16 +91,11 @@ contract AccountExecHooksTest is AccountTestBase { function test_execHookPair_install() public { _installPlugin1WithHooks( - _EXEC_SELECTOR, - ManifestFunction({ - functionType: ManifestAssociatedFunctionType.SELF, - functionId: _PRE_HOOK_FUNCTION_ID_1, - dependencyIndex: 0 - }), - ManifestFunction({ - functionType: ManifestAssociatedFunctionType.SELF, - functionId: _POST_HOOK_FUNCTION_ID_2, - dependencyIndex: 0 + ManifestExecutionHook({ + executionSelector: _EXEC_SELECTOR, + functionId: _BOTH_HOOKS_FUNCTION_ID_3, + isPreHook: true, + isPostHook: true }) ); } @@ -118,7 +110,7 @@ contract AccountExecHooksTest is AccountTestBase { emit ReceivedCall( abi.encodeWithSelector( IPlugin.preExecutionHook.selector, - _PRE_HOOK_FUNCTION_ID_1, + _BOTH_HOOKS_FUNCTION_ID_3, address(this), // caller 0, // msg.value in call to account abi.encodeWithSelector(_EXEC_SELECTOR) @@ -131,7 +123,7 @@ contract AccountExecHooksTest is AccountTestBase { vm.expectEmit(true, true, true, true); // post hook call emit ReceivedCall( - abi.encodeCall(IPlugin.postExecutionHook, (_POST_HOOK_FUNCTION_ID_2, "")), + abi.encodeCall(IPlugin.postExecutionHook, (_BOTH_HOOKS_FUNCTION_ID_3, "")), 0 // msg value in call to plugin ); @@ -147,12 +139,11 @@ contract AccountExecHooksTest is AccountTestBase { function test_postOnlyExecHook_install() public { _installPlugin1WithHooks( - _EXEC_SELECTOR, - ManifestFunction({functionType: ManifestAssociatedFunctionType.NONE, functionId: 0, dependencyIndex: 0}), - ManifestFunction({ - functionType: ManifestAssociatedFunctionType.SELF, + ManifestExecutionHook({ + executionSelector: _EXEC_SELECTOR, functionId: _POST_HOOK_FUNCTION_ID_2, - dependencyIndex: 0 + isPreHook: false, + isPostHook: true }) ); } @@ -236,53 +227,8 @@ contract AccountExecHooksTest is AccountTestBase { ); } - function test_execHookDependencies_failInstall() public { - // Install the first plugin. - _installPlugin1WithHooks( - _EXEC_SELECTOR, - ManifestFunction({ - functionType: ManifestAssociatedFunctionType.SELF, - functionId: _PRE_HOOK_FUNCTION_ID_1, - dependencyIndex: 0 - }), - ManifestFunction({ - functionType: ManifestAssociatedFunctionType.SELF, - functionId: _POST_HOOK_FUNCTION_ID_2, - dependencyIndex: 0 - }) - ); - - // Attempt to install a second plugin that applies the first plugin's hook pair (as dependencies) to the - // same selector. This should revert. - FunctionReference[] memory dependencies = new FunctionReference[](2); - dependencies[0] = FunctionReferenceLib.pack(address(mockPlugin1), _PRE_HOOK_FUNCTION_ID_1); - dependencies[1] = FunctionReferenceLib.pack(address(mockPlugin1), _POST_HOOK_FUNCTION_ID_2); - - _installPlugin2WithHooksExpectFail( - _EXEC_SELECTOR, - ManifestFunction({ - functionType: ManifestAssociatedFunctionType.DEPENDENCY, - functionId: 0, - dependencyIndex: 0 - }), - ManifestFunction({ - functionType: ManifestAssociatedFunctionType.DEPENDENCY, - functionId: 0, - dependencyIndex: 1 - }), - dependencies, - abi.encodePacked(PluginManagerInternals.InvalidPluginManifest.selector) - ); - - vm.stopPrank(); - } - - function _installPlugin1WithHooks( - bytes4 selector, - ManifestFunction memory preHook, - ManifestFunction memory postHook - ) internal { - m1.executionHooks.push(ManifestExecutionHook(selector, preHook, postHook)); + function _installPlugin1WithHooks(ManifestExecutionHook memory execHooks) internal { + m1.executionHooks.push(execHooks); mockPlugin1 = new MockPlugin(m1); manifestHash1 = keccak256(abi.encode(mockPlugin1.pluginManifest())); @@ -322,65 +268,6 @@ contract AccountExecHooksTest is AccountTestBase { }); } - function _installPlugin2WithHooksExpectSuccess( - bytes4 selector, - ManifestFunction memory preHook, - ManifestFunction memory postHook, - FunctionReference[] memory dependencies - ) internal { - if (preHook.functionType == ManifestAssociatedFunctionType.DEPENDENCY) { - m2.dependencyInterfaceIds.push(type(IPlugin).interfaceId); - } - if (postHook.functionType == ManifestAssociatedFunctionType.DEPENDENCY) { - m2.dependencyInterfaceIds.push(type(IPlugin).interfaceId); - } - - m2.executionHooks.push(ManifestExecutionHook(selector, preHook, postHook)); - - mockPlugin2 = new MockPlugin(m2); - manifestHash2 = keccak256(abi.encode(mockPlugin2.pluginManifest())); - - vm.expectEmit(true, true, true, true); - emit ReceivedCall(abi.encodeCall(IPlugin.onInstall, (bytes(""))), 0); - vm.expectEmit(true, true, true, true); - emit PluginInstalled(address(mockPlugin2), manifestHash2, dependencies); - - account1.installPlugin({ - plugin: address(mockPlugin2), - manifestHash: manifestHash2, - pluginInstallData: bytes(""), - dependencies: dependencies - }); - } - - function _installPlugin2WithHooksExpectFail( - bytes4 selector, - ManifestFunction memory preHook, - ManifestFunction memory postHook, - FunctionReference[] memory dependencies, - bytes memory revertData - ) internal { - if (preHook.functionType == ManifestAssociatedFunctionType.DEPENDENCY) { - m2.dependencyInterfaceIds.push(type(IPlugin).interfaceId); - } - if (postHook.functionType == ManifestAssociatedFunctionType.DEPENDENCY) { - m2.dependencyInterfaceIds.push(type(IPlugin).interfaceId); - } - - m2.executionHooks.push(ManifestExecutionHook(selector, preHook, postHook)); - - mockPlugin2 = new MockPlugin(m2); - manifestHash2 = keccak256(abi.encode(mockPlugin2.pluginManifest())); - - vm.expectRevert(revertData); - account1.installPlugin({ - plugin: address(mockPlugin2), - manifestHash: manifestHash2, - pluginInstallData: bytes(""), - dependencies: dependencies - }); - } - function _installPlugin2WithPreValidationHook(bytes4 selector, ManifestFunction memory preValidationHook) internal { diff --git a/test/account/AccountLoupe.t.sol b/test/account/AccountLoupe.t.sol index d56a8ff9..9fbc1e21 100644 --- a/test/account/AccountLoupe.t.sol +++ b/test/account/AccountLoupe.t.sol @@ -4,19 +4,12 @@ pragma solidity ^0.8.19; import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; -import { - ManifestAssociatedFunctionType, - ManifestExecutionHook, - ManifestFunction, - PluginManifest -} from "../../src/interfaces/IPlugin.sol"; -import {IAccountLoupe} from "../../src/interfaces/IAccountLoupe.sol"; +import {IAccountLoupe, ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; import {IPluginManager} from "../../src/interfaces/IPluginManager.sol"; import {IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; import {ISingleOwnerPlugin} from "../../src/plugins/owner/ISingleOwnerPlugin.sol"; import {ComprehensivePlugin} from "../mocks/plugins/ComprehensivePlugin.sol"; -import {MockPlugin} from "../mocks/MockPlugin.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; contract AccountLoupeTest is AccountTestBase { @@ -107,82 +100,40 @@ contract AccountLoupeTest is AccountTestBase { } function test_pluginLoupe_getExecutionHooks() public { - IAccountLoupe.ExecutionHooks[] memory hooks = account1.getExecutionHooks(comprehensivePlugin.foo.selector); - - assertEq(hooks.length, 1); - assertEq( - FunctionReference.unwrap(hooks[0].preExecHook), - FunctionReference.unwrap( - FunctionReferenceLib.pack( + ExecutionHook[] memory hooks = account1.getExecutionHooks(comprehensivePlugin.foo.selector); + ExecutionHook[3] memory expectedHooks = [ + ExecutionHook({ + hookFunction: FunctionReferenceLib.pack( + address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.BOTH_EXECUTION_HOOKS) + ), + isPreHook: true, + isPostHook: true + }), + ExecutionHook({ + hookFunction: FunctionReferenceLib.pack( address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.PRE_EXECUTION_HOOK) - ) - ) - ); - assertEq( - FunctionReference.unwrap(hooks[0].postExecHook), - FunctionReference.unwrap( - FunctionReferenceLib.pack( - address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.POST_EXECUTION_HOOK) - ) - ) - ); - } - - function test_pluginLoupe_getHooks_multiple() public { - // Add a second set of execution hooks to the account, and validate that it can return all hooks applied - // over the function. - - PluginManifest memory mockPluginManifest; - - mockPluginManifest.executionHooks = new ManifestExecutionHook[](1); - mockPluginManifest.executionHooks[0] = ManifestExecutionHook({ - executionSelector: ComprehensivePlugin.foo.selector, - preExecHook: ManifestFunction({ - functionType: ManifestAssociatedFunctionType.SELF, - functionId: 0, - dependencyIndex: 0 + ), + isPreHook: true, + isPostHook: false }), - postExecHook: ManifestFunction({ - functionType: ManifestAssociatedFunctionType.SELF, - functionId: 0, - dependencyIndex: 0 + ExecutionHook({ + hookFunction: FunctionReferenceLib.pack( + address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.POST_EXECUTION_HOOK) + ), + isPreHook: false, + isPostHook: true }) - }); - - MockPlugin mockPlugin = new MockPlugin(mockPluginManifest); - bytes32 manifestHash = keccak256(abi.encode(mockPlugin.pluginManifest())); - - account1.installPlugin(address(mockPlugin), manifestHash, "", new FunctionReference[](0)); - - // Assert that the returned execution hooks are what is expected + ]; - IAccountLoupe.ExecutionHooks[] memory hooks = account1.getExecutionHooks(comprehensivePlugin.foo.selector); - - assertEq(hooks.length, 2); - assertEq( - FunctionReference.unwrap(hooks[0].preExecHook), - FunctionReference.unwrap( - FunctionReferenceLib.pack( - address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.PRE_EXECUTION_HOOK) - ) - ) - ); - assertEq( - FunctionReference.unwrap(hooks[0].postExecHook), - FunctionReference.unwrap( - FunctionReferenceLib.pack( - address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.POST_EXECUTION_HOOK) - ) - ) - ); - assertEq( - FunctionReference.unwrap(hooks[1].preExecHook), - FunctionReference.unwrap(FunctionReferenceLib.pack(address(mockPlugin), uint8(0))) - ); - assertEq( - FunctionReference.unwrap(hooks[1].postExecHook), - FunctionReference.unwrap(FunctionReferenceLib.pack(address(mockPlugin), uint8(0))) - ); + assertEq(hooks.length, 3); + for (uint256 i = 0; i < hooks.length; i++) { + assertEq( + FunctionReference.unwrap(hooks[i].hookFunction), + FunctionReference.unwrap(expectedHooks[i].hookFunction) + ); + assertEq(hooks[i].isPreHook, expectedHooks[i].isPreHook); + assertEq(hooks[i].isPostHook, expectedHooks[i].isPostHook); + } } function test_pluginLoupe_getValidationHooks() public { diff --git a/test/account/ManifestValidity.t.sol b/test/account/ManifestValidity.t.sol index 2631cf1c..21023ef3 100644 --- a/test/account/ManifestValidity.t.sol +++ b/test/account/ManifestValidity.t.sol @@ -6,11 +6,8 @@ import {FunctionReference} from "../../src/helpers/FunctionReferenceLib.sol"; import { BadValidationMagicValue_PreValidationHook_Plugin, - BadValidationMagicValue_PreExecHook_Plugin, - BadValidationMagicValue_PostExecHook_Plugin, BadHookMagicValue_UserOpValidationFunction_Plugin, - BadHookMagicValue_RuntimeValidationFunction_Plugin, - BadHookMagicValue_PostExecHook_Plugin + BadHookMagicValue_RuntimeValidationFunction_Plugin } from "../mocks/plugins/ManifestValidityMocks.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; @@ -36,36 +33,6 @@ contract ManifestValidityTest is AccountTestBase { }); } - // Tests that the plugin manager rejects a plugin with a pre-execution hook set to "validation always allow" - function test_ManifestValidity_invalid_ValidationAlwaysAllow_PreExecHook() public { - BadValidationMagicValue_PreExecHook_Plugin plugin = new BadValidationMagicValue_PreExecHook_Plugin(); - - bytes32 manifestHash = keccak256(abi.encode(plugin.pluginManifest())); - - vm.expectRevert(abi.encodeWithSelector(PluginManagerInternals.InvalidPluginManifest.selector)); - account1.installPlugin({ - plugin: address(plugin), - manifestHash: manifestHash, - pluginInstallData: "", - dependencies: new FunctionReference[](0) - }); - } - - // Tests that the plugin manager rejects a plugin with a post-execution hook set to "validation always allow" - function test_ManifestValidity_invalid_ValidationAlwaysAllow_PostExecHook() public { - BadValidationMagicValue_PostExecHook_Plugin plugin = new BadValidationMagicValue_PostExecHook_Plugin(); - - bytes32 manifestHash = keccak256(abi.encode(plugin.pluginManifest())); - - vm.expectRevert(abi.encodeWithSelector(PluginManagerInternals.InvalidPluginManifest.selector)); - account1.installPlugin({ - plugin: address(plugin), - manifestHash: manifestHash, - pluginInstallData: "", - dependencies: new FunctionReference[](0) - }); - } - // Tests that the plugin manager rejects a plugin with a user op validationFunction set to "hook always deny" function test_ManifestValidity_invalid_HookAlwaysDeny_UserOpValidation() public { BadHookMagicValue_UserOpValidationFunction_Plugin plugin = @@ -97,19 +64,4 @@ contract ManifestValidityTest is AccountTestBase { dependencies: new FunctionReference[](0) }); } - - // Tests that the plugin manager rejects a plugin with a post-execution hook set to "hook always deny" - function test_ManifestValidity_invalid_HookAlwaysDeny_PostExecHook() public { - BadHookMagicValue_PostExecHook_Plugin plugin = new BadHookMagicValue_PostExecHook_Plugin(); - - bytes32 manifestHash = keccak256(abi.encode(plugin.pluginManifest())); - - vm.expectRevert(abi.encodeWithSelector(PluginManagerInternals.InvalidPluginManifest.selector)); - account1.installPlugin({ - plugin: address(plugin), - manifestHash: manifestHash, - pluginInstallData: "", - dependencies: new FunctionReference[](0) - }); - } } diff --git a/test/mocks/plugins/ComprehensivePlugin.sol b/test/mocks/plugins/ComprehensivePlugin.sol index 2e2f2441..87f85210 100644 --- a/test/mocks/plugins/ComprehensivePlugin.sol +++ b/test/mocks/plugins/ComprehensivePlugin.sol @@ -19,10 +19,9 @@ contract ComprehensivePlugin is BasePlugin { PRE_VALIDATION_HOOK_1, PRE_VALIDATION_HOOK_2, VALIDATION, + BOTH_EXECUTION_HOOKS, PRE_EXECUTION_HOOK, - PRE_PERMITTED_CALL_EXECUTION_HOOK, - POST_EXECUTION_HOOK, - POST_PERMITTED_CALL_EXECUTION_HOOK + POST_EXECUTION_HOOK } string public constant NAME = "Comprehensive Plugin"; @@ -97,7 +96,7 @@ contract ComprehensivePlugin is BasePlugin { { if (functionId == uint8(FunctionId.PRE_EXECUTION_HOOK)) { return ""; - } else if (functionId == uint8(FunctionId.PRE_PERMITTED_CALL_EXECUTION_HOOK)) { + } else if (functionId == uint8(FunctionId.BOTH_EXECUTION_HOOKS)) { return ""; } revert NotImplemented(); @@ -106,7 +105,7 @@ contract ComprehensivePlugin is BasePlugin { function postExecutionHook(uint8 functionId, bytes calldata) external pure override { if (functionId == uint8(FunctionId.POST_EXECUTION_HOOK)) { return; - } else if (functionId == uint8(FunctionId.POST_PERMITTED_CALL_EXECUTION_HOOK)) { + } else if (functionId == uint8(FunctionId.BOTH_EXECUTION_HOOKS)) { return; } revert NotImplemented(); @@ -163,19 +162,24 @@ contract ComprehensivePlugin is BasePlugin { }) }); - manifest.executionHooks = new ManifestExecutionHook[](1); + manifest.executionHooks = new ManifestExecutionHook[](3); manifest.executionHooks[0] = ManifestExecutionHook({ executionSelector: this.foo.selector, - preExecHook: ManifestFunction({ - functionType: ManifestAssociatedFunctionType.SELF, - functionId: uint8(FunctionId.PRE_EXECUTION_HOOK), - dependencyIndex: 0 // Unused. - }), - postExecHook: ManifestFunction({ - functionType: ManifestAssociatedFunctionType.SELF, - functionId: uint8(FunctionId.POST_EXECUTION_HOOK), - dependencyIndex: 0 // Unused. - }) + functionId: uint8(FunctionId.BOTH_EXECUTION_HOOKS), + isPreHook: true, + isPostHook: true + }); + manifest.executionHooks[1] = ManifestExecutionHook({ + executionSelector: this.foo.selector, + functionId: uint8(FunctionId.PRE_EXECUTION_HOOK), + isPreHook: true, + isPostHook: false + }); + manifest.executionHooks[2] = ManifestExecutionHook({ + executionSelector: this.foo.selector, + functionId: uint8(FunctionId.POST_EXECUTION_HOOK), + isPreHook: false, + isPostHook: true }); return manifest; diff --git a/test/mocks/plugins/ManifestValidityMocks.sol b/test/mocks/plugins/ManifestValidityMocks.sol index c467f2ef..ed5370fb 100644 --- a/test/mocks/plugins/ManifestValidityMocks.sol +++ b/test/mocks/plugins/ManifestValidityMocks.sol @@ -5,7 +5,6 @@ import { ManifestFunction, ManifestAssociatedFunctionType, ManifestAssociatedFunction, - ManifestExecutionHook, PluginManifest } from "../../../src/interfaces/IPlugin.sol"; @@ -52,79 +51,6 @@ contract BadValidationMagicValue_PreValidationHook_Plugin is BaseTestPlugin { } } -// solhint-disable-next-line contract-name-camelcase -contract BadValidationMagicValue_PreExecHook_Plugin is BaseTestPlugin { - function onInstall(bytes calldata) external override {} - - function onUninstall(bytes calldata) external override {} - - function foo() external pure returns (bytes32) { - return keccak256("bar"); - } - - function pluginManifest() external pure override returns (PluginManifest memory) { - PluginManifest memory manifest; - - manifest.executionFunctions = new bytes4[](1); - manifest.executionFunctions[0] = this.foo.selector; - - manifest.executionHooks = new ManifestExecutionHook[](1); - - // Illegal assignment: validation always allow only usable on runtime validation functions - manifest.executionHooks[0] = ManifestExecutionHook({ - executionSelector: this.foo.selector, - preExecHook: ManifestFunction({ - functionType: ManifestAssociatedFunctionType.RUNTIME_VALIDATION_ALWAYS_ALLOW, - functionId: 0, - dependencyIndex: 0 - }), - postExecHook: ManifestFunction({ - functionType: ManifestAssociatedFunctionType.SELF, - functionId: 0, // Dummy unimplemented function id, but can be added correctly - dependencyIndex: 0 - }) - }); - - return manifest; - } -} - -// solhint-disable-next-line contract-name-camelcase -contract BadValidationMagicValue_PostExecHook_Plugin is BaseTestPlugin { - function onInstall(bytes calldata) external override {} - - function onUninstall(bytes calldata) external override {} - - function foo() external pure returns (bytes32) { - return keccak256("bar"); - } - - function pluginManifest() external pure override returns (PluginManifest memory) { - PluginManifest memory manifest; - - manifest.executionFunctions = new bytes4[](1); - manifest.executionFunctions[0] = this.foo.selector; - - manifest.executionHooks = new ManifestExecutionHook[](1); - // Illegal assignment: validation always allow only usable on runtime validation functions - manifest.executionHooks[0] = ManifestExecutionHook({ - executionSelector: this.foo.selector, - preExecHook: ManifestFunction({ - functionType: ManifestAssociatedFunctionType.SELF, - functionId: 0, // Dummy unimplemented function id, but can be added correctly - dependencyIndex: 0 - }), - postExecHook: ManifestFunction({ - functionType: ManifestAssociatedFunctionType.RUNTIME_VALIDATION_ALWAYS_ALLOW, - functionId: 0, - dependencyIndex: 0 - }) - }); - - return manifest; - } -} - // solhint-disable-next-line contract-name-camelcase contract BadHookMagicValue_UserOpValidationFunction_Plugin is BaseTestPlugin { function onInstall(bytes calldata) external override {} @@ -184,39 +110,3 @@ contract BadHookMagicValue_RuntimeValidationFunction_Plugin is BaseTestPlugin { return manifest; } } - -// solhint-disable-next-line contract-name-camelcase -contract BadHookMagicValue_PostExecHook_Plugin is BaseTestPlugin { - function onInstall(bytes calldata) external override {} - - function onUninstall(bytes calldata) external override {} - - function foo() external pure returns (bytes32) { - return keccak256("bar"); - } - - function pluginManifest() external pure override returns (PluginManifest memory) { - PluginManifest memory manifest; - - manifest.executionFunctions = new bytes4[](1); - manifest.executionFunctions[0] = this.foo.selector; - - manifest.executionHooks = new ManifestExecutionHook[](1); - // Illegal assignment: hook always deny only usable on runtime validation functions - manifest.executionHooks[0] = ManifestExecutionHook({ - executionSelector: this.foo.selector, - preExecHook: ManifestFunction({ - functionType: ManifestAssociatedFunctionType.SELF, - functionId: 0, // Dummy unimplemented function id, but can be added correctly - dependencyIndex: 0 - }), - postExecHook: ManifestFunction({ - functionType: ManifestAssociatedFunctionType.PRE_HOOK_ALWAYS_DENY, - functionId: 0, - dependencyIndex: 0 - }) - }); - - return manifest; - } -} From 443b87b47984014c1561afecbda944dcbfa04fec Mon Sep 17 00:00:00 2001 From: Adam Egyed <5456061+adamegyed@users.noreply.github.com> Date: Tue, 14 May 2024 10:37:24 -0700 Subject: [PATCH 010/111] fix: Add owner view function to manifest validation list (#55) --- src/plugins/owner/SingleOwnerPlugin.sol | 6 +++++- test/account/UpgradeableModularAccount.t.sol | 9 +++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/plugins/owner/SingleOwnerPlugin.sol b/src/plugins/owner/SingleOwnerPlugin.sol index b1d5d5e1..38f16872 100644 --- a/src/plugins/owner/SingleOwnerPlugin.sol +++ b/src/plugins/owner/SingleOwnerPlugin.sol @@ -156,7 +156,7 @@ contract SingleOwnerPlugin is BasePlugin, ISingleOwnerPlugin, IERC1271 { functionId: uint8(FunctionId.VALIDATION_OWNER_OR_SELF), dependencyIndex: 0 // Unused. }); - manifest.validationFunctions = new ManifestAssociatedFunction[](7); + manifest.validationFunctions = new ManifestAssociatedFunction[](8); manifest.validationFunctions[0] = ManifestAssociatedFunction({ executionSelector: this.transferOwnership.selector, associatedFunction: ownerValidationFunction @@ -191,6 +191,10 @@ contract SingleOwnerPlugin is BasePlugin, ISingleOwnerPlugin, IERC1271 { executionSelector: this.isValidSignature.selector, associatedFunction: alwaysAllowRuntime }); + manifest.validationFunctions[7] = ManifestAssociatedFunction({ + executionSelector: this.owner.selector, + associatedFunction: alwaysAllowRuntime + }); return manifest; } diff --git a/test/account/UpgradeableModularAccount.t.sol b/test/account/UpgradeableModularAccount.t.sol index 3ef6f107..c1c0580c 100644 --- a/test/account/UpgradeableModularAccount.t.sol +++ b/test/account/UpgradeableModularAccount.t.sol @@ -419,6 +419,15 @@ contract UpgradeableModularAccountTest is AccountTestBase { assertEq(address(account3), address(uint160(uint256(vm.load(address(account1), slot))))); } + function test_transferOwnership() public { + assertEq(SingleOwnerPlugin(address(account1)).owner(), owner1); + + vm.prank(owner1); + SingleOwnerPlugin(address(account1)).transferOwnership(owner2); + + assertEq(SingleOwnerPlugin(address(account1)).owner(), owner2); + } + // Internal Functions function _printStorageReadsAndWrites(address addr) internal { From 59e8564e3c88f51343eac39c6ef48e5c2455da54 Mon Sep 17 00:00:00 2001 From: Adam Egyed <5456061+adamegyed@users.noreply.github.com> Date: Thu, 16 May 2024 09:25:56 -0700 Subject: [PATCH 011/111] refactor: cleanup migrated content (#56) --- src/libraries/AssociatedLinkedListSetLib.sol | 519 ------------------ src/libraries/PluginStorageLib.sol | 62 --- .../plugins/ModularSessionKeyPlugin.sol | 373 ------------- src/samples/plugins/TokenSessionKeyPlugin.sol | 117 ---- .../plugins/interfaces/ISessionKeyPlugin.sol | 109 ---- .../interfaces/ITokenSessionKeyPlugin.sol | 19 - .../AssociatedLinkedListSetLib.t.sol | 220 -------- test/libraries/PluginStorageLib.t.sol | 89 --- test/mocks/MockERC20.sol | 12 - .../plugins/ModularSessionKeyPlugin.t.sol | 375 ------------- 10 files changed, 1895 deletions(-) delete mode 100644 src/libraries/AssociatedLinkedListSetLib.sol delete mode 100644 src/libraries/PluginStorageLib.sol delete mode 100644 src/samples/plugins/ModularSessionKeyPlugin.sol delete mode 100644 src/samples/plugins/TokenSessionKeyPlugin.sol delete mode 100644 src/samples/plugins/interfaces/ISessionKeyPlugin.sol delete mode 100644 src/samples/plugins/interfaces/ITokenSessionKeyPlugin.sol delete mode 100644 test/libraries/AssociatedLinkedListSetLib.t.sol delete mode 100644 test/libraries/PluginStorageLib.t.sol delete mode 100644 test/mocks/MockERC20.sol delete mode 100644 test/samples/plugins/ModularSessionKeyPlugin.t.sol diff --git a/src/libraries/AssociatedLinkedListSetLib.sol b/src/libraries/AssociatedLinkedListSetLib.sol deleted file mode 100644 index 4663e4ac..00000000 --- a/src/libraries/AssociatedLinkedListSetLib.sol +++ /dev/null @@ -1,519 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -// type SetValue is bytes30; - -// /// @dev The sentinel value is used to indicate the head and tail of the list. -// bytes32 constant SENTINEL_VALUE = bytes32(uint256(1)); - -// /// @dev Removing the last element will result in this flag not being set correctly, but all operations will -// /// function normally, albeit with one extra sload for getAll. -// bytes32 constant HAS_NEXT_FLAG = bytes32(uint256(2)); - -// /// @dev Type representing the set, which is just a storage slot placeholder like the solidity mapping type. -// struct AssociatedLinkedListSet { -// bytes32 placeholder; -// } - -// /// @title Associated Linked List Set Library -// /// @notice Provides a set data structure that is enumerable and held in address-associated storage (per the -// /// ERC-4337 spec) -// library AssociatedLinkedListSetLib { -// // Mapping Entry Byte Layout -// // | value | 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA____ | -// // | meta | 0x____________________________________________________________BBBB | - -// // Bit-layout of the meta bytes (2 bytes) -// // | user flags | 11111111 11111100 | -// // | has next | 00000000 00000010 | -// // | sentinel | 00000000 00000001 | - -// // Mapping keys exclude the upper 15 bits of the meta bytes, which allows keys to be either a value or the -// // sentinel. - -// bytes4 internal constant _ASSOCIATED_STORAGE_PREFIX = 0x9cc6c923; // -// bytes4(keccak256("AssociatedLinkedListSet")) - -// // A custom type representing the index of a storage slot -// type StoragePointer is bytes32; - -// // A custom type representing a pointer to a location in memory beyond the current free memory pointer. -// // Holds a fixed-size buffer similar to "bytes memory", but without a length field. -// // Care must be taken when using these, as they may be overwritten if ANY memory is allocated after -// allocating -// // a TempBytesMemory. -// type TempBytesMemory is bytes32; - -// // INTERNAL METHODS - -// /// @notice Adds a value to a set. -// /// @param set The set to add the value to. -// /// @param associated The address the set is associated with. -// /// @param value The value to add. -// /// @return True if the value was added, false if the value cannot be added (already exists or is zero). -// function tryAdd(AssociatedLinkedListSet storage set, address associated, SetValue value) -// internal -// returns (bool) -// { -// bytes32 unwrappedKey = bytes32(SetValue.unwrap(value)); -// if (unwrappedKey == bytes32(0)) { -// // Cannot add the zero value -// return false; -// } - -// TempBytesMemory keyBuffer = _allocateTempKeyBuffer(set, associated); - -// StoragePointer valueSlot = _mapLookup(keyBuffer, unwrappedKey); -// if (_load(valueSlot) != bytes32(0)) { -// // Entry already exists -// return false; -// } - -// // Load the head of the set -// StoragePointer sentinelSlot = _mapLookup(keyBuffer, SENTINEL_VALUE); -// bytes32 prev = _load(sentinelSlot); -// if (prev == bytes32(0) || isSentinel(prev)) { -// // set is empty, need to do: -// // map[SENTINEL_VALUE] = unwrappedKey; -// // map[unwrappedKey] = SENTINEL_VALUE; -// _store(sentinelSlot, unwrappedKey); -// _store(valueSlot, SENTINEL_VALUE); -// } else { -// // set is not empty, need to do: -// // map[SENTINEL_VALUE] = unwrappedKey | HAS_NEXT_FLAG; -// // map[unwrappedKey] = prev; -// _store(sentinelSlot, unwrappedKey | HAS_NEXT_FLAG); -// _store(valueSlot, prev); -// } - -// return true; -// } - -// /// @notice Removes a value from a set. -// /// @dev This is an O(n) operation, where n is the number of elements in the set. -// /// @param set The set to remove the value from -// /// @param associated The address the set is associated with -// /// @param value The value to remove -// /// @return True if the value was removed, false if the value does not exist -// function tryRemove(AssociatedLinkedListSet storage set, address associated, SetValue value) -// internal -// returns (bool) -// { -// bytes32 unwrappedKey = bytes32(SetValue.unwrap(value)); -// TempBytesMemory keyBuffer = _allocateTempKeyBuffer(set, associated); - -// StoragePointer valueSlot = _mapLookup(keyBuffer, unwrappedKey); -// bytes32 nextValue = _load(valueSlot); -// if (unwrappedKey == bytes32(0) || nextValue == bytes32(0)) { -// // Entry does not exist -// return false; -// } - -// bytes32 prevKey = SENTINEL_VALUE; -// bytes32 currentVal; -// do { -// // Load the current entry -// StoragePointer prevSlot = _mapLookup(keyBuffer, prevKey); -// currentVal = _load(prevSlot); -// bytes32 currentKey = clearFlags(currentVal); -// if (currentKey == unwrappedKey) { -// // Found the entry -// // Set the previous value's next value to the next value, -// // and the flags to the current value's flags. -// // and the next value's `hasNext` flag to determine whether or not the next value is (or points -// to) -// // the sentinel value. - -// // Need to do: -// // map[prevKey] = clearFlags(nextValue) | getUserFlags(currentVal) | (nextValue & -// HAS_NEXT_FLAG); -// // map[currentKey] = bytes32(0); - -// _store(prevSlot, clearFlags(nextValue) | getUserFlags(currentVal) | (nextValue & -// HAS_NEXT_FLAG)); -// _store(valueSlot, bytes32(0)); - -// return true; -// } -// prevKey = currentKey; -// } while (!isSentinel(currentVal) && currentVal != bytes32(0)); -// return false; -// } - -// /// @notice Removes a value from a set, given the previous value in the set. -// /// @dev This is an O(1) operation but requires additional knowledge. -// /// @param set The set to remove the value from -// /// @param associated The address the set is associated with -// /// @param value The value to remove -// /// @param prev The previous value in the set -// /// @return True if the value was removed, false if the value does not exist -// function tryRemoveKnown(AssociatedLinkedListSet storage set, address associated, SetValue value, bytes32 -// prev) -// internal -// returns (bool) -// { -// bytes32 unwrappedKey = bytes32(SetValue.unwrap(value)); -// TempBytesMemory keyBuffer = _allocateTempKeyBuffer(set, associated); - -// prev = clearFlags(prev); - -// if (prev == bytes32(0) || unwrappedKey == bytes32(0)) { -// return false; -// } - -// // assert that the previous key's next value is the value to be removed -// StoragePointer prevSlot = _mapLookup(keyBuffer, prev); -// bytes32 currentValue = _load(prevSlot); -// if (clearFlags(currentValue) != unwrappedKey) { -// return false; -// } - -// StoragePointer valueSlot = _mapLookup(keyBuffer, unwrappedKey); -// bytes32 next = _load(valueSlot); -// if (next == bytes32(0)) { -// // The set didn't actually contain the value -// return false; -// } - -// // Need to do: -// // map[prev] = clearFlags(next) | getUserFlags(currentValue) | (next & HAS_NEXT_FLAG); -// // map[unwrappedKey] = bytes32(0); -// _store(prevSlot, clearFlags(next) | getUserFlags(currentValue) | (next & HAS_NEXT_FLAG)); -// _store(valueSlot, bytes32(0)); - -// return true; -// } - -// /// @notice Removes all values from a set. -// /// @dev This is an O(n) operation, where n is the number of elements in the set. -// /// @param set The set to remove the values from -// /// @param associated The address the set is associated with -// function clear(AssociatedLinkedListSet storage set, address associated) internal { -// TempBytesMemory keyBuffer = _allocateTempKeyBuffer(set, associated); - -// bytes32 cursor = SENTINEL_VALUE; - -// do { -// bytes32 cleared = clearFlags(cursor); -// StoragePointer cursorSlot = _mapLookup(keyBuffer, cleared); -// bytes32 next = _load(cursorSlot); -// _store(cursorSlot, bytes32(0)); -// cursor = next; -// } while (!isSentinel(cursor) && cursor != bytes32(0)); - -// StoragePointer sentinelSlot = _mapLookup(keyBuffer, SENTINEL_VALUE); -// _store(sentinelSlot, bytes32(0)); -// } - -// /// @notice Set the flags on a value in the set. -// /// @dev The user flags can only be set on the upper 14 bits, because the lower two are reserved for the -// /// sentinel and has next bit. -// /// @param set The set containing the value. -// /// @param associated The address the set is associated with. -// /// @param value The value to set the flags on. -// /// @param flags The flags to set. -// /// @return True if the set contains the value and the operation succeeds, false otherwise. -// function trySetFlags(AssociatedLinkedListSet storage set, address associated, SetValue value, uint16 flags) -// internal -// returns (bool) -// { -// bytes32 unwrappedKey = SetValue.unwrap(value); -// TempBytesMemory keyBuffer = _allocateTempKeyBuffer(set, associated); - -// // Ignore the lower 2 bits. -// flags &= 0xFFFC; - -// // If the set doesn't actually contain the value, return false; -// StoragePointer valueSlot = _mapLookup(keyBuffer, unwrappedKey); -// bytes32 next = _load(valueSlot); -// if (next == bytes32(0)) { -// return false; -// } - -// // Set the flags -// _store(valueSlot, clearUserFlags(next) | bytes32(uint256(flags))); - -// return true; -// } - -// /// @notice Set the given flags on a value in the set, preserving the values of other flags. -// /// @dev The user flags can only be set on the upper 14 bits, because the lower two are reserved for the -// /// sentinel and has next bit. -// /// Short-circuits if the flags are already enabled, returning true. -// /// @param set The set containing the value. -// /// @param associated The address the set is associated with. -// /// @param value The value to enable the flags on. -// /// @param flags The flags to enable. -// /// @return True if the operation succeeds or short-circuits due to the flags already being enabled. False -// /// otherwise. -// function tryEnableFlags(AssociatedLinkedListSet storage set, address associated, SetValue value, uint16 -// flags) -// internal -// returns (bool) -// { -// flags &= 0xFFFC; // Allow short-circuit if lower bits are accidentally set -// uint16 currFlags = getFlags(set, associated, value); -// if (currFlags & flags == flags) return true; // flags are already enabled -// return trySetFlags(set, associated, value, currFlags | flags); -// } - -// /// @notice Clear the given flags on a value in the set, preserving the values of other flags. -// /// @notice If the value is not in the set, this function will still return true. -// /// @dev The user flags can only be set on the upper 14 bits, because the lower two are reserved for the -// /// sentinel and has next bit. -// /// Short-circuits if the flags are already disabled, or if set does not contain the value. Short-circuits -// /// return true. -// /// @param set The set containing the value. -// /// @param associated The address the set is associated with. -// /// @param value The value to disable the flags on. -// /// @param flags The flags to disable. -// /// @return True if the operation succeeds, or short-circuits due to the flags already being disabled or if -// the -// /// set does not contain the value. False otherwise. -// function tryDisableFlags(AssociatedLinkedListSet storage set, address associated, SetValue value, uint16 -// flags) -// internal -// returns (bool) -// { -// flags &= 0xFFFC; // Allow short-circuit if lower bits are accidentally set -// uint16 currFlags = getFlags(set, associated, value); -// if (currFlags & flags == 0) return true; // flags are already disabled -// return trySetFlags(set, associated, value, currFlags & ~flags); -// } - -// /// @notice Checks if a set contains a value -// /// @dev This method does not clear the upper bits of `value`, that is expected to be done as part of -// casting -// /// to the correct type. If this function is provided the sentinel value by using the upper bits, this -// function -// /// may returns `true`. -// /// @param set The set to check -// /// @param associated The address the set is associated with -// /// @param value The value to check for -// /// @return True if the set contains the value, false otherwise -// function contains(AssociatedLinkedListSet storage set, address associated, SetValue value) -// internal -// view -// returns (bool) -// { -// bytes32 unwrappedKey = bytes32(SetValue.unwrap(value)); -// TempBytesMemory keyBuffer = _allocateTempKeyBuffer(set, associated); - -// StoragePointer slot = _mapLookup(keyBuffer, unwrappedKey); -// return _load(slot) != bytes32(0); -// } - -// /// @notice Checks if a set is empty -// /// @param set The set to check -// /// @param associated The address the set is associated with -// /// @return True if the set is empty, false otherwise -// function isEmpty(AssociatedLinkedListSet storage set, address associated) internal view returns (bool) { -// TempBytesMemory keyBuffer = _allocateTempKeyBuffer(set, associated); - -// StoragePointer sentinelSlot = _mapLookup(keyBuffer, SENTINEL_VALUE); -// bytes32 val = _load(sentinelSlot); -// return val == bytes32(0) || isSentinel(val); // either the sentinel is unset, or points to itself -// } - -// /// @notice Get the flags on a value in the set. -// /// @dev The reserved lower 2 bits will not be returned, as those are reserved for the sentinel and has next -// /// bit. -// /// @param set The set containing the value. -// /// @param associated The address the set is associated with. -// /// @param value The value to get the flags from. -// /// @return The flags set on the value. -// function getFlags(AssociatedLinkedListSet storage set, address associated, SetValue value) -// internal -// view -// returns (uint16) -// { -// bytes32 unwrappedKey = SetValue.unwrap(value); -// TempBytesMemory keyBuffer = _allocateTempKeyBuffer(set, associated); -// return uint16(uint256(_load(_mapLookup(keyBuffer, unwrappedKey))) & 0xFFFC); -// } - -// /// @notice Check if the flags on a value are enabled. -// /// @dev The reserved lower 2 bits will be ignored, as those are reserved for the sentinel and has next bit. -// /// @param set The set containing the value. -// /// @param associated The address the set is associated with. -// /// @param value The value to check the flags on. -// /// @param flags The flags to check. -// /// @return True if all of the flags are enabled, false otherwise. -// function flagsEnabled(AssociatedLinkedListSet storage set, address associated, SetValue value, uint16 flags) -// internal -// view -// returns (bool) -// { -// flags &= 0xFFFC; -// return getFlags(set, associated, value) & flags == flags; -// } - -// /// @notice Check if the flags on a value are disabled. -// /// @dev The reserved lower 2 bits will be ignored, as those are reserved for the sentinel and has next bit. -// /// @param set The set containing the value. -// /// @param associated The address the set is associated with. -// /// @param value The value to check the flags on. -// /// @param flags The flags to check. -// /// @return True if all of the flags are disabled, false otherwise. -// function flagsDisabled(AssociatedLinkedListSet storage set, address associated, SetValue value, uint16 -// flags) -// internal -// view -// returns (bool) -// { -// flags &= 0xFFFC; -// return ~(getFlags(set, associated, value)) & flags == flags; -// } - -// /// @notice Gets all elements in a set. -// /// @dev This is an O(n) operation, where n is the number of elements in the set. -// /// @param set The set to get the elements of. -// /// @return res An array of all elements in the set. -// function getAll(AssociatedLinkedListSet storage set, address associated) -// internal -// view -// returns (SetValue[] memory res) -// { -// TempBytesMemory keyBuffer = _allocateTempKeyBuffer(set, associated); - -// StoragePointer sentinelSlot = _mapLookup(keyBuffer, SENTINEL_VALUE); -// bytes32 cursor = _load(sentinelSlot); - -// uint256 count; -// while (!isSentinel(cursor) && cursor != bytes32(0)) { -// unchecked { -// ++count; -// } -// bytes32 cleared = clearFlags(cursor); - -// if (hasNext(cursor)) { -// StoragePointer cursorSlot = _mapLookup(keyBuffer, cleared); -// cursor = _load(cursorSlot); -// } else { -// cursor = bytes32(0); -// } -// } - -// res = new SetValue[](count); - -// if (count == 0) { -// return res; -// } - -// // Re-allocate the key buffer because we just overwrote it! -// keyBuffer = _allocateTempKeyBuffer(set, associated); - -// cursor = SENTINEL_VALUE; -// for (uint256 i = 0; i < count;) { -// StoragePointer cursorSlot = _mapLookup(keyBuffer, cursor); -// bytes32 cursorValue = _load(cursorSlot); -// bytes32 cleared = clearFlags(cursorValue); -// res[i] = SetValue.wrap(bytes30(cleared)); -// cursor = cleared; - -// unchecked { -// ++i; -// } -// } -// } - -// function isSentinel(bytes32 value) internal pure returns (bool ret) { -// assembly ("memory-safe") { -// ret := and(value, 1) -// } -// } - -// function hasNext(bytes32 value) internal pure returns (bool) { -// return value & HAS_NEXT_FLAG != 0; -// } - -// function clearFlags(bytes32 val) internal pure returns (bytes32) { -// return val & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0001; -// } - -// /// @dev Preserves the lower two bits -// function clearUserFlags(bytes32 val) internal pure returns (bytes32) { -// return val & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0003; -// } - -// function getUserFlags(bytes32 val) internal pure returns (bytes32) { -// return val & bytes32(uint256(0xFFFC)); -// } - -// // PRIVATE METHODS - -// /// @notice Given an allocated key buffer, returns the storage slot for a given key -// function _mapLookup(TempBytesMemory keyBuffer, bytes32 value) private pure returns (StoragePointer slot) { -// assembly ("memory-safe") { -// // Store the value in the last word. -// let keyWord2 := value -// mstore(add(keyBuffer, 0x60), keyWord2) -// slot := keccak256(keyBuffer, 0x80) -// } -// } - -// /// @notice Allocates a key buffer for a given ID and associated address into scratch space memory. -// /// @dev The returned buffer must not be used if any additional memory is allocated after calling this -// /// function. -// /// @param set The set to allocate the key buffer for. -// /// @param associated The address the set is associated with. -// /// @return key A key buffer that can be used to lookup values in the set -// function _allocateTempKeyBuffer(AssociatedLinkedListSet storage set, address associated) -// private -// pure -// returns (TempBytesMemory key) -// { -// // Key derivation for an entry -// // associated addr (left-padded) || prefix || uint224(0) batchIndex || set storage slot || entry -// // Word 1: -// // | zeros | 0x000000000000000000000000________________________________________ | -// // | address | 0x________________________AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA | -// // Word 2: -// // | prefix | 0xPPPPPPPP________________________________________________________ | -// // | batch index (zero) | 0x________00000000000000000000000000000000000000000000000000000000 | -// // Word 3: -// // | set storage slot | 0xSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS | -// // Word 4: -// // | entry value | 0xVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV____ | -// // | entry meta | 0x____________________________________________________________MMMM | - -// // The batch index is for consistency with PluginStorageLib, and the prefix in front of it is -// // to prevent any potential crafted collisions where the batch index may be equal to storage slot -// // of the ALLS. The prefix is set to the upper bits of the batch index to make it infeasible to -// // reach from just incrementing the value. - -// // This segment is memory-safe because it only uses the scratch space memory after the value of the free -// // memory pointer. -// // See https://docs.soliditylang.org/en/v0.8.21/assembly.html#memory-safety -// assembly ("memory-safe") { -// // Clean upper bits of arguments -// associated := and(associated, 0xffffffffffffffffffffffffffffffffffffffff) - -// // Use memory past-the-free-memory-pointer without updating it, as this is just scratch space -// key := mload(0x40) -// // Store the associated address in the first word, left-padded with zeroes -// mstore(key, associated) -// // Store the prefix and a batch index of 0 -// mstore(add(key, 0x20), _ASSOCIATED_STORAGE_PREFIX) -// // Store the list's storage slot in the third word -// mstore(add(key, 0x40), set.slot) -// // Leaves the last word open for the value entry -// } - -// return key; -// } - -// /// @dev Loads a value from storage -// function _load(StoragePointer ptr) private view returns (bytes32 val) { -// assembly ("memory-safe") { -// val := sload(ptr) -// } -// } - -// /// @dev Writes a value into storage -// function _store(StoragePointer ptr, bytes32 val) private { -// assembly ("memory-safe") { -// sstore(ptr, val) -// } -// } -// } diff --git a/src/libraries/PluginStorageLib.sol b/src/libraries/PluginStorageLib.sol deleted file mode 100644 index 5811f8f8..00000000 --- a/src/libraries/PluginStorageLib.sol +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -// type StoragePointer is bytes32; - -// /// @title Plugin Storage Library -// /// @notice Library for allocating and accessing ERC-4337 address-associated storage within plugins. -// library PluginStorageLib { -// /// @notice Allocates a memory buffer for an associated storage key, and sets the associated address and -// batch -// /// index. -// /// @param addr The address to associate with the storage key. -// /// @param batchIndex The batch index to associate with the storage key. -// /// @param keySize The size of the key in words, where each word is 32 bytes. Not inclusive of the address -// and -// /// batch index. -// /// @return key The allocated memory buffer. -// function allocateAssociatedStorageKey(address addr, uint256 batchIndex, uint8 keySize) -// internal -// pure -// returns (bytes memory key) -// { -// assembly ("memory-safe") { -// // Clear any dirty upper bits of keySize to prevent overflow -// keySize := and(keySize, 0xff) - -// // compute the total size of the buffer, include the address and batch index -// let totalSize := add(64, mul(32, keySize)) - -// // Allocate memory for the key -// key := mload(0x40) -// mstore(0x40, add(add(key, totalSize), 32)) -// mstore(key, totalSize) - -// // Clear any dirty upper bits of address -// addr := and(addr, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) -// // Store the address and batch index in the key buffer -// mstore(add(key, 32), addr) -// mstore(add(key, 64), batchIndex) -// } -// } - -// function associatedStorageLookup(bytes memory key, bytes32 input) internal pure returns (StoragePointer ptr) -// { -// assembly ("memory-safe") { -// mstore(add(key, 96), input) -// ptr := keccak256(add(key, 32), mload(key)) -// } -// } - -// function associatedStorageLookup(bytes memory key, bytes32 input1, bytes32 input2) -// internal -// pure -// returns (StoragePointer ptr) -// { -// assembly ("memory-safe") { -// mstore(add(key, 96), input1) -// mstore(add(key, 128), input2) -// ptr := keccak256(add(key, 32), mload(key)) -// } -// } -// } diff --git a/src/samples/plugins/ModularSessionKeyPlugin.sol b/src/samples/plugins/ModularSessionKeyPlugin.sol deleted file mode 100644 index 53b4c0eb..00000000 --- a/src/samples/plugins/ModularSessionKeyPlugin.sol +++ /dev/null @@ -1,373 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.25; - -// import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -// import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -// import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; -// import {UpgradeableModularAccount} from "../../account/UpgradeableModularAccount.sol"; -// import { -// ManifestFunction, -// ManifestAssociatedFunctionType, -// ManifestAssociatedFunction, -// PluginManifest, -// PluginMetadata, -// SelectorPermission -// } from "../../interfaces/IPlugin.sol"; -// import {BasePlugin} from "../../plugins/BasePlugin.sol"; -// import {IModularSessionKeyPlugin} from "./interfaces/ISessionKeyPlugin.sol"; -// import {ISingleOwnerPlugin} from "../../plugins/owner/ISingleOwnerPlugin.sol"; -// import {SingleOwnerPlugin} from "../../plugins/owner/SingleOwnerPlugin.sol"; -// import {PluginStorageLib, StoragePointer} from "../../libraries/PluginStorageLib.sol"; - -// /// @title Modular Session Key Plugin -// /// @author Decipher ERC-6900 Team -// /// @notice This plugin allows some designated EOA or smart contract to temporarily -// /// own a modular account. Note that this plugin is ONLY for demonstrating the purpose -// /// of the functionalities of ERC-6900, and MUST not be used at the production level. -// /// This modular session key plugin acts as a 'parent plugin' for all specific session -// /// keys. Using dependency, this plugin can be thought as a parent contract that stores -// /// session key duration information, and validation functions for session keys. All -// /// logics for session keys will be implemented in child plugins. -// /// It allows for session key owners to access MSCA both through user operation and -// /// runtime, with its own validation functions. -// /// Also, it has a dependency on SingleOwnerPlugin, to make sure that only the owner of -// /// the MSCA can add or remove session keys. -// contract ModularSessionKeyPlugin is BasePlugin, IModularSessionKeyPlugin { -// using ECDSA for bytes32; -// using PluginStorageLib for address; -// using PluginStorageLib for bytes; -// using EnumerableSet for EnumerableSet.Bytes32Set; - -// string public constant NAME = "Modular Session Key Plugin"; -// string public constant VERSION = "1.0.0"; -// string public constant AUTHOR = "Decipher ERC-6900 Team"; - -// uint256 internal constant _SIG_VALIDATION_FAILED = 1; - -// mapping(address account => EnumerableSet.Bytes32Set) private _sessionKeySet; - -// struct SessionInfo { -// uint48 validAfter; -// uint48 validUntil; -// } - -// // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -// // ┃ Execution functions ┃ -// // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -// /// @inheritdoc IModularSessionKeyPlugin -// function addSessionKey(address sessionKey, bytes4 allowedSelector, uint48 validAfter, uint48 validUntil) -// external -// { -// _addSessionKey(msg.sender, sessionKey, allowedSelector, validAfter, validUntil); -// emit SessionKeyAdded(msg.sender, sessionKey, allowedSelector, validAfter, validUntil); -// } - -// /// @inheritdoc IModularSessionKeyPlugin -// function removeSessionKey(address sessionKey, bytes4 allowedSelector) external { -// _removeSessionKey(msg.sender, sessionKey, allowedSelector); -// emit SessionKeyRemoved(msg.sender, sessionKey, allowedSelector); -// } - -// /// @inheritdoc IModularSessionKeyPlugin -// function addSessionKeyBatch( -// address[] calldata sessionKeys, -// bytes4[] calldata allowedSelectors, -// uint48[] calldata validAfters, -// uint48[] calldata validUntils -// ) external { -// if ( -// sessionKeys.length != allowedSelectors.length || sessionKeys.length != validAfters.length -// || sessionKeys.length != validUntils.length -// ) { -// revert WrongDataLength(); -// } -// for (uint256 i = 0; i < sessionKeys.length;) { -// _addSessionKey(msg.sender, sessionKeys[i], allowedSelectors[i], validAfters[i], validUntils[i]); - -// unchecked { -// ++i; -// } -// } -// emit SessionKeysAdded(msg.sender, sessionKeys, allowedSelectors, validAfters, validUntils); -// } - -// function removeSessionKeyBatch(address[] calldata sessionKeys, bytes4[] calldata allowedSelectors) external -// { -// if (sessionKeys.length != allowedSelectors.length) { -// revert WrongDataLength(); -// } -// for (uint256 i = 0; i < sessionKeys.length;) { -// _removeSessionKey(msg.sender, sessionKeys[i], allowedSelectors[i]); - -// unchecked { -// ++i; -// } -// } -// emit SessionKeysRemoved(msg.sender, sessionKeys, allowedSelectors); -// } - -// // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -// // ┃ Plugin view functions ┃ -// // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -// /// @inheritdoc IModularSessionKeyPlugin -// function getSessionDuration(address account, address sessionKey, bytes4 allowedSelector) -// external -// view -// returns (uint48 validAfter, uint48 validUntil) -// { -// bytes memory key = account.allocateAssociatedStorageKey(0, 1); -// StoragePointer ptr = key.associatedStorageLookup(keccak256(abi.encodePacked(sessionKey, -// allowedSelector))); -// SessionInfo storage sessionInfo = _castPtrToStruct(ptr); -// validAfter = sessionInfo.validAfter; -// validUntil = sessionInfo.validUntil; -// } - -// /// @inheritdoc IModularSessionKeyPlugin -// function getSessionKeysAndSelectors(address account) -// external -// view -// returns (address[] memory sessionKeys, bytes4[] memory selectors) -// { -// EnumerableSet.Bytes32Set storage sessionKeySet = _sessionKeySet[account]; -// uint256 length = sessionKeySet.length(); -// sessionKeys = new address[](length); -// selectors = new bytes4[](length); -// for (uint256 i = 0; i < length;) { -// (sessionKeys[i], selectors[i]) = _castToAddressAndBytes4(sessionKeySet.at(i)); - -// unchecked { -// ++i; -// } -// } -// } - -// // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -// // ┃ Plugin interface functions ┃ -// // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -// /// @inheritdoc BasePlugin -// function onInstall(bytes calldata data) external override { -// if (data.length != 0) { -// ( -// address[] memory sessionKeys, -// bytes4[] memory allowedSelectors, -// uint48[] memory validAfters, -// uint48[] memory validUntils -// ) = abi.decode(data, (address[], bytes4[], uint48[], uint48[])); -// if ( -// sessionKeys.length != allowedSelectors.length || sessionKeys.length != validAfters.length -// || sessionKeys.length != validUntils.length -// ) { -// revert WrongDataLength(); -// } -// for (uint256 i = 0; i < sessionKeys.length;) { -// _addSessionKey(msg.sender, sessionKeys[i], allowedSelectors[i], validAfters[i], validUntils[i]); - -// unchecked { -// ++i; -// } -// } -// } -// } - -// /// @inheritdoc BasePlugin -// function onUninstall(bytes calldata) external override { -// EnumerableSet.Bytes32Set storage sessionKeySet = _sessionKeySet[msg.sender]; -// uint256 length = sessionKeySet.length(); -// for (uint256 i = 0; i < length;) { -// (address sessionKey, bytes4 allowedSelecor) = _castToAddressAndBytes4(sessionKeySet.at(i)); -// _removeSessionKey(msg.sender, sessionKey, allowedSelecor); - -// unchecked { -// ++i; -// } -// } -// } - -// /// @inheritdoc BasePlugin -// function userOpValidationFunction(uint8 functionId, PackedUserOperation calldata userOp, bytes32 userOpHash) -// external -// view -// override -// returns (uint256) -// { -// if (functionId == uint8(FunctionId.VALIDATION_TEMPORARY_OWNER)) { -// (address signer, ECDSA.RecoverError err) = -// userOpHash.toEthSignedMessageHash().tryRecover(userOp.signature); -// if (err != ECDSA.RecoverError.NoError) { -// revert InvalidSignature(); -// } -// bytes4 selector = bytes4(userOp.callData[0:4]); -// bytes memory key = msg.sender.allocateAssociatedStorageKey(0, 1); -// StoragePointer ptr = key.associatedStorageLookup(keccak256(abi.encodePacked(signer, selector))); -// SessionInfo storage duration = _castPtrToStruct(ptr); -// uint48 validAfter = duration.validAfter; -// uint48 validUntil = duration.validUntil; - -// return _packValidationData(validUntil == 0, validUntil, validAfter); -// } -// revert NotImplemented(); -// } - -// /// @inheritdoc BasePlugin -// function runtimeValidationFunction(uint8 functionId, address sender, uint256, bytes calldata data) -// external -// view -// override -// { -// if (functionId == uint8(FunctionId.VALIDATION_TEMPORARY_OWNER)) { -// bytes4 selector = bytes4(data[0:4]); -// bytes memory key = msg.sender.allocateAssociatedStorageKey(0, 1); -// StoragePointer ptr = key.associatedStorageLookup(keccak256(abi.encodePacked(sender, selector))); -// SessionInfo storage duration = _castPtrToStruct(ptr); -// uint48 validAfter = duration.validAfter; -// uint48 validUntil = duration.validUntil; - -// if (validUntil != 0) { -// if (block.timestamp < validAfter || block.timestamp > validUntil) { -// revert WrongTimeRangeForSession(); -// } -// return; -// } -// revert NotAuthorized(); -// } -// revert NotImplemented(); -// } - -// /// @inheritdoc BasePlugin -// function pluginManifest() external pure override returns (PluginManifest memory) { -// PluginManifest memory manifest; - -// manifest.executionFunctions = new bytes4[](4); -// manifest.executionFunctions[0] = this.addSessionKey.selector; -// manifest.executionFunctions[1] = this.removeSessionKey.selector; -// manifest.executionFunctions[2] = this.addSessionKeyBatch.selector; -// manifest.executionFunctions[3] = this.removeSessionKeyBatch.selector; - -// ManifestFunction memory ownerValidationFunction = ManifestFunction({ -// functionType: ManifestAssociatedFunctionType.DEPENDENCY, -// functionId: 0, // Unused. -// dependencyIndex: 0 // Used as first index. -// }); -// manifest.validationFunctions = new ManifestAssociatedFunction[](5); -// manifest.validationFunctions[0] = ManifestAssociatedFunction({ -// executionSelector: this.addSessionKey.selector, -// associatedFunction: ownerValidationFunction -// }); -// manifest.validationFunctions[1] = ManifestAssociatedFunction({ -// executionSelector: this.removeSessionKey.selector, -// associatedFunction: ownerValidationFunction -// }); -// manifest.validationFunctions[2] = ManifestAssociatedFunction({ -// executionSelector: this.addSessionKeyBatch.selector, -// associatedFunction: ownerValidationFunction -// }); -// manifest.validationFunctions[3] = ManifestAssociatedFunction({ -// executionSelector: this.removeSessionKeyBatch.selector, -// associatedFunction: ownerValidationFunction -// }); - -// ManifestFunction memory alwaysAllowFunction = ManifestFunction({ -// functionType: ManifestAssociatedFunctionType.RUNTIME_VALIDATION_ALWAYS_ALLOW, -// functionId: 0, // Unused. -// dependencyIndex: 0 // Unused. -// }); - -// manifest.validationFunctions[4] = ManifestAssociatedFunction({ -// executionSelector: this.getSessionDuration.selector, -// associatedFunction: alwaysAllowFunction -// }); - -// manifest.dependencyInterfaceIds = new bytes4[](1); -// manifest.dependencyInterfaceIds[0] = type(ISingleOwnerPlugin).interfaceId; - -// return manifest; -// } - -// /// @inheritdoc BasePlugin -// function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { -// PluginMetadata memory metadata; -// metadata.name = NAME; -// metadata.version = VERSION; -// metadata.author = AUTHOR; - -// return metadata; -// } - -// // ┏━━━━━━━━━━━━━━━┓ -// // ┃ EIP-165 ┃ -// // ┗━━━━━━━━━━━━━━━┛ - -// /// @inheritdoc BasePlugin -// function supportsInterface(bytes4 interfaceId) public view override returns (bool) { -// return interfaceId == type(IModularSessionKeyPlugin).interfaceId || -// super.supportsInterface(interfaceId); -// } - -// // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -// // ┃ Internal / Private functions ┃ -// // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -// function _addSessionKey( -// address account, -// address sessionKey, -// bytes4 allowedSelector, -// uint48 validAfter, -// uint48 validUntil -// ) internal { -// if (validUntil <= validAfter) { -// revert WrongTimeRangeForSession(); -// } -// bytes memory key = account.allocateAssociatedStorageKey(0, 1); -// StoragePointer ptr = key.associatedStorageLookup(keccak256(abi.encodePacked(sessionKey, -// allowedSelector))); -// SessionInfo storage sessionInfo = _castPtrToStruct(ptr); -// sessionInfo.validAfter = validAfter; -// sessionInfo.validUntil = validUntil; - -// EnumerableSet.Bytes32Set storage sessionKeySet = _sessionKeySet[account]; -// sessionKeySet.add(_castToBytes32(sessionKey, allowedSelector)); -// } - -// function _removeSessionKey(address account, address sessionKey, bytes4 allowedSelector) internal { -// bytes memory key = account.allocateAssociatedStorageKey(0, 1); -// StoragePointer ptr = key.associatedStorageLookup(keccak256(abi.encodePacked(sessionKey, -// allowedSelector))); -// SessionInfo storage sessionInfo = _castPtrToStruct(ptr); -// sessionInfo.validAfter = 0; -// sessionInfo.validUntil = 0; - -// EnumerableSet.Bytes32Set storage sessionKeySet = _sessionKeySet[account]; -// sessionKeySet.remove(_castToBytes32(sessionKey, allowedSelector)); -// } - -// function _castPtrToStruct(StoragePointer ptr) internal pure returns (SessionInfo storage val) { -// assembly ("memory-safe") { -// val.slot := ptr -// } -// } - -// function _castToBytes32(address addr, bytes4 b4) internal pure returns (bytes32 res) { -// assembly { -// res := or(shl(32, addr), b4) -// } -// } - -// function _castToAddressAndBytes4(bytes32 b32) internal pure returns (address addr, bytes4 b4) { -// assembly { -// addr := shr(32, b32) -// b4 := and(b32, 0xFFFFFFFF) -// } -// } - -// function _packValidationData(bool sigFailed, uint48 validUntil, uint48 validAfter) -// internal -// pure -// returns (uint256) -// { -// return (sigFailed ? 1 : 0) | (uint256(validUntil) << 160) | (uint256(validAfter) << (160 + 48)); -// } -// } diff --git a/src/samples/plugins/TokenSessionKeyPlugin.sol b/src/samples/plugins/TokenSessionKeyPlugin.sol deleted file mode 100644 index 09275374..00000000 --- a/src/samples/plugins/TokenSessionKeyPlugin.sol +++ /dev/null @@ -1,117 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.25; - -// import { -// ManifestFunction, -// ManifestAssociatedFunctionType, -// ManifestAssociatedFunction, -// PluginManifest, -// PluginMetadata, -// SelectorPermission, -// ManifestExternalCallPermission -// } from "../../interfaces/IPlugin.sol"; -// import {BasePlugin} from "../../plugins/BasePlugin.sol"; -// import {ModularSessionKeyPlugin} from "./ModularSessionKeyPlugin.sol"; -// import {ITokenSessionKeyPlugin} from "./interfaces/ITokenSessionKeyPlugin.sol"; -// import {IModularSessionKeyPlugin} from "./interfaces/ISessionKeyPlugin.sol"; -// import {IPluginExecutor} from "../../interfaces/IPluginExecutor.sol"; - -// /// @title Token Session Key Plugin -// /// @author Decipher ERC-6900 Team -// /// @notice This plugin acts as a 'child plugin' for ModularSessionKeyPlugin. -// /// It implements the logic for session keys that are allowed to call ERC20 -// /// transferFrom function. It allows for session key owners to access MSCA -// /// with `transferFromSessionKey` function, which calls `executeFromPluginExternal` -// /// function in PluginExecutor contract. -// /// The target ERC20 contract and the selector for transferFrom function are hardcoded -// /// in this plugin, since the pluginManifest function requires the information of -// /// permitted external calls not to be changed in the future. For other child session -// /// key plugins, there can be a set of permitted external calls according to the -// /// specific needs. -// contract TokenSessionKeyPlugin is BasePlugin, ITokenSessionKeyPlugin { -// string public constant NAME = "Token Session Key Plugin"; -// string public constant VERSION = "1.0.0"; -// string public constant AUTHOR = "Decipher ERC-6900 Team"; - -// // Mock address of target ERC20 contract -// address public constant TARGET_ERC20_CONTRACT = 0xdeaDDeADDEaDdeaDdEAddEADDEAdDeadDEADDEaD; -// bytes4 public constant TRANSFERFROM_SELECTOR = -// bytes4(keccak256(bytes("transferFrom(address,address,uint256)"))); - -// // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -// // ┃ Execution functions ┃ -// // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -// /// @inheritdoc ITokenSessionKeyPlugin -// function transferFromSessionKey(address target, address from, address to, uint256 amount) -// external -// returns (bytes memory returnData) -// { -// bytes memory data = abi.encodeWithSelector(TRANSFERFROM_SELECTOR, from, to, amount); -// returnData = IPluginExecutor(msg.sender).executeFromPluginExternal(target, 0, data); -// } - -// // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -// // ┃ Plugin interface functions ┃ -// // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -// /// @inheritdoc BasePlugin -// function onInstall(bytes calldata data) external override {} - -// /// @inheritdoc BasePlugin -// function onUninstall(bytes calldata data) external override {} - -// /// @inheritdoc BasePlugin -// function pluginManifest() external pure override returns (PluginManifest memory) { -// PluginManifest memory manifest; - -// manifest.executionFunctions = new bytes4[](1); -// manifest.executionFunctions[0] = this.transferFromSessionKey.selector; - -// ManifestFunction memory tempOwnerValidationFunction = ManifestFunction({ -// functionType: ManifestAssociatedFunctionType.DEPENDENCY, -// functionId: 0, // Unused -// dependencyIndex: 0 // Used as first index -// }); - -// manifest.validationFunctions = new ManifestAssociatedFunction[](1); -// manifest.validationFunctions[0] = ManifestAssociatedFunction({ -// executionSelector: this.transferFromSessionKey.selector, -// associatedFunction: tempOwnerValidationFunction -// }); - -// manifest.dependencyInterfaceIds = new bytes4[](1); -// manifest.dependencyInterfaceIds[0] = type(IModularSessionKeyPlugin).interfaceId; - -// bytes4[] memory permittedExternalSelectors = new bytes4[](1); -// permittedExternalSelectors[0] = TRANSFERFROM_SELECTOR; - -// manifest.permittedExternalCalls = new ManifestExternalCallPermission[](1); -// manifest.permittedExternalCalls[0] = ManifestExternalCallPermission({ -// externalAddress: TARGET_ERC20_CONTRACT, -// permitAnySelector: false, -// selectors: permittedExternalSelectors -// }); - -// return manifest; -// } - -// /// @inheritdoc BasePlugin -// function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { -// PluginMetadata memory metadata; -// metadata.name = NAME; -// metadata.version = VERSION; -// metadata.author = AUTHOR; - -// return metadata; -// } - -// // ┏━━━━━━━━━━━━━━━┓ -// // ┃ EIP-165 ┃ -// // ┗━━━━━━━━━━━━━━━┛ - -// /// @inheritdoc BasePlugin -// function supportsInterface(bytes4 interfaceId) public view override returns (bool) { -// return interfaceId == type(ITokenSessionKeyPlugin).interfaceId || super.supportsInterface(interfaceId); -// } -// } diff --git a/src/samples/plugins/interfaces/ISessionKeyPlugin.sol b/src/samples/plugins/interfaces/ISessionKeyPlugin.sol deleted file mode 100644 index fd1b9bd0..00000000 --- a/src/samples/plugins/interfaces/ISessionKeyPlugin.sol +++ /dev/null @@ -1,109 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.25; - -// import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; - -// interface IModularSessionKeyPlugin { -// enum FunctionId { -// VALIDATION_TEMPORARY_OWNER -// } - -// /// @notice This event is emitted when a session key is added to the account. -// /// @param account The account whose session key is updated. -// /// @param sessionKey The address of the session key. -// /// @param selector The selector of the function that the session key is allowed to call. -// /// @param validAfter The time after which the owner is valid. -// /// @param validUntil The time until which the owner is valid. -// event SessionKeyAdded( -// address indexed account, address indexed sessionKey, bytes4 selector, uint48 validAfter, uint48 -// validUntil -// ); - -// /// @notice This event is emitted when a session key is removed from the account. -// /// @param account The account whose session key is updated. -// /// @param sessionKey The address of the session key. -// /// @param selector The selector of the function that the session key is allowed to call. -// event SessionKeyRemoved(address indexed account, address indexed sessionKey, bytes4 selector); - -// /// @notice This event is emitted when session keys are added to the account. -// /// @param account The account whose session keys are updated. -// /// @param sessionKeys The addresses of the session keys. -// /// @param selectors The selectors of the functions that the session keys are allowed to call. -// /// @param validAfters The times after which the owners are valid. -// /// @param validUntils The times until which the owners are valid. -// event SessionKeysAdded( -// address indexed account, -// address[] sessionKeys, -// bytes4[] selectors, -// uint48[] validAfters, -// uint48[] validUntils -// ); - -// /// @notice This event is emitted when session keys are removed from the account. -// /// @param account The account whose session keys are updated. -// /// @param sessionKeys The addresses of the session keys. -// /// @param selectors The selectors of the functions that the session keys are allowed to call. -// event SessionKeysRemoved(address indexed account, address[] sessionKeys, bytes4[] selectors); - -// error InvalidSignature(); -// error NotAuthorized(); -// error WrongTimeRangeForSession(); -// error WrongDataLength(); - -// /// @notice Add a session key to the account. -// /// @dev This function is installed on the account as part of plugin installation, and should -// /// only be called from an account. The function selector installed by a child session key plugin -// /// is passed as a parameter, which enforces its own permissions on the calls it can make. -// /// @param sessionKey The address of the session key. -// /// @param allowedSelector The selector of the function that the session key is allowed to call. -// /// @param validAfter The time after which the owner is valid. -// /// @param validUntil The time until which the owner is valid. -// function addSessionKey(address sessionKey, bytes4 allowedSelector, uint48 validAfter, uint48 validUntil) -// external; - -// /// @notice Remove a session key from the account. -// /// @dev This function is installed on the account as part of plugin installation, and should -// /// only be called from an account. -// /// @param sessionKey The address of the session key. -// /// @param allowedSelector The selector of the function that the session key is allowed to call. -// function removeSessionKey(address sessionKey, bytes4 allowedSelector) external; - -// /// @notice Add session keys to the account. -// /// @dev This function is installed on the account as part of plugin installation, and should -// /// only be called from an account. -// /// @param sessionKeys The addresses of the session keys. -// /// @param allowedSelectors The selectors of the functions that the session keys are allowed to call. -// /// @param validAfters The times after which the owners are valid. -// /// @param validUntils The times until which the owners are valid. -// function addSessionKeyBatch( -// address[] calldata sessionKeys, -// bytes4[] calldata allowedSelectors, -// uint48[] calldata validAfters, -// uint48[] calldata validUntils -// ) external; - -// /// @notice Remove session keys from the account. -// /// @dev This function is installed on the account as part of plugin installation, and should -// /// only be called from an account. -// /// @param sessionKeys The addresses of the session keys. -// /// @param allowedSelectors The selectors of the functions that the session keys are allowed to call. -// function removeSessionKeyBatch(address[] calldata sessionKeys, bytes4[] calldata allowedSelectors) external; - -// /// @notice Get Session data for a given account and session key. -// /// @param account The account to get session data for. -// /// @param sessionKey The address of the session key. -// /// @param allowedSelector The selector of the function that the session key is allowed to call. -// function getSessionDuration(address account, address sessionKey, bytes4 allowedSelector) -// external -// view -// returns (uint48 validAfter, uint48 validUntil); - -// /// @notice Get all session keys and selectors for a given account. -// /// @param account The account to get session keys and selectors for. -// /// @return sessionKeys The addresses of the session keys. -// /// @return selectors The selectors of the functions that the session keys are allowed to call. -// function getSessionKeysAndSelectors(address account) -// external -// view -// returns (address[] memory sessionKeys, bytes4[] memory selectors); -// } diff --git a/src/samples/plugins/interfaces/ITokenSessionKeyPlugin.sol b/src/samples/plugins/interfaces/ITokenSessionKeyPlugin.sol deleted file mode 100644 index 2de98c0a..00000000 --- a/src/samples/plugins/interfaces/ITokenSessionKeyPlugin.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.25; - -// import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; - -// interface ITokenSessionKeyPlugin { -// error NotAuthorized(); - -// /// @notice Route call to executeFromPluginExternal at the MSCA. -// /// @dev This function will call with value = 0, since sending ether -// /// to ERC20 contract is not a normal case. -// /// @param target The target address to execute the call on. -// /// @param from The address to transfer tokens from. -// /// @param to The address to transfer tokens to. -// /// @param amount The amount of tokens to transfer. -// function transferFromSessionKey(address target, address from, address to, uint256 amount) -// external -// returns (bytes memory returnData); -// } diff --git a/test/libraries/AssociatedLinkedListSetLib.t.sol b/test/libraries/AssociatedLinkedListSetLib.t.sol deleted file mode 100644 index 99ed5e25..00000000 --- a/test/libraries/AssociatedLinkedListSetLib.t.sol +++ /dev/null @@ -1,220 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; - -// import {Test} from "forge-std/Test.sol"; -// import { -// AssociatedLinkedListSet, -// AssociatedLinkedListSetLib, -// SENTINEL_VALUE, -// SetValue -// } from "../../src/libraries/AssociatedLinkedListSetLib.sol"; - -// contract AssociatedLinkedListSetLibTest is Test { -// using AssociatedLinkedListSetLib for AssociatedLinkedListSet; - -// AssociatedLinkedListSet internal _set1; -// AssociatedLinkedListSet internal _set2; - -// address internal _associated = address(this); - -// // User-defined function for wrapping from bytes30 (uint240) to SetValue -// // Can define a custom one for addresses, uints, etc. -// function _getListValue(uint240 value) internal pure returns (SetValue) { -// return SetValue.wrap(bytes30(value)); -// } - -// function test_add_contains() public { -// SetValue value = _getListValue(12); - -// assertTrue(_set1.tryAdd(_associated, value)); -// assertTrue(_set1.contains(_associated, value)); -// } - -// function test_empty() public { -// SetValue value = _getListValue(12); - -// assertFalse(_set1.contains(_associated, value)); -// assertTrue(_set1.isEmpty(_associated)); -// } - -// function test_remove() public { -// SetValue value = _getListValue(12); - -// assertTrue(_set1.tryAdd(_associated, value)); -// assertTrue(_set1.contains(_associated, value)); - -// assertTrue(_set1.tryRemove(_associated, value)); -// assertFalse(_set1.contains(_associated, value)); -// } - -// function test_remove_empty() public { -// SetValue value = _getListValue(12); - -// assertFalse(_set1.tryRemove(_associated, value)); -// } - -// function test_remove_nonexistent() public { -// SetValue value = _getListValue(12); - -// assertTrue(_set1.tryAdd(_associated, value)); -// assertTrue(_set1.contains(_associated, value)); - -// SetValue value2 = _getListValue(13); -// assertFalse(_set1.tryRemove(_associated, value2)); -// assertTrue(_set1.contains(_associated, value)); -// } - -// function test_remove_nonexistent_empty() public { -// SetValue value = _getListValue(12); - -// assertFalse(_set1.tryRemove(_associated, value)); -// } - -// function test_remove_nonexistent_empty2() public { -// SetValue value = _getListValue(12); - -// assertTrue(_set1.tryAdd(_associated, value)); -// assertTrue(_set1.contains(_associated, value)); - -// SetValue value2 = _getListValue(13); -// assertFalse(_set1.tryRemove(_associated, value2)); -// assertTrue(_set1.contains(_associated, value)); -// } - -// function test_add_remove_add() public { -// SetValue value = _getListValue(12); - -// assertTrue(_set1.tryAdd(_associated, value)); -// assertTrue(_set1.contains(_associated, value)); - -// assertTrue(_set1.tryRemove(_associated, value)); -// assertFalse(_set1.contains(_associated, value)); - -// assertTrue(_set1.tryAdd(_associated, value)); -// assertTrue(_set1.contains(_associated, value)); -// } - -// function test_add_remove_add_empty() public { -// SetValue value = _getListValue(12); - -// assertTrue(_set1.tryAdd(_associated, value)); -// assertTrue(_set1.contains(_associated, value)); - -// assertTrue(_set1.tryRemove(_associated, value)); -// assertFalse(_set1.contains(_associated, value)); - -// assertTrue(_set1.tryAdd(_associated, value)); -// assertTrue(_set1.contains(_associated, value)); -// } - -// function test_no_address_collision() public { -// SetValue value = _getListValue(12); - -// assertTrue(_set1.tryAdd(_associated, value)); -// assertTrue(_set1.contains(_associated, value)); -// assertFalse(_set2.contains(_associated, value)); -// } - -// function test_clear() public { -// SetValue value = _getListValue(12); - -// assertTrue(_set1.tryAdd(_associated, value)); -// assertTrue(_set1.contains(_associated, value)); - -// _set1.clear(_associated); - -// assertFalse(_set1.contains(_associated, value)); -// assertTrue(_set1.isEmpty(_associated)); -// } - -// function test_getAll() public { -// SetValue value = _getListValue(12); -// SetValue value2 = _getListValue(13); - -// assertTrue(_set1.tryAdd(_associated, value)); -// assertTrue(_set1.tryAdd(_associated, value2)); - -// SetValue[] memory values = _set1.getAll(_associated); -// assertEq(values.length, 2); -// // Returned set will be in reverse order of added elements -// assertEq(SetValue.unwrap(values[1]), SetValue.unwrap(value)); -// assertEq(SetValue.unwrap(values[0]), SetValue.unwrap(value2)); -// } - -// function test_getAll2() public { -// SetValue value = _getListValue(12); -// SetValue value2 = _getListValue(13); -// SetValue value3 = _getListValue(14); - -// assertTrue(_set1.tryAdd(_associated, value)); -// assertTrue(_set1.tryAdd(_associated, value2)); -// assertTrue(_set1.tryAdd(_associated, value3)); - -// SetValue[] memory values = _set1.getAll(_associated); -// assertEq(values.length, 3); -// // Returned set will be in reverse order of added elements -// assertEq(SetValue.unwrap(values[2]), SetValue.unwrap(value)); -// assertEq(SetValue.unwrap(values[1]), SetValue.unwrap(value2)); -// assertEq(SetValue.unwrap(values[0]), SetValue.unwrap(value3)); -// } - -// function test_getAll_empty() public { -// SetValue[] memory values = _set1.getAll(_associated); -// assertEq(values.length, 0); -// } - -// function test_tryRemoveKnown1() public { -// SetValue value = _getListValue(12); - -// assertTrue(_set1.tryAdd(_associated, value)); -// assertTrue(_set1.contains(_associated, value)); - -// assertTrue(_set1.tryRemoveKnown(_associated, value, SENTINEL_VALUE)); -// assertFalse(_set1.contains(_associated, value)); -// assertTrue(_set1.isEmpty(_associated)); -// } - -// function test_tryRemoveKnown2() public { -// SetValue value1 = _getListValue(12); -// SetValue value2 = _getListValue(13); - -// assertTrue(_set1.tryAdd(_associated, value1)); -// assertTrue(_set1.tryAdd(_associated, value2)); -// assertTrue(_set1.contains(_associated, value1)); -// assertTrue(_set1.contains(_associated, value2)); - -// // Assert that getAll returns the correct values -// SetValue[] memory values = _set1.getAll(_associated); -// assertEq(values.length, 2); -// assertEq(SetValue.unwrap(values[1]), SetValue.unwrap(value1)); -// assertEq(SetValue.unwrap(values[0]), SetValue.unwrap(value2)); - -// assertTrue(_set1.tryRemoveKnown(_associated, value1, bytes32(SetValue.unwrap(value2)))); -// assertFalse(_set1.contains(_associated, value1)); -// assertTrue(_set1.contains(_associated, value2)); - -// // Assert that getAll returns the correct values -// values = _set1.getAll(_associated); -// assertEq(values.length, 1); -// assertEq(SetValue.unwrap(values[0]), SetValue.unwrap(value2)); - -// assertTrue(_set1.tryRemoveKnown(_associated, value2, SENTINEL_VALUE)); -// assertFalse(_set1.contains(_associated, value1)); - -// assertTrue(_set1.isEmpty(_associated)); -// } - -// function test_tryRemoveKnown_invalid1() public { -// SetValue value1 = _getListValue(12); -// SetValue value2 = _getListValue(13); - -// assertTrue(_set1.tryAdd(_associated, value1)); -// assertTrue(_set1.tryAdd(_associated, value2)); - -// assertFalse(_set1.tryRemoveKnown(_associated, value1, bytes32(SetValue.unwrap(value1)))); -// assertTrue(_set1.contains(_associated, value1)); - -// assertFalse(_set1.tryRemoveKnown(_associated, value2, bytes32(SetValue.unwrap(value2)))); -// assertTrue(_set1.contains(_associated, value2)); -// } -// } diff --git a/test/libraries/PluginStorageLib.t.sol b/test/libraries/PluginStorageLib.t.sol deleted file mode 100644 index 3bd1dc76..00000000 --- a/test/libraries/PluginStorageLib.t.sol +++ /dev/null @@ -1,89 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; - -// import {Test} from "forge-std/Test.sol"; -// import {PluginStorageLib, StoragePointer} from "../../src/libraries/PluginStorageLib.sol"; - -// contract PluginStorageLibTest is Test { -// using PluginStorageLib for bytes; -// using PluginStorageLib for bytes32; - -// uint256 public constant FUZZ_ARR_SIZE = 32; - -// address public account1; - -// struct TestStruct { -// uint256 a; -// uint256 b; -// } - -// function setUp() public { -// account1 = makeAddr("account1"); -// } - -// function test_storagePointer() public { -// bytes memory key = PluginStorageLib.allocateAssociatedStorageKey(account1, 0, 1); - -// StoragePointer ptr = PluginStorageLib.associatedStorageLookup( -// key, hex"00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff" -// ); -// TestStruct storage val = _castPtrToStruct(ptr); - -// vm.record(); -// val.a = 0xdeadbeef; -// val.b = 123; -// (, bytes32[] memory accountWrites) = vm.accesses(address(this)); - -// assertEq(accountWrites.length, 2); -// bytes32 expectedKey = keccak256( -// abi.encodePacked( -// uint256(uint160(account1)), -// uint256(0), -// hex"00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff" -// ) -// ); -// assertEq(accountWrites[0], expectedKey); -// assertEq(vm.load(address(this), expectedKey), bytes32(uint256(0xdeadbeef))); -// assertEq(accountWrites[1], bytes32(uint256(expectedKey) + 1)); -// assertEq(vm.load(address(this), bytes32(uint256(expectedKey) + 1)), bytes32(uint256(123))); -// } - -// function testFuzz_storagePointer( -// address account, -// uint256 batchIndex, -// bytes32 inputKey, -// uint256[FUZZ_ARR_SIZE] calldata values -// ) public { -// bytes memory key = PluginStorageLib.allocateAssociatedStorageKey(account, batchIndex, 1); -// uint256[FUZZ_ARR_SIZE] storage val = -// _castPtrToArray(PluginStorageLib.associatedStorageLookup(key, inputKey)); -// // Write values to storage -// vm.record(); -// for (uint256 i = 0; i < FUZZ_ARR_SIZE; i++) { -// val[i] = values[i]; -// } -// // Assert the writes took place in the right location, and the correct value is stored there -// (, bytes32[] memory accountWrites) = vm.accesses(address(this)); -// assertEq(accountWrites.length, FUZZ_ARR_SIZE); -// for (uint256 i = 0; i < FUZZ_ARR_SIZE; i++) { -// bytes32 expectedKey = bytes32( -// uint256(keccak256(abi.encodePacked(uint256(uint160(account)), uint256(batchIndex), inputKey))) + -// i -// ); -// assertEq(accountWrites[i], expectedKey); -// assertEq(vm.load(address(this), expectedKey), bytes32(uint256(values[i]))); -// } -// } - -// function _castPtrToArray(StoragePointer ptr) internal pure returns (uint256[FUZZ_ARR_SIZE] storage val) { -// assembly ("memory-safe") { -// val.slot := ptr -// } -// } - -// function _castPtrToStruct(StoragePointer ptr) internal pure returns (TestStruct storage val) { -// assembly ("memory-safe") { -// val.slot := ptr -// } -// } -// } diff --git a/test/mocks/MockERC20.sol b/test/mocks/MockERC20.sol deleted file mode 100644 index d87a9234..00000000 --- a/test/mocks/MockERC20.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; - -import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; - -contract MockERC20 is ERC20 { - constructor(string memory name, string memory symbol) ERC20(name, symbol) {} - - function mint(address account, uint256 amount) external { - _mint(account, amount); - } -} diff --git a/test/samples/plugins/ModularSessionKeyPlugin.t.sol b/test/samples/plugins/ModularSessionKeyPlugin.t.sol deleted file mode 100644 index 92ee87f6..00000000 --- a/test/samples/plugins/ModularSessionKeyPlugin.t.sol +++ /dev/null @@ -1,375 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; - -// import {Test} from "forge-std/Test.sol"; - -// import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.sol"; -// import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; -// import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; - -// import {SingleOwnerPlugin} from "../../../src/plugins/owner/SingleOwnerPlugin.sol"; -// import {ISingleOwnerPlugin} from "../../../src/plugins/owner/ISingleOwnerPlugin.sol"; -// import {ModularSessionKeyPlugin} from "../../../src/samples/plugins/ModularSessionKeyPlugin.sol"; -// import {IModularSessionKeyPlugin} from "../../../src/samples/plugins/interfaces/ISessionKeyPlugin.sol"; -// import {TokenSessionKeyPlugin} from "../../../src/samples/plugins/TokenSessionKeyPlugin.sol"; -// import {ITokenSessionKeyPlugin} from "../../../src/samples/plugins/interfaces/ITokenSessionKeyPlugin.sol"; - -// import {UpgradeableModularAccount} from "../../../src/account/UpgradeableModularAccount.sol"; -// import {MSCAFactoryFixture} from "../../mocks/MSCAFactoryFixture.sol"; -// import {FunctionReference, FunctionReferenceLib} from "../../../src/helpers/FunctionReferenceLib.sol"; -// import {IPluginManager} from "../../../src/interfaces/IPluginManager.sol"; -// import {MockERC20} from "../../mocks/MockERC20.sol"; - -// contract ModularSessionKeyPluginTest is Test { -// using ECDSA for bytes32; - -// SingleOwnerPlugin public ownerPlugin; -// ModularSessionKeyPlugin public modularSessionKeyPlugin; -// TokenSessionKeyPlugin public tokenSessionKeyPlugin; -// EntryPoint public entryPoint; -// MSCAFactoryFixture public factory; -// UpgradeableModularAccount public account; - -// MockERC20 public mockERC20impl; -// MockERC20 public mockERC20; -// address public mockEmptyERC20Addr; - -// address public owner; -// uint256 public ownerKey; - -// address public maliciousOwner; - -// address public tempOwner; -// uint256 public tempOwnerKey; - -// address public target; - -// address payable public beneficiary; - -// uint256 public constant CALL_GAS_LIMIT = 150000; -// uint256 public constant VERIFICATION_GAS_LIMIT = 3600000; - -// bytes4 public constant TRANSFERFROM_SESSIONKEY_SELECTOR = -// ITokenSessionKeyPlugin.transferFromSessionKey.selector; - -// // Event declarations (needed for vm.expectEmit) -// event UserOperationRevertReason( -// bytes32 indexed userOpHash, address indexed sender, uint256 nonce, bytes revertReason -// ); -// event SessionKeyAdded( -// address indexed account, address indexed sessionKey, bytes4 allowedSelector, uint48 _after, uint48 -// _until -// ); -// event SessionKeyRemoved(address indexed account, address indexed sessionKey, bytes4 allowedSelector); -// event SessionKeysAdded( -// address indexed account, address[] sessionKeys, bytes4[] allowedSelectors, uint48[] afters, uint48[] -// untils -// ); -// event SessionKeysRemoved(address indexed account, address[] sessionKeys, bytes4[] allowedSelectors); -// event PluginUninstalled(address indexed plugin, bool indexed onUninstallSuccess); - -// function setUp() public { -// ownerPlugin = new SingleOwnerPlugin(); -// modularSessionKeyPlugin = new ModularSessionKeyPlugin(); -// tokenSessionKeyPlugin = new TokenSessionKeyPlugin(); - -// entryPoint = new EntryPoint(); -// factory = new MSCAFactoryFixture(entryPoint, ownerPlugin); -// mockERC20impl = new MockERC20("Mock", "MCK"); - -// // Etching MockERC20 code into hardcoded address at TokenSessionKeyPlugin -// mockEmptyERC20Addr = tokenSessionKeyPlugin.TARGET_ERC20_CONTRACT(); -// bytes memory code = address(mockERC20impl).code; -// vm.etch(mockEmptyERC20Addr, code); -// mockERC20 = MockERC20(mockEmptyERC20Addr); - -// (owner, ownerKey) = makeAddrAndKey("owner"); -// (maliciousOwner,) = makeAddrAndKey("maliciousOwner"); -// (tempOwner, tempOwnerKey) = makeAddrAndKey("tempOwner"); -// target = makeAddr("target"); - -// beneficiary = payable(makeAddr("beneficiary")); -// vm.deal(beneficiary, 1 wei); -// vm.deal(owner, 10 ether); - -// // Here, SingleOwnerPlugin already installed in factory -// account = factory.createAccount(owner, 0); - -// // Mint Mock ERC20 Tokens to account -// mockERC20.mint(address(account), 1 ether); -// // Fund the account with some ether -// vm.deal(address(account), 1 ether); - -// vm.startPrank(owner); -// FunctionReference[] memory modularSessionDependency = new FunctionReference[](1); -// modularSessionDependency[0] = FunctionReferenceLib.pack( -// address(ownerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER_OR_SELF) -// ); - -// bytes32 modularSessionKeyManifestHash = keccak256(abi.encode(modularSessionKeyPlugin.pluginManifest())); - -// address[] memory tempOwners = new address[](1); -// tempOwners[0] = address(tempOwner); - -// bytes4[] memory allowedSelectors = new bytes4[](1); -// allowedSelectors[0] = TRANSFERFROM_SESSIONKEY_SELECTOR; - -// uint48[] memory afters = new uint48[](1); -// afters[0] = 0; - -// uint48[] memory untils = new uint48[](1); -// untils[0] = 2; - -// bytes memory data = abi.encode(tempOwners, allowedSelectors, afters, untils); - -// account.installPlugin({ -// plugin: address(modularSessionKeyPlugin), -// manifestHash: modularSessionKeyManifestHash, -// pluginInstallData: data, -// dependencies: modularSessionDependency -// }); - -// FunctionReference[] memory tokenSessionDependency = new FunctionReference[](1); -// tokenSessionDependency[0] = FunctionReferenceLib.pack( -// address(modularSessionKeyPlugin), -// uint8(IModularSessionKeyPlugin.FunctionId.VALIDATION_TEMPORARY_OWNER) -// ); -// bytes32 tokenSessionKeyManifestHash = keccak256(abi.encode(tokenSessionKeyPlugin.pluginManifest())); - -// account.installPlugin({ -// plugin: address(tokenSessionKeyPlugin), -// manifestHash: tokenSessionKeyManifestHash, -// pluginInstallData: "", -// dependencies: tokenSessionDependency -// }); -// vm.stopPrank(); - -// vm.startPrank(address(account)); -// mockERC20.approve(address(account), 1 ether); - -// (uint48 _after, uint48 _until) = modularSessionKeyPlugin.getSessionDuration( -// address(account), tempOwner, TRANSFERFROM_SESSIONKEY_SELECTOR -// ); - -// assertEq(_after, 0); -// assertEq(_until, 2); -// vm.stopPrank(); -// } - -// function test_sessionKey_batch() public { -// address tempOwner2 = makeAddr("tempOwner2"); -// address tempOwner3 = makeAddr("tempOwner3"); - -// address[] memory tempOwners = new address[](2); -// tempOwners[0] = tempOwner2; -// tempOwners[1] = tempOwner3; - -// bytes4[] memory allowedSelectors = new bytes4[](2); -// allowedSelectors[0] = TRANSFERFROM_SESSIONKEY_SELECTOR; -// allowedSelectors[1] = TRANSFERFROM_SESSIONKEY_SELECTOR; - -// uint48[] memory afters = new uint48[](2); -// afters[0] = 0; -// afters[1] = 0; - -// uint48[] memory untils = new uint48[](2); -// untils[0] = 2; -// untils[1] = 2; - -// vm.expectEmit(true, true, true, true); -// emit SessionKeysAdded(address(account), tempOwners, allowedSelectors, afters, untils); -// vm.prank(address(account)); -// modularSessionKeyPlugin.addSessionKeyBatch(tempOwners, allowedSelectors, afters, untils); - -// vm.prank(tempOwner3); -// TokenSessionKeyPlugin(address(account)).transferFromSessionKey( -// address(mockERC20), address(account), target, 1 ether -// ); - -// assertEq(mockERC20.balanceOf(address(account)), 0); -// assertEq(mockERC20.balanceOf(target), 1 ether); - -// vm.expectEmit(true, true, true, true); -// emit SessionKeysRemoved(address(account), tempOwners, allowedSelectors); -// vm.prank(address(account)); -// modularSessionKeyPlugin.removeSessionKeyBatch(tempOwners, allowedSelectors); -// } - -// function test_sessionKey_userOp() public { -// PackedUserOperation[] memory userOps = new PackedUserOperation[](1); - -// (, PackedUserOperation memory userOp) = _constructUserOp(address(mockERC20), address(account), target, 1 -// ether); -// userOps[0] = userOp; - -// entryPoint.handleOps(userOps, beneficiary); - -// assertEq(mockERC20.balanceOf(address(account)), 0); -// assertEq(mockERC20.balanceOf(target), 1 ether); -// } - -// function test_sessionKey_runtime() public { -// vm.prank(address(tempOwner)); -// TokenSessionKeyPlugin(address(account)).transferFromSessionKey( -// address(mockERC20), address(account), target, 1 ether -// ); - -// assertEq(mockERC20.balanceOf(address(account)), 0); -// assertEq(mockERC20.balanceOf(target), 1 ether); -// } - -// function test_sessionKey_removeTempOwner() public { -// vm.startPrank(address(account)); - -// vm.expectEmit(true, true, true, true); -// emit SessionKeyRemoved(address(account), tempOwner, TRANSFERFROM_SESSIONKEY_SELECTOR); -// modularSessionKeyPlugin.removeSessionKey(tempOwner, TRANSFERFROM_SESSIONKEY_SELECTOR); - -// vm.stopPrank(); - -// (uint48 _after, uint48 _until) = modularSessionKeyPlugin.getSessionDuration( -// address(account), tempOwner, TRANSFERFROM_SESSIONKEY_SELECTOR -// ); -// assertEq(_after, 0); -// assertEq(_until, 0); - -// // Check if tempOwner can still send user operations -// vm.startPrank(address(tempOwner)); - -// bytes memory revertReason = abi.encodeWithSelector(IModularSessionKeyPlugin.NotAuthorized.selector); -// vm.expectRevert( -// abi.encodeWithSelector( -// UpgradeableModularAccount.RuntimeValidationFunctionReverted.selector, -// address(modularSessionKeyPlugin), -// IModularSessionKeyPlugin.FunctionId.VALIDATION_TEMPORARY_OWNER, -// revertReason -// ) -// ); -// TokenSessionKeyPlugin(address(account)).transferFromSessionKey( -// address(mockERC20), address(account), target, 1 ether -// ); -// } - -// function test_sessionKey_invalidContractFails() public { -// address wrongERC20Contract = makeAddr("wrongERC20Contract"); -// (bytes32 userOpHash, PackedUserOperation memory userOp) = -// _constructUserOp(address(wrongERC20Contract), address(account), target, 1 ether); - -// PackedUserOperation[] memory userOps = new PackedUserOperation[](1); -// userOps[0] = userOp; - -// bytes memory revertCallData = abi.encodeWithSelector( -// tokenSessionKeyPlugin.TRANSFERFROM_SELECTOR(), address(account), target, 1 ether -// ); -// bytes memory revertReason = abi.encodeWithSelector( -// UpgradeableModularAccount.ExecFromPluginExternalNotPermitted.selector, -// address(tokenSessionKeyPlugin), -// address(wrongERC20Contract), -// 0, -// revertCallData -// ); -// vm.expectEmit(true, true, true, true); -// emit UserOperationRevertReason(userOpHash, address(account), 0, revertReason); - -// entryPoint.handleOps(userOps, beneficiary); -// } - -// function test_sessionKey_unregisteredTempOwnerFails() public { -// vm.prank(address(maliciousOwner)); -// bytes memory revertReason = abi.encodeWithSelector(IModularSessionKeyPlugin.NotAuthorized.selector); - -// vm.expectRevert( -// abi.encodeWithSelector( -// UpgradeableModularAccount.RuntimeValidationFunctionReverted.selector, -// address(modularSessionKeyPlugin), -// IModularSessionKeyPlugin.FunctionId.VALIDATION_TEMPORARY_OWNER, -// revertReason -// ) -// ); -// TokenSessionKeyPlugin(address(account)).transferFromSessionKey( -// address(mockERC20), address(account), target, 1 ether -// ); -// } - -// function test_sessionKey_invalidSessionDurationFails() public { -// // Move block.timestamp to 12345 -// vm.warp(12345); - -// vm.startPrank(address(tempOwner)); - -// bytes memory revertReason = -// abi.encodeWithSelector(IModularSessionKeyPlugin.WrongTimeRangeForSession.selector); - -// vm.expectRevert( -// abi.encodeWithSelector( -// UpgradeableModularAccount.RuntimeValidationFunctionReverted.selector, -// address(modularSessionKeyPlugin), -// IModularSessionKeyPlugin.FunctionId.VALIDATION_TEMPORARY_OWNER, -// revertReason -// ) -// ); -// TokenSessionKeyPlugin(address(account)).transferFromSessionKey( -// address(mockERC20), address(account), target, 1 ether -// ); -// } - -// function test_sessionKey_uninstallModularSessionKeyPlugin() public { -// address[] memory tempOwners = new address[](1); -// tempOwners[0] = address(tempOwner); - -// bytes4[] memory allowedSelectors = new bytes4[](1); -// allowedSelectors[0] = TRANSFERFROM_SESSIONKEY_SELECTOR; - -// vm.startPrank(owner); - -// vm.expectEmit(true, true, true, true); - -// emit PluginUninstalled(address(tokenSessionKeyPlugin), true); -// account.uninstallPlugin({ -// plugin: address(tokenSessionKeyPlugin), -// config: bytes(""), -// pluginUninstallData: "" -// }); - -// vm.expectEmit(true, true, true, true); -// emit PluginUninstalled(address(modularSessionKeyPlugin), true); -// account.uninstallPlugin({ -// plugin: address(modularSessionKeyPlugin), -// config: bytes(""), -// pluginUninstallData: "" -// }); - -// vm.stopPrank(); -// } - -// // Internal Function -// function _constructUserOp(address targetContract, address from, address to, uint256 amount) -// internal -// view -// returns (bytes32, PackedUserOperation memory) -// { -// bytes memory userOpCallData = -// abi.encodeCall(TokenSessionKeyPlugin.transferFromSessionKey, (targetContract, from, to, amount)); - -// PackedUserOperation memory userOp = PackedUserOperation({ -// sender: address(account), -// nonce: 0, -// initCode: "", -// callData: userOpCallData, -// callGasLimit: CALL_GAS_LIMIT, -// verificationGasLimit: VERIFICATION_GAS_LIMIT, -// preVerificationGas: 0, -// maxFeePerGas: 2, -// maxPriorityFeePerGas: 1, -// paymasterAndData: "", -// signature: "" -// }); - -// // Generate signature -// bytes32 userOpHash = entryPoint.getUserOpHash(userOp); -// (uint8 v, bytes32 r, bytes32 s) = vm.sign(tempOwnerKey, userOpHash.toEthSignedMessageHash()); -// userOp.signature = abi.encodePacked(r, s, v); - -// return (userOpHash, userOp); -// } -// } From ed804c352850a401f6ff85219cffc6d80776a718 Mon Sep 17 00:00:00 2001 From: Adam Egyed <5456061+adamegyed@users.noreply.github.com> Date: Fri, 31 May 2024 08:30:07 -0700 Subject: [PATCH 012/111] feat: [v0.8-develop, experimental] split up interfaces by type [1/N] (#58) --- src/account/UpgradeableModularAccount.sol | 15 ++- src/interfaces/IExecutionHook.sol | 25 ++++ src/interfaces/IPlugin.sol | 64 +-------- src/interfaces/IValidation.sol | 27 ++++ src/interfaces/IValidationHook.sol | 29 ++++ src/plugins/BasePlugin.sol | 125 +----------------- src/plugins/TokenReceiverPlugin.sol | 9 +- src/plugins/owner/ISingleOwnerPlugin.sol | 4 +- src/plugins/owner/SingleOwnerPlugin.sol | 28 ++-- test/account/AccountExecHooks.t.sol | 9 +- test/mocks/MockPlugin.sol | 7 +- test/mocks/plugins/BaseTestPlugin.sol | 12 -- test/mocks/plugins/ComprehensivePlugin.sol | 15 ++- .../ExecFromPluginPermissionsMocks.sol | 13 +- test/mocks/plugins/ManifestValidityMocks.sol | 17 ++- test/mocks/plugins/ReturnDataPluginMocks.sol | 13 +- test/mocks/plugins/ValidationPluginMocks.sol | 20 ++- test/plugin/SingleOwnerPlugin.t.sol | 12 +- 18 files changed, 184 insertions(+), 260 deletions(-) create mode 100644 src/interfaces/IExecutionHook.sol create mode 100644 src/interfaces/IValidation.sol create mode 100644 src/interfaces/IValidationHook.sol delete mode 100644 test/mocks/plugins/BaseTestPlugin.sol diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 47192abd..96c33ce4 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -11,6 +11,9 @@ import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet import {FunctionReferenceLib} from "../helpers/FunctionReferenceLib.sol"; import {_coalescePreValidation, _coalesceValidation} from "../helpers/ValidationDataHelpers.sol"; import {IPlugin, PluginManifest} from "../interfaces/IPlugin.sol"; +import {IValidation} from "../interfaces/IValidation.sol"; +import {IValidationHook} from "../interfaces/IValidationHook.sol"; +import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; import {IPluginExecutor} from "../interfaces/IPluginExecutor.sol"; import {FunctionReference, IPluginManager} from "../interfaces/IPluginManager.sol"; import {IStandardExecutor, Call} from "../interfaces/IStandardExecutor.sol"; @@ -364,7 +367,7 @@ contract UpgradeableModularAccount is FunctionReference preUserOpValidationHook = toFunctionReference(key); (address plugin, uint8 functionId) = preUserOpValidationHook.unpack(); - currentValidationData = IPlugin(plugin).preUserOpValidationHook(functionId, userOp, userOpHash); + currentValidationData = IValidationHook(plugin).preUserOpValidationHook(functionId, userOp, userOpHash); if (uint160(currentValidationData) > 1) { // If the aggregator is not 0 or 1, it is an unexpected value @@ -376,7 +379,7 @@ contract UpgradeableModularAccount is // Run the user op validationFunction { (address plugin, uint8 functionId) = userOpValidationFunction.unpack(); - currentValidationData = IPlugin(plugin).userOpValidationFunction(functionId, userOp, userOpHash); + currentValidationData = IValidation(plugin).validateUserOp(functionId, userOp, userOpHash); if (preUserOpValidationHooksLength != 0) { // If we have other validation data we need to coalesce with @@ -408,7 +411,7 @@ contract UpgradeableModularAccount is (address plugin, uint8 functionId) = preRuntimeValidationHook.unpack(); // solhint-disable-next-line no-empty-blocks - try IPlugin(plugin).preRuntimeValidationHook(functionId, msg.sender, msg.value, msg.data) {} + try IValidationHook(plugin).preRuntimeValidationHook(functionId, msg.sender, msg.value, msg.data) {} catch (bytes memory revertReason) { revert PreRuntimeValidationHookFailed(plugin, functionId, revertReason); } @@ -419,7 +422,7 @@ contract UpgradeableModularAccount is if (!runtimeValidationFunction.isEmptyOrMagicValue()) { (address plugin, uint8 functionId) = runtimeValidationFunction.unpack(); // solhint-disable-next-line no-empty-blocks - try IPlugin(plugin).runtimeValidationFunction(functionId, msg.sender, msg.value, msg.data) {} + try IValidation(plugin).validateRuntime(functionId, msg.sender, msg.value, msg.data) {} catch (bytes memory revertReason) { revert RuntimeValidationFunctionReverted(plugin, functionId, revertReason); } @@ -475,7 +478,7 @@ contract UpgradeableModularAccount is returns (bytes memory preExecHookReturnData) { (address plugin, uint8 functionId) = preExecHook.unpack(); - try IPlugin(plugin).preExecutionHook(functionId, msg.sender, msg.value, data) returns ( + try IExecutionHook(plugin).preExecutionHook(functionId, msg.sender, msg.value, data) returns ( bytes memory returnData ) { preExecHookReturnData = returnData; @@ -500,7 +503,7 @@ contract UpgradeableModularAccount is (address plugin, uint8 functionId) = postHookToRun.postExecHook.unpack(); // solhint-disable-next-line no-empty-blocks - try IPlugin(plugin).postExecutionHook(functionId, postHookToRun.preExecHookReturnData) {} + try IExecutionHook(plugin).postExecutionHook(functionId, postHookToRun.preExecHookReturnData) {} catch (bytes memory revertReason) { revert PostExecHookReverted(plugin, functionId, revertReason); } diff --git a/src/interfaces/IExecutionHook.sol b/src/interfaces/IExecutionHook.sol new file mode 100644 index 00000000..3240c489 --- /dev/null +++ b/src/interfaces/IExecutionHook.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: CC0-1.0 +pragma solidity ^0.8.25; + +import {IPlugin} from "./IPlugin.sol"; + +interface IExecutionHook is IPlugin { + /// @notice Run the pre execution hook specified by the `functionId`. + /// @dev To indicate the entire call should revert, the function MUST revert. + /// @param functionId 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. + /// @return Context to pass to a post execution hook, if present. An empty bytes array MAY be returned. + function preExecutionHook(uint8 functionId, address sender, uint256 value, bytes calldata data) + external + returns (bytes memory); + + /// @notice Run the post execution hook specified by the `functionId`. + /// @dev To indicate the entire call should revert, the function MUST revert. + /// @param functionId 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(uint8 functionId, bytes calldata preExecHookData) external; +} diff --git a/src/interfaces/IPlugin.sol b/src/interfaces/IPlugin.sol index d3163730..cf28c5d2 100644 --- a/src/interfaces/IPlugin.sol +++ b/src/interfaces/IPlugin.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: CC0-1.0 pragma solidity ^0.8.25; -import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; +import {IERC165} from "@openzeppelin/contracts/interfaces/IERC165.sol"; // Forge formatter will displace the first comment for the enum field out of the enum itself, // so annotating here to prevent that. @@ -96,7 +96,7 @@ struct PluginManifest { ManifestExecutionHook[] executionHooks; } -interface IPlugin { +interface IPlugin is IERC165 { /// @notice Initialize plugin data for the modular account. /// @dev Called by the modular account during `installPlugin`. /// @param data Optional bytes array to be decoded and used by the plugin to setup initial plugin data for the @@ -109,66 +109,6 @@ interface IPlugin { /// account. function onUninstall(bytes calldata data) external; - /// @notice Run the pre user operation validation hook specified by the `functionId`. - /// @dev Pre user operation validation hooks MUST NOT return an authorizer value other than 0 or 1. - /// @param functionId 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(uint8 functionId, PackedUserOperation calldata userOp, bytes32 userOpHash) - external - returns (uint256); - - /// @notice Run the user operation validationFunction specified by the `functionId`. - /// @param functionId 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 userOpValidationFunction(uint8 functionId, PackedUserOperation calldata userOp, bytes32 userOpHash) - external - returns (uint256); - - /// @notice Run the pre runtime validation hook specified by the `functionId`. - /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param functionId 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(uint8 functionId, address sender, uint256 value, bytes calldata data) - external; - - /// @notice Run the runtime validationFunction specified by the `functionId`. - /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param functionId 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 runtimeValidationFunction(uint8 functionId, address sender, uint256 value, bytes calldata data) - external; - - /// @notice Run the pre execution hook specified by the `functionId`. - /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param functionId 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. - /// @return Context to pass to a post execution hook, if present. An empty bytes array MAY be returned. - function preExecutionHook(uint8 functionId, address sender, uint256 value, bytes calldata data) - external - returns (bytes memory); - - /// @notice Run the post execution hook specified by the `functionId`. - /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param functionId 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(uint8 functionId, bytes calldata preExecHookData) external; - /// @notice Describe the contents and intended configuration of the plugin. /// @dev This manifest MUST stay constant over time. /// @return A manifest describing the contents and intended configuration of the plugin. diff --git a/src/interfaces/IValidation.sol b/src/interfaces/IValidation.sol new file mode 100644 index 00000000..ac9c68e7 --- /dev/null +++ b/src/interfaces/IValidation.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: CC0-1.0 +pragma solidity ^0.8.25; + +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; + +import {IPlugin} from "./IPlugin.sol"; + +interface IValidation is IPlugin { + /// @notice Run the user operation validationFunction specified by the `functionId`. + /// @param functionId 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(uint8 functionId, PackedUserOperation calldata userOp, bytes32 userOpHash) + external + returns (uint256); + + /// @notice Run the runtime validationFunction specified by the `functionId`. + /// @dev To indicate the entire call should revert, the function MUST revert. + /// @param functionId 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 validateRuntime(uint8 functionId, address sender, uint256 value, bytes calldata data) external; +} diff --git a/src/interfaces/IValidationHook.sol b/src/interfaces/IValidationHook.sol new file mode 100644 index 00000000..4cbee5e7 --- /dev/null +++ b/src/interfaces/IValidationHook.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: CC0-1.0 +pragma solidity ^0.8.25; + +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; + +import {IPlugin} from "./IPlugin.sol"; + +interface IValidationHook is IPlugin { + /// @notice Run the pre user operation validation hook specified by the `functionId`. + /// @dev Pre user operation validation hooks MUST NOT return an authorizer value other than 0 or 1. + /// @param functionId 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(uint8 functionId, PackedUserOperation calldata userOp, bytes32 userOpHash) + external + returns (uint256); + + /// @notice Run the pre runtime validation hook specified by the `functionId`. + /// @dev To indicate the entire call should revert, the function MUST revert. + /// @param functionId 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(uint8 functionId, address sender, uint256 value, bytes calldata data) + external; +} diff --git a/src/plugins/BasePlugin.sol b/src/plugins/BasePlugin.sol index 44145286..3b0fd521 100644 --- a/src/plugins/BasePlugin.sol +++ b/src/plugins/BasePlugin.sol @@ -1,10 +1,9 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.25; -import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; -import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +import {ERC165, IERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; -import {IPlugin, PluginManifest, PluginMetadata} from "../interfaces/IPlugin.sol"; +import {IPlugin} from "../interfaces/IPlugin.sol"; /// @title Base contract for plugins /// @dev Implements ERC-165 to support IPlugin's interface, which is a requirement @@ -13,124 +12,6 @@ import {IPlugin, PluginManifest, PluginMetadata} from "../interfaces/IPlugin.sol abstract contract BasePlugin is ERC165, IPlugin { error NotImplemented(); - /// @notice Initialize plugin data for the modular account. - /// @dev Called by the modular account during `installPlugin`. - /// @param data Optional bytes array to be decoded and used by the plugin to setup initial plugin data for the - /// modular account. - function onInstall(bytes calldata data) external virtual { - (data); - revert NotImplemented(); - } - - /// @notice Clear plugin data for the modular account. - /// @dev Called by the modular account during `uninstallPlugin`. - /// @param data Optional bytes array to be decoded and used by the plugin to clear plugin data for the modular - /// account. - function onUninstall(bytes calldata data) external virtual { - (data); - revert NotImplemented(); - } - - /// @notice Run the pre user operation validation hook specified by the `functionId`. - /// @dev Pre user operation validation hooks MUST NOT return an authorizer value other than 0 or 1. - /// @param functionId 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(uint8 functionId, PackedUserOperation calldata userOp, bytes32 userOpHash) - external - virtual - returns (uint256) - { - (functionId, userOp, userOpHash); - revert NotImplemented(); - } - - /// @notice Run the user operation validationFunction specified by the `functionId`. - /// @param functionId 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 userOpValidationFunction(uint8 functionId, PackedUserOperation calldata userOp, bytes32 userOpHash) - external - virtual - returns (uint256) - { - (functionId, userOp, userOpHash); - revert NotImplemented(); - } - - /// @notice Run the pre runtime validation hook specified by the `functionId`. - /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param functionId 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(uint8 functionId, address sender, uint256 value, bytes calldata data) - external - virtual - { - (functionId, sender, value, data); - revert NotImplemented(); - } - - /// @notice Run the runtime validationFunction specified by the `functionId`. - /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param functionId 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 runtimeValidationFunction(uint8 functionId, address sender, uint256 value, bytes calldata data) - external - virtual - { - (functionId, sender, value, data); - revert NotImplemented(); - } - - /// @notice Run the pre execution hook specified by the `functionId`. - /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param functionId 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. - /// @return Context to pass to a post execution hook, if present. An empty bytes array MAY be returned. - function preExecutionHook(uint8 functionId, address sender, uint256 value, bytes calldata data) - external - virtual - returns (bytes memory) - { - (functionId, sender, value, data); - revert NotImplemented(); - } - - /// @notice Run the post execution hook specified by the `functionId`. - /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param functionId 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(uint8 functionId, bytes calldata preExecHookData) external virtual { - (functionId, preExecHookData); - revert NotImplemented(); - } - - /// @notice Describe the contents and intended configuration of the plugin. - /// @dev This manifest MUST stay constant over time. - /// @return A manifest describing the contents and intended configuration of the plugin. - function pluginManifest() external pure virtual returns (PluginManifest memory) { - revert NotImplemented(); - } - - /// @notice Describe the metadata of the plugin. - /// @dev This metadata MUST stay constant over time. - /// @return A metadata struct describing the plugin. - function pluginMetadata() external pure virtual returns (PluginMetadata memory); - /// @dev Returns true if this contract implements the interface defined by /// `interfaceId`. See the corresponding /// https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] @@ -143,7 +24,7 @@ abstract contract BasePlugin is ERC165, IPlugin { /// making calls to plugins. /// @param interfaceId The interface ID to check for support. /// @return True if the contract supports `interfaceId`. - function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { return interfaceId == type(IPlugin).interfaceId || super.supportsInterface(interfaceId); } } diff --git a/src/plugins/TokenReceiverPlugin.sol b/src/plugins/TokenReceiverPlugin.sol index 980d7397..1a9e8feb 100644 --- a/src/plugins/TokenReceiverPlugin.sol +++ b/src/plugins/TokenReceiverPlugin.sol @@ -5,6 +5,7 @@ import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Recei import {IERC1155Receiver} from "@openzeppelin/contracts/interfaces/IERC1155Receiver.sol"; import { + IPlugin, ManifestFunction, ManifestAssociatedFunctionType, ManifestAssociatedFunction, @@ -52,15 +53,15 @@ contract TokenReceiverPlugin is BasePlugin, IERC721Receiver, IERC1155Receiver { // ┃ Plugin interface functions ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - /// @inheritdoc BasePlugin + /// @inheritdoc IPlugin // solhint-disable-next-line no-empty-blocks function onInstall(bytes calldata) external pure override {} - /// @inheritdoc BasePlugin + /// @inheritdoc IPlugin // solhint-disable-next-line no-empty-blocks function onUninstall(bytes calldata) external pure override {} - /// @inheritdoc BasePlugin + /// @inheritdoc IPlugin function pluginManifest() external pure override returns (PluginManifest memory) { PluginManifest memory manifest; @@ -96,7 +97,7 @@ contract TokenReceiverPlugin is BasePlugin, IERC721Receiver, IERC1155Receiver { return manifest; } - /// @inheritdoc BasePlugin + /// @inheritdoc IPlugin function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { PluginMetadata memory metadata; metadata.name = NAME; diff --git a/src/plugins/owner/ISingleOwnerPlugin.sol b/src/plugins/owner/ISingleOwnerPlugin.sol index d20296ef..0c3d8255 100644 --- a/src/plugins/owner/ISingleOwnerPlugin.sol +++ b/src/plugins/owner/ISingleOwnerPlugin.sol @@ -1,7 +1,9 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.25; -interface ISingleOwnerPlugin { +import {IValidation} from "../../interfaces/IValidation.sol"; + +interface ISingleOwnerPlugin is IValidation { enum FunctionId { VALIDATION_OWNER_OR_SELF } diff --git a/src/plugins/owner/SingleOwnerPlugin.sol b/src/plugins/owner/SingleOwnerPlugin.sol index 38f16872..dc5bae13 100644 --- a/src/plugins/owner/SingleOwnerPlugin.sol +++ b/src/plugins/owner/SingleOwnerPlugin.sol @@ -18,7 +18,9 @@ import { SelectorPermission } from "../../interfaces/IPlugin.sol"; import {IStandardExecutor} from "../../interfaces/IStandardExecutor.sol"; -import {BasePlugin} from "../BasePlugin.sol"; +import {IPlugin} from "../../interfaces/IPlugin.sol"; +import {IValidation} from "../../interfaces/IValidation.sol"; +import {BasePlugin, IERC165} from "../BasePlugin.sol"; import {ISingleOwnerPlugin} from "./ISingleOwnerPlugin.sol"; /// @title Single Owner Plugin @@ -37,7 +39,7 @@ import {ISingleOwnerPlugin} from "./ISingleOwnerPlugin.sol"; /// the account, violating storage access rules. This also means that the /// owner of a modular account may not be another modular account if you want to /// send user operations through a bundler. -contract SingleOwnerPlugin is BasePlugin, ISingleOwnerPlugin, IERC1271 { +contract SingleOwnerPlugin is ISingleOwnerPlugin, BasePlugin, IERC1271 { using ECDSA for bytes32; using MessageHashUtils for bytes32; @@ -66,22 +68,18 @@ contract SingleOwnerPlugin is BasePlugin, ISingleOwnerPlugin, IERC1271 { // ┃ Plugin interface functions ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - /// @inheritdoc BasePlugin + /// @inheritdoc IPlugin function onInstall(bytes calldata data) external override { _transferOwnership(abi.decode(data, (address))); } - /// @inheritdoc BasePlugin + /// @inheritdoc IPlugin function onUninstall(bytes calldata) external override { _transferOwnership(address(0)); } - /// @inheritdoc BasePlugin - function runtimeValidationFunction(uint8 functionId, address sender, uint256, bytes calldata) - external - view - override - { + /// @inheritdoc IValidation + function validateRuntime(uint8 functionId, address sender, uint256, bytes calldata) external view override { if (functionId == uint8(FunctionId.VALIDATION_OWNER_OR_SELF)) { // Validate that the sender is the owner of the account or self. if (sender != _owners[msg.sender] && sender != msg.sender) { @@ -92,8 +90,8 @@ contract SingleOwnerPlugin is BasePlugin, ISingleOwnerPlugin, IERC1271 { revert NotImplemented(); } - /// @inheritdoc BasePlugin - function userOpValidationFunction(uint8 functionId, PackedUserOperation calldata userOp, bytes32 userOpHash) + /// @inheritdoc IValidation + function validateUserOp(uint8 functionId, PackedUserOperation calldata userOp, bytes32 userOpHash) external view override @@ -142,7 +140,7 @@ contract SingleOwnerPlugin is BasePlugin, ISingleOwnerPlugin, IERC1271 { return _owners[account]; } - /// @inheritdoc BasePlugin + /// @inheritdoc IPlugin function pluginManifest() external pure override returns (PluginManifest memory) { PluginManifest memory manifest; @@ -199,7 +197,7 @@ contract SingleOwnerPlugin is BasePlugin, ISingleOwnerPlugin, IERC1271 { return manifest; } - /// @inheritdoc BasePlugin + /// @inheritdoc IPlugin function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { PluginMetadata memory metadata; metadata.name = NAME; @@ -224,7 +222,7 @@ contract SingleOwnerPlugin is BasePlugin, ISingleOwnerPlugin, IERC1271 { // ┗━━━━━━━━━━━━━━━┛ /// @inheritdoc BasePlugin - function supportsInterface(bytes4 interfaceId) public view override returns (bool) { + function supportsInterface(bytes4 interfaceId) public view override(BasePlugin, IERC165) returns (bool) { return interfaceId == type(ISingleOwnerPlugin).interfaceId || super.supportsInterface(interfaceId); } diff --git a/test/account/AccountExecHooks.t.sol b/test/account/AccountExecHooks.t.sol index 9b7cde45..a1db86c6 100644 --- a/test/account/AccountExecHooks.t.sol +++ b/test/account/AccountExecHooks.t.sol @@ -9,6 +9,7 @@ import { ManifestFunction, PluginManifest } from "../../src/interfaces/IPlugin.sol"; +import {IExecutionHook} from "../../src/interfaces/IExecutionHook.sol"; import {FunctionReference} from "../../src/helpers/FunctionReferenceLib.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; @@ -70,7 +71,7 @@ contract AccountExecHooksTest is AccountTestBase { vm.expectEmit(true, true, true, true); emit ReceivedCall( abi.encodeWithSelector( - IPlugin.preExecutionHook.selector, + IExecutionHook.preExecutionHook.selector, _PRE_HOOK_FUNCTION_ID_1, address(this), // caller 0, // msg.value in call to account @@ -109,7 +110,7 @@ contract AccountExecHooksTest is AccountTestBase { // pre hook call emit ReceivedCall( abi.encodeWithSelector( - IPlugin.preExecutionHook.selector, + IExecutionHook.preExecutionHook.selector, _BOTH_HOOKS_FUNCTION_ID_3, address(this), // caller 0, // msg.value in call to account @@ -123,7 +124,7 @@ contract AccountExecHooksTest is AccountTestBase { vm.expectEmit(true, true, true, true); // post hook call emit ReceivedCall( - abi.encodeCall(IPlugin.postExecutionHook, (_BOTH_HOOKS_FUNCTION_ID_3, "")), + abi.encodeCall(IExecutionHook.postExecutionHook, (_BOTH_HOOKS_FUNCTION_ID_3, "")), 0 // msg value in call to plugin ); @@ -155,7 +156,7 @@ contract AccountExecHooksTest is AccountTestBase { vm.expectEmit(true, true, true, true); emit ReceivedCall( - abi.encodeCall(IPlugin.postExecutionHook, (_POST_HOOK_FUNCTION_ID_2, "")), + abi.encodeCall(IExecutionHook.postExecutionHook, (_POST_HOOK_FUNCTION_ID_2, "")), 0 // msg value in call to plugin ); diff --git a/test/mocks/MockPlugin.sol b/test/mocks/MockPlugin.sol index 67891502..3a2bf984 100644 --- a/test/mocks/MockPlugin.sol +++ b/test/mocks/MockPlugin.sol @@ -4,6 +4,8 @@ pragma solidity ^0.8.19; import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; import {PluginManifest, IPlugin, PluginMetadata} from "../../src/interfaces/IPlugin.sol"; +import {IValidation} from "../../src/interfaces/IValidation.sol"; +import {IExecutionHook} from "../../src/interfaces/IExecutionHook.sol"; contract MockPlugin is ERC165 { // It's super inefficient to hold the entire abi-encoded manifest in storage, but this is fine since it's @@ -79,9 +81,8 @@ contract MockPlugin is ERC165 { fallback() external payable { emit ReceivedCall(msg.data, msg.value); if ( - msg.sig == IPlugin.userOpValidationFunction.selector - || msg.sig == IPlugin.runtimeValidationFunction.selector - || msg.sig == IPlugin.preExecutionHook.selector + msg.sig == IValidation.validateUserOp.selector || msg.sig == IValidation.validateRuntime.selector + || msg.sig == IExecutionHook.preExecutionHook.selector ) { // return 0 for userOp/runtimeVal case, return bytes("") for preExecutionHook case assembly ("memory-safe") { diff --git a/test/mocks/plugins/BaseTestPlugin.sol b/test/mocks/plugins/BaseTestPlugin.sol deleted file mode 100644 index 508845c1..00000000 --- a/test/mocks/plugins/BaseTestPlugin.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; - -import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; -import {PluginMetadata} from "../../../src/interfaces/IPlugin.sol"; - -contract BaseTestPlugin is BasePlugin { - // Don't need to implement this in each context - function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { - revert NotImplemented(); - } -} diff --git a/test/mocks/plugins/ComprehensivePlugin.sol b/test/mocks/plugins/ComprehensivePlugin.sol index 87f85210..edc8379e 100644 --- a/test/mocks/plugins/ComprehensivePlugin.sol +++ b/test/mocks/plugins/ComprehensivePlugin.sol @@ -11,10 +11,15 @@ import { PluginManifest, PluginMetadata } from "../../../src/interfaces/IPlugin.sol"; +import {PluginManifest} from "../../../src/interfaces/IPlugin.sol"; +import {IValidation} from "../../../src/interfaces/IValidation.sol"; +import {IValidationHook} from "../../../src/interfaces/IValidationHook.sol"; +import {IExecutionHook} from "../../../src/interfaces/IExecutionHook.sol"; + import {IStandardExecutor} from "../../../src/interfaces/IStandardExecutor.sol"; import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; -contract ComprehensivePlugin is BasePlugin { +contract ComprehensivePlugin is IValidation, IValidationHook, IExecutionHook, BasePlugin { enum FunctionId { PRE_VALIDATION_HOOK_1, PRE_VALIDATION_HOOK_2, @@ -56,7 +61,7 @@ contract ComprehensivePlugin is BasePlugin { revert NotImplemented(); } - function userOpValidationFunction(uint8 functionId, PackedUserOperation calldata, bytes32) + function validateUserOp(uint8 functionId, PackedUserOperation calldata, bytes32) external pure override @@ -77,11 +82,7 @@ contract ComprehensivePlugin is BasePlugin { revert NotImplemented(); } - function runtimeValidationFunction(uint8 functionId, address, uint256, bytes calldata) - external - pure - override - { + function validateRuntime(uint8 functionId, address, uint256, bytes calldata) external pure override { if (functionId == uint8(FunctionId.VALIDATION)) { return; } diff --git a/test/mocks/plugins/ExecFromPluginPermissionsMocks.sol b/test/mocks/plugins/ExecFromPluginPermissionsMocks.sol index 72c812bb..ae775dd0 100644 --- a/test/mocks/plugins/ExecFromPluginPermissionsMocks.sol +++ b/test/mocks/plugins/ExecFromPluginPermissionsMocks.sol @@ -6,15 +6,16 @@ import { ManifestAssociatedFunctionType, ManifestAssociatedFunction, ManifestExternalCallPermission, - PluginManifest + PluginManifest, + PluginMetadata } from "../../../src/interfaces/IPlugin.sol"; import {IPluginExecutor} from "../../../src/interfaces/IPluginExecutor.sol"; -import {BaseTestPlugin} from "./BaseTestPlugin.sol"; +import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; import {ResultCreatorPlugin} from "./ReturnDataPluginMocks.sol"; import {Counter} from "../Counter.sol"; -contract EFPCallerPlugin is BaseTestPlugin { +contract EFPCallerPlugin is BasePlugin { // Store the counters as immutables, and use the view -> pure cast to get the manifest // solhint-disable private-vars-leading-underscore, immutable-vars-naming address private immutable counter1; @@ -107,6 +108,8 @@ contract EFPCallerPlugin is BaseTestPlugin { return _castToPure(_getManifest)(); } + function pluginMetadata() external pure override returns (PluginMetadata memory) {} + // The manifest requested access to use the plugin-defined method "foo" function useEFPPermissionAllowed() external returns (bytes memory) { return IPluginExecutor(msg.sender).executeFromPlugin(abi.encodeCall(ResultCreatorPlugin.foo, ())); @@ -187,7 +190,7 @@ contract EFPCallerPlugin is BaseTestPlugin { } } -contract EFPCallerPluginAnyExternal is BaseTestPlugin { +contract EFPCallerPluginAnyExternal is BasePlugin { function onInstall(bytes calldata) external override {} function onUninstall(bytes calldata) external override {} @@ -213,6 +216,8 @@ contract EFPCallerPluginAnyExternal is BaseTestPlugin { return manifest; } + function pluginMetadata() external pure override returns (PluginMetadata memory) {} + function passthroughExecute(address target, uint256 value, bytes calldata data) external payable diff --git a/test/mocks/plugins/ManifestValidityMocks.sol b/test/mocks/plugins/ManifestValidityMocks.sol index ed5370fb..2ea94f3c 100644 --- a/test/mocks/plugins/ManifestValidityMocks.sol +++ b/test/mocks/plugins/ManifestValidityMocks.sol @@ -5,13 +5,14 @@ import { ManifestFunction, ManifestAssociatedFunctionType, ManifestAssociatedFunction, - PluginManifest + PluginManifest, + PluginMetadata } from "../../../src/interfaces/IPlugin.sol"; -import {BaseTestPlugin} from "./BaseTestPlugin.sol"; +import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; // solhint-disable-next-line contract-name-camelcase -contract BadValidationMagicValue_PreValidationHook_Plugin is BaseTestPlugin { +contract BadValidationMagicValue_PreValidationHook_Plugin is BasePlugin { function onInstall(bytes calldata) external override {} function onUninstall(bytes calldata) external override {} @@ -49,10 +50,12 @@ contract BadValidationMagicValue_PreValidationHook_Plugin is BaseTestPlugin { return manifest; } + + function pluginMetadata() external pure override returns (PluginMetadata memory) {} } // solhint-disable-next-line contract-name-camelcase -contract BadHookMagicValue_UserOpValidationFunction_Plugin is BaseTestPlugin { +contract BadHookMagicValue_UserOpValidationFunction_Plugin is BasePlugin { function onInstall(bytes calldata) external override {} function onUninstall(bytes calldata) external override {} @@ -79,10 +82,12 @@ contract BadHookMagicValue_UserOpValidationFunction_Plugin is BaseTestPlugin { return manifest; } + + function pluginMetadata() external pure override returns (PluginMetadata memory) {} } // solhint-disable-next-line contract-name-camelcase -contract BadHookMagicValue_RuntimeValidationFunction_Plugin is BaseTestPlugin { +contract BadHookMagicValue_RuntimeValidationFunction_Plugin is BasePlugin { function onInstall(bytes calldata) external override {} function onUninstall(bytes calldata) external override {} @@ -109,4 +114,6 @@ contract BadHookMagicValue_RuntimeValidationFunction_Plugin is BaseTestPlugin { return manifest; } + + function pluginMetadata() external pure override returns (PluginMetadata memory) {} } diff --git a/test/mocks/plugins/ReturnDataPluginMocks.sol b/test/mocks/plugins/ReturnDataPluginMocks.sol index 153a4340..766bb903 100644 --- a/test/mocks/plugins/ReturnDataPluginMocks.sol +++ b/test/mocks/plugins/ReturnDataPluginMocks.sol @@ -6,11 +6,12 @@ import { ManifestAssociatedFunctionType, ManifestAssociatedFunction, ManifestExternalCallPermission, - PluginManifest + PluginManifest, + PluginMetadata } from "../../../src/interfaces/IPlugin.sol"; import {IPluginExecutor} from "../../../src/interfaces/IPluginExecutor.sol"; -import {BaseTestPlugin} from "./BaseTestPlugin.sol"; +import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; contract RegularResultContract { function foo() external pure returns (bytes32) { @@ -22,7 +23,7 @@ contract RegularResultContract { } } -contract ResultCreatorPlugin is BaseTestPlugin { +contract ResultCreatorPlugin is BasePlugin { function onInstall(bytes calldata) external override {} function onUninstall(bytes calldata) external override {} @@ -54,9 +55,11 @@ contract ResultCreatorPlugin is BaseTestPlugin { return manifest; } + + function pluginMetadata() external pure override returns (PluginMetadata memory) {} } -contract ResultConsumerPlugin is BaseTestPlugin { +contract ResultConsumerPlugin is BasePlugin { ResultCreatorPlugin public immutable RESULT_CREATOR; RegularResultContract public immutable REGULAR_RESULT_CONTRACT; @@ -151,4 +154,6 @@ contract ResultConsumerPlugin is BaseTestPlugin { return manifest; } + + function pluginMetadata() external pure override returns (PluginMetadata memory) {} } diff --git a/test/mocks/plugins/ValidationPluginMocks.sol b/test/mocks/plugins/ValidationPluginMocks.sol index 937e5c65..48ca0ea1 100644 --- a/test/mocks/plugins/ValidationPluginMocks.sol +++ b/test/mocks/plugins/ValidationPluginMocks.sol @@ -7,11 +7,14 @@ import { ManifestFunction, ManifestAssociatedFunctionType, ManifestAssociatedFunction, + PluginMetadata, PluginManifest } from "../../../src/interfaces/IPlugin.sol"; -import {BaseTestPlugin} from "./BaseTestPlugin.sol"; +import {IValidation} from "../../../src/interfaces/IValidation.sol"; +import {IValidationHook} from "../../../src/interfaces/IValidationHook.sol"; +import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; -abstract contract MockBaseUserOpValidationPlugin is BaseTestPlugin { +abstract contract MockBaseUserOpValidationPlugin is IValidation, IValidationHook, BasePlugin { enum FunctionId { USER_OP_VALIDATION, PRE_VALIDATION_HOOK_1, @@ -44,7 +47,7 @@ abstract contract MockBaseUserOpValidationPlugin is BaseTestPlugin { revert NotImplemented(); } - function userOpValidationFunction(uint8 functionId, PackedUserOperation calldata, bytes32) + function validateUserOp(uint8 functionId, PackedUserOperation calldata, bytes32) external view override @@ -55,6 +58,17 @@ abstract contract MockBaseUserOpValidationPlugin is BaseTestPlugin { } revert NotImplemented(); } + + // Empty stubs + function pluginMetadata() external pure override returns (PluginMetadata memory) {} + + function preRuntimeValidationHook(uint8, address, uint256, bytes calldata) external pure override { + revert NotImplemented(); + } + + function validateRuntime(uint8, address, uint256, bytes calldata) external pure override { + revert NotImplemented(); + } } contract MockUserOpValidationPlugin is MockBaseUserOpValidationPlugin { diff --git a/test/plugin/SingleOwnerPlugin.t.sol b/test/plugin/SingleOwnerPlugin.t.sol index dea5b2f9..5c2ecb63 100644 --- a/test/plugin/SingleOwnerPlugin.t.sol +++ b/test/plugin/SingleOwnerPlugin.t.sol @@ -114,15 +114,11 @@ contract SingleOwnerPluginTest is OptimizedTest { assertEq(address(0), plugin.owner()); plugin.transferOwnership(owner1); assertEq(owner1, plugin.owner()); - plugin.runtimeValidationFunction( - uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER_OR_SELF), owner1, 0, "" - ); + plugin.validateRuntime(uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER_OR_SELF), owner1, 0, ""); vm.startPrank(b); vm.expectRevert(ISingleOwnerPlugin.NotAuthorized.selector); - plugin.runtimeValidationFunction( - uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER_OR_SELF), owner1, 0, "" - ); + plugin.validateRuntime(uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER_OR_SELF), owner1, 0, ""); } function testFuzz_validateUserOpSig(string memory salt, PackedUserOperation memory userOp) public { @@ -137,7 +133,7 @@ contract SingleOwnerPluginTest is OptimizedTest { userOp.signature = abi.encodePacked(r, s, v); // sig check should fail - uint256 success = plugin.userOpValidationFunction( + uint256 success = plugin.validateUserOp( uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER_OR_SELF), userOp, userOpHash ); assertEq(success, 1); @@ -147,7 +143,7 @@ contract SingleOwnerPluginTest is OptimizedTest { assertEq(signer, plugin.owner()); // sig check should pass - success = plugin.userOpValidationFunction( + success = plugin.validateUserOp( uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER_OR_SELF), userOp, userOpHash ); assertEq(success, 0); From 765a34f784244c88ea3b81c1ff99b68dbeca1485 Mon Sep 17 00:00:00 2001 From: Adam Egyed <5456061+adamegyed@users.noreply.github.com> Date: Fri, 31 May 2024 11:32:25 -0700 Subject: [PATCH 013/111] feat: [v0.8-develop]: sig validation in interface (#59) --- src/account/AccountStorage.sol | 6 ++++ src/account/PluginManagerInternals.sol | 14 ++++++++ src/account/UpgradeableModularAccount.sol | 27 +++++++++++++++ src/interfaces/IPlugin.sol | 1 + src/interfaces/IValidation.sol | 13 +++++++ src/interfaces/IValidationHook.sol | 15 ++++++++ src/plugins/owner/ISingleOwnerPlugin.sol | 3 +- src/plugins/owner/SingleOwnerPlugin.sol | 36 ++++++++++++-------- test/account/UpgradeableModularAccount.t.sol | 18 ++++++++++ test/mocks/plugins/ComprehensivePlugin.sol | 14 +++++++- test/mocks/plugins/ValidationPluginMocks.sol | 4 +++ test/plugin/SingleOwnerPlugin.t.sol | 27 +++++++++++++-- 12 files changed, 158 insertions(+), 20 deletions(-) diff --git a/src/account/AccountStorage.sol b/src/account/AccountStorage.sol index 5562fdcd..09a4a974 100644 --- a/src/account/AccountStorage.sol +++ b/src/account/AccountStorage.sol @@ -63,8 +63,14 @@ struct AccountStorage { mapping(IPlugin => mapping(address => PermittedExternalCallData)) permittedExternalCalls; // For ERC165 introspection mapping(bytes4 => uint256) supportedIfaces; + // Installed plugins capable of signature validation. + EnumerableSet.Bytes32Set signatureValidations; } +// TODO: Change how pre-validation hooks work to allow association with validation, rather than selector. +// Pre signature validation hooks +// mapping(FunctionReference => EnumerableSet.Bytes32Set) preSignatureValidationHooks; + function getAccountStorage() pure returns (AccountStorage storage _storage) { assembly ("memory-safe") { _storage.slot := _ACCOUNT_STORAGE_SLOT diff --git a/src/account/PluginManagerInternals.sol b/src/account/PluginManagerInternals.sol index 8f228932..a3a108c1 100644 --- a/src/account/PluginManagerInternals.sol +++ b/src/account/PluginManagerInternals.sol @@ -262,6 +262,13 @@ abstract contract PluginManagerInternals is IPluginManager { ); } + length = manifest.signatureValidationFunctions.length; + for (uint256 i = 0; i < length; ++i) { + FunctionReference signatureValidationFunction = + FunctionReferenceLib.pack(plugin, manifest.signatureValidationFunctions[i]); + _storage.signatureValidations.add(toSetValue(signatureValidationFunction)); + } + // Hooks are not allowed to be provided as dependencies, so we use an empty array for resolving them. FunctionReference[] memory emptyDependencies; @@ -359,6 +366,13 @@ abstract contract PluginManagerInternals is IPluginManager { ); } + length = manifest.signatureValidationFunctions.length; + for (uint256 i = 0; i < length; ++i) { + FunctionReference signatureValidationFunction = + FunctionReferenceLib.pack(plugin, manifest.signatureValidationFunctions[i]); + _storage.signatureValidations.remove(toSetValue(signatureValidationFunction)); + } + length = manifest.validationFunctions.length; for (uint256 i = 0; i < length; ++i) { ManifestAssociatedFunction memory mv = manifest.validationFunctions[i]; diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 96c33ce4..b815e42f 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -6,6 +6,7 @@ import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntry import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; +import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {FunctionReferenceLib} from "../helpers/FunctionReferenceLib.sol"; @@ -24,6 +25,7 @@ import { getAccountStorage, getPermittedCallKey, SelectorData, + toSetValue, toFunctionReference, toExecutionHook } from "./AccountStorage.sol"; @@ -36,6 +38,7 @@ contract UpgradeableModularAccount is AccountStorageInitializable, BaseAccount, IERC165, + IERC1271, IPluginExecutor, IStandardExecutor, PluginManagerInternals, @@ -55,6 +58,10 @@ contract UpgradeableModularAccount is bytes4 internal constant _INTERFACE_ID_INVALID = 0xffffffff; bytes4 internal constant _IERC165_INTERFACE_ID = 0x01ffc9a7; + // bytes4(keccak256("isValidSignature(bytes32,bytes)")) + bytes4 internal constant _1271_MAGIC_VALUE = 0x1626ba7e; + bytes4 internal constant _1271_INVALID = 0xffffffff; + event ModularAccountInitialized(IEntryPoint indexed entryPoint); error AlwaysDenyRule(); @@ -67,6 +74,7 @@ contract UpgradeableModularAccount is error PreRuntimeValidationHookFailed(address plugin, uint8 functionId, bytes revertReason); error RuntimeValidationFunctionMissing(bytes4 selector); error RuntimeValidationFunctionReverted(address plugin, uint8 functionId, bytes revertReason); + error SignatureValidationInvalid(address plugin, uint8 functionId); error UnexpectedAggregator(address plugin, uint8 functionId, address aggregator); error UnrecognizedFunction(bytes4 selector); error UserOpValidationFunctionMissing(bytes4 selector); @@ -310,6 +318,25 @@ contract UpgradeableModularAccount is super.upgradeToAndCall(newImplementation, data); } + function isValidSignature(bytes32 hash, bytes calldata signature) public view override returns (bytes4) { + AccountStorage storage _storage = getAccountStorage(); + + FunctionReference sigValidation = FunctionReference.wrap(bytes21(signature)); + + (address plugin, uint8 functionId) = sigValidation.unpack(); + if (!_storage.signatureValidations.contains(toSetValue(sigValidation))) { + revert SignatureValidationInvalid(plugin, functionId); + } + + if ( + IValidation(plugin).validateSignature(functionId, msg.sender, hash, signature[21:]) + == _1271_MAGIC_VALUE + ) { + return _1271_MAGIC_VALUE; + } + return _1271_INVALID; + } + /// @notice Gets the entry point for this account /// @return entryPoint The entry point for this account function entryPoint() public view override returns (IEntryPoint) { diff --git a/src/interfaces/IPlugin.sol b/src/interfaces/IPlugin.sol index cf28c5d2..a229f51b 100644 --- a/src/interfaces/IPlugin.sol +++ b/src/interfaces/IPlugin.sol @@ -94,6 +94,7 @@ struct PluginManifest { ManifestAssociatedFunction[] validationFunctions; ManifestAssociatedFunction[] preValidationHooks; ManifestExecutionHook[] executionHooks; + uint8[] signatureValidationFunctions; } interface IPlugin is IERC165 { diff --git a/src/interfaces/IValidation.sol b/src/interfaces/IValidation.sol index ac9c68e7..b4edddcc 100644 --- a/src/interfaces/IValidation.sol +++ b/src/interfaces/IValidation.sol @@ -24,4 +24,17 @@ interface IValidation is IPlugin { /// @param value The call value. /// @param data The calldata sent. function validateRuntime(uint8 functionId, address sender, uint256 value, bytes calldata data) external; + + /// @notice Validates a signature using ERC-1271. + /// @dev To indicate the entire call should revert, the function MUST revert. + /// @param functionId 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(uint8 functionId, address sender, bytes32 hash, bytes calldata signature) + external + view + returns (bytes4); } diff --git a/src/interfaces/IValidationHook.sol b/src/interfaces/IValidationHook.sol index 4cbee5e7..8eb7a61d 100644 --- a/src/interfaces/IValidationHook.sol +++ b/src/interfaces/IValidationHook.sol @@ -26,4 +26,19 @@ interface IValidationHook is IPlugin { /// @param data The calldata sent. function preRuntimeValidationHook(uint8 functionId, address sender, uint256 value, bytes calldata data) external; + + // TODO: support this hook type within the account & in the manifest + + /// @notice Run the pre signature validation hook specified by the `functionId`. + /// @dev To indicate the call should revert, the function MUST revert. + /// @param functionId 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(uint8 functionId, address sender, bytes32 hash, bytes calldata + // signature) + // external + // view + // returns (bytes4); } diff --git a/src/plugins/owner/ISingleOwnerPlugin.sol b/src/plugins/owner/ISingleOwnerPlugin.sol index 0c3d8255..6d80eb50 100644 --- a/src/plugins/owner/ISingleOwnerPlugin.sol +++ b/src/plugins/owner/ISingleOwnerPlugin.sol @@ -5,7 +5,8 @@ import {IValidation} from "../../interfaces/IValidation.sol"; interface ISingleOwnerPlugin is IValidation { enum FunctionId { - VALIDATION_OWNER_OR_SELF + VALIDATION_OWNER_OR_SELF, + SIG_VALIDATION } /// @notice This event is emitted when ownership of the account changes. diff --git a/src/plugins/owner/SingleOwnerPlugin.sol b/src/plugins/owner/SingleOwnerPlugin.sol index dc5bae13..825b0230 100644 --- a/src/plugins/owner/SingleOwnerPlugin.sol +++ b/src/plugins/owner/SingleOwnerPlugin.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.25; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; @@ -39,7 +38,7 @@ import {ISingleOwnerPlugin} from "./ISingleOwnerPlugin.sol"; /// the account, violating storage access rules. This also means that the /// owner of a modular account may not be another modular account if you want to /// send user operations through a bundler. -contract SingleOwnerPlugin is ISingleOwnerPlugin, BasePlugin, IERC1271 { +contract SingleOwnerPlugin is ISingleOwnerPlugin, BasePlugin { using ECDSA for bytes32; using MessageHashUtils for bytes32; @@ -52,6 +51,7 @@ contract SingleOwnerPlugin is ISingleOwnerPlugin, BasePlugin, IERC1271 { // bytes4(keccak256("isValidSignature(bytes32,bytes)")) bytes4 internal constant _1271_MAGIC_VALUE = 0x1626ba7e; + bytes4 internal constant _1271_INVALID = 0xffffffff; mapping(address => address) internal _owners; @@ -112,18 +112,26 @@ contract SingleOwnerPlugin is ISingleOwnerPlugin, BasePlugin, IERC1271 { // ┃ Execution view functions ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - /// @inheritdoc IERC1271 + /// @inheritdoc IValidation /// @dev The signature is valid if it is signed by the owner's private key /// (if the owner is an EOA) or if it is a valid ERC-1271 signature from the /// owner (if the owner is a contract). Note that unlike the signature /// validation used in `validateUserOp`, this does///*not** wrap the digest in /// an "Ethereum Signed Message" envelope before checking the signature in /// the EOA-owner case. - function isValidSignature(bytes32 digest, bytes memory signature) external view override returns (bytes4) { - if (SignatureChecker.isValidSignatureNow(_owners[msg.sender], digest, signature)) { - return _1271_MAGIC_VALUE; + function validateSignature(uint8 functionId, address, bytes32 digest, bytes calldata signature) + external + view + override + returns (bytes4) + { + if (functionId == uint8(FunctionId.SIG_VALIDATION)) { + if (SignatureChecker.isValidSignatureNow(_owners[msg.sender], digest, signature)) { + return _1271_MAGIC_VALUE; + } + return _1271_INVALID; } - return 0xffffffff; + revert NotImplemented(); } /// @inheritdoc ISingleOwnerPlugin @@ -144,17 +152,16 @@ contract SingleOwnerPlugin is ISingleOwnerPlugin, BasePlugin, IERC1271 { function pluginManifest() external pure override returns (PluginManifest memory) { PluginManifest memory manifest; - manifest.executionFunctions = new bytes4[](3); + manifest.executionFunctions = new bytes4[](2); manifest.executionFunctions[0] = this.transferOwnership.selector; - manifest.executionFunctions[1] = this.isValidSignature.selector; - manifest.executionFunctions[2] = this.owner.selector; + manifest.executionFunctions[1] = this.owner.selector; ManifestFunction memory ownerValidationFunction = ManifestFunction({ functionType: ManifestAssociatedFunctionType.SELF, functionId: uint8(FunctionId.VALIDATION_OWNER_OR_SELF), dependencyIndex: 0 // Unused. }); - manifest.validationFunctions = new ManifestAssociatedFunction[](8); + manifest.validationFunctions = new ManifestAssociatedFunction[](7); manifest.validationFunctions[0] = ManifestAssociatedFunction({ executionSelector: this.transferOwnership.selector, associatedFunction: ownerValidationFunction @@ -186,14 +193,13 @@ contract SingleOwnerPlugin is ISingleOwnerPlugin, BasePlugin, IERC1271 { dependencyIndex: 0 // Unused. }); manifest.validationFunctions[6] = ManifestAssociatedFunction({ - executionSelector: this.isValidSignature.selector, - associatedFunction: alwaysAllowRuntime - }); - manifest.validationFunctions[7] = ManifestAssociatedFunction({ executionSelector: this.owner.selector, associatedFunction: alwaysAllowRuntime }); + manifest.signatureValidationFunctions = new uint8[](1); + manifest.signatureValidationFunctions[0] = uint8(FunctionId.SIG_VALIDATION); + return manifest; } diff --git a/test/account/UpgradeableModularAccount.t.sol b/test/account/UpgradeableModularAccount.t.sol index c1c0580c..363d1654 100644 --- a/test/account/UpgradeableModularAccount.t.sol +++ b/test/account/UpgradeableModularAccount.t.sol @@ -6,6 +6,7 @@ import {console} from "forge-std/Test.sol"; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; +import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; import {PluginManagerInternals} from "../../src/account/PluginManagerInternals.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; @@ -16,6 +17,7 @@ import {IPluginManager} from "../../src/interfaces/IPluginManager.sol"; import {Call} from "../../src/interfaces/IStandardExecutor.sol"; import {SingleOwnerPlugin} from "../../src/plugins/owner/SingleOwnerPlugin.sol"; import {TokenReceiverPlugin} from "../../src/plugins/TokenReceiverPlugin.sol"; +import {ISingleOwnerPlugin} from "../../src/plugins/owner/ISingleOwnerPlugin.sol"; import {Counter} from "../mocks/Counter.sol"; import {ComprehensivePlugin} from "../mocks/plugins/ComprehensivePlugin.sol"; @@ -428,6 +430,22 @@ contract UpgradeableModularAccountTest is AccountTestBase { assertEq(SingleOwnerPlugin(address(account1)).owner(), owner2); } + function test_isValidSignature() public { + bytes32 message = keccak256("hello world"); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, message); + + // singleOwnerPlugin.ownerOf(address(account1)); + + bytes memory signature = abi.encodePacked( + address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.SIG_VALIDATION), r, s, v + ); + + bytes4 validationResult = IERC1271(address(account1)).isValidSignature(message, signature); + + assertEq(validationResult, bytes4(0x1626ba7e)); + } + // Internal Functions function _printStorageReadsAndWrites(address addr) internal { diff --git a/test/mocks/plugins/ComprehensivePlugin.sol b/test/mocks/plugins/ComprehensivePlugin.sol index edc8379e..bdb82499 100644 --- a/test/mocks/plugins/ComprehensivePlugin.sol +++ b/test/mocks/plugins/ComprehensivePlugin.sol @@ -26,7 +26,8 @@ contract ComprehensivePlugin is IValidation, IValidationHook, IExecutionHook, Ba VALIDATION, BOTH_EXECUTION_HOOKS, PRE_EXECUTION_HOOK, - POST_EXECUTION_HOOK + POST_EXECUTION_HOOK, + SIG_VALIDATION } string public constant NAME = "Comprehensive Plugin"; @@ -89,6 +90,17 @@ contract ComprehensivePlugin is IValidation, IValidationHook, IExecutionHook, Ba revert NotImplemented(); } + function validateSignature(uint8 functionId, address, bytes32, bytes calldata) + external + pure + returns (bytes4) + { + if (functionId == uint8(FunctionId.SIG_VALIDATION)) { + return 0xffffffff; + } + revert NotImplemented(); + } + function preExecutionHook(uint8 functionId, address, uint256, bytes calldata) external pure diff --git a/test/mocks/plugins/ValidationPluginMocks.sol b/test/mocks/plugins/ValidationPluginMocks.sol index 48ca0ea1..9e7909df 100644 --- a/test/mocks/plugins/ValidationPluginMocks.sol +++ b/test/mocks/plugins/ValidationPluginMocks.sol @@ -59,6 +59,10 @@ abstract contract MockBaseUserOpValidationPlugin is IValidation, IValidationHook revert NotImplemented(); } + function validateSignature(uint8, address, bytes32, bytes calldata) external pure override returns (bytes4) { + revert NotImplemented(); + } + // Empty stubs function pluginMetadata() external pure override returns (PluginMetadata memory) {} diff --git a/test/plugin/SingleOwnerPlugin.t.sol b/test/plugin/SingleOwnerPlugin.t.sol index 5c2ecb63..71afbdbf 100644 --- a/test/plugin/SingleOwnerPlugin.t.sol +++ b/test/plugin/SingleOwnerPlugin.t.sol @@ -157,20 +157,41 @@ contract SingleOwnerPluginTest is OptimizedTest { (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest); // sig check should fail - assertEq(plugin.isValidSignature(digest, abi.encodePacked(r, s, v)), bytes4(0xFFFFFFFF)); + assertEq( + plugin.validateSignature( + uint8(ISingleOwnerPlugin.FunctionId.SIG_VALIDATION), + address(this), + digest, + abi.encodePacked(r, s, v) + ), + bytes4(0xFFFFFFFF) + ); // transfer ownership to signer plugin.transferOwnership(signer); assertEq(signer, plugin.owner()); // sig check should pass - assertEq(plugin.isValidSignature(digest, abi.encodePacked(r, s, v)), _1271_MAGIC_VALUE); + assertEq( + plugin.validateSignature( + uint8(ISingleOwnerPlugin.FunctionId.SIG_VALIDATION), + address(this), + digest, + abi.encodePacked(r, s, v) + ), + _1271_MAGIC_VALUE + ); } function testFuzz_isValidSignatureForContractOwner(bytes32 digest) public { vm.startPrank(a); plugin.transferOwnership(address(contractOwner)); bytes memory signature = contractOwner.sign(digest); - assertEq(plugin.isValidSignature(digest, signature), _1271_MAGIC_VALUE); + assertEq( + plugin.validateSignature( + uint8(ISingleOwnerPlugin.FunctionId.SIG_VALIDATION), address(this), digest, signature + ), + _1271_MAGIC_VALUE + ); } } From 7ac0f6b9e4e3085de7a8b7b55b387ab4c3b49030 Mon Sep 17 00:00:00 2001 From: Adam Egyed <5456061+adamegyed@users.noreply.github.com> Date: Fri, 31 May 2024 11:44:33 -0700 Subject: [PATCH 014/111] feat: [v0.8-develop, experimental]: remove plugin call restriction [3/N] (#60) --- src/account/AccountExecutor.sol | 13 --------- src/plugins/owner/SingleOwnerPlugin.sol | 28 ++++---------------- test/account/AccountLoupe.t.sol | 10 +++---- test/account/UpgradeableModularAccount.t.sol | 13 ++++++--- test/utils/AccountTestBase.sol | 4 ++- 5 files changed, 20 insertions(+), 48 deletions(-) diff --git a/src/account/AccountExecutor.sol b/src/account/AccountExecutor.sol index 136676f6..4ed2c57a 100644 --- a/src/account/AccountExecutor.sol +++ b/src/account/AccountExecutor.sol @@ -1,25 +1,12 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.25; -import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; -import {IPlugin} from "../interfaces/IPlugin.sol"; - abstract contract AccountExecutor { - error PluginExecutionDenied(address plugin); - - /// @dev If the target is a plugin (as determined by its support for the IPlugin interface), revert. - /// This prevents the modular account from calling plugins (both installed and uninstalled) outside - /// of the normal flow (via execution functions installed on the account), which could lead to data - /// inconsistencies and unexpected behavior. /// @param target The address of the contract to call. /// @param value The value to send with the call. /// @param data The call data. /// @return result The return data of the call, or the error message from the call if call reverts. function _exec(address target, uint256 value, bytes memory data) internal returns (bytes memory result) { - if (ERC165Checker.supportsInterface(target, type(IPlugin).interfaceId)) { - revert PluginExecutionDenied(target); - } - bool success; (success, result) = target.call{value: value}(data); diff --git a/src/plugins/owner/SingleOwnerPlugin.sol b/src/plugins/owner/SingleOwnerPlugin.sol index 825b0230..bebbad21 100644 --- a/src/plugins/owner/SingleOwnerPlugin.sol +++ b/src/plugins/owner/SingleOwnerPlugin.sol @@ -152,51 +152,33 @@ contract SingleOwnerPlugin is ISingleOwnerPlugin, BasePlugin { function pluginManifest() external pure override returns (PluginManifest memory) { PluginManifest memory manifest; - manifest.executionFunctions = new bytes4[](2); - manifest.executionFunctions[0] = this.transferOwnership.selector; - manifest.executionFunctions[1] = this.owner.selector; - ManifestFunction memory ownerValidationFunction = ManifestFunction({ functionType: ManifestAssociatedFunctionType.SELF, functionId: uint8(FunctionId.VALIDATION_OWNER_OR_SELF), dependencyIndex: 0 // Unused. }); - manifest.validationFunctions = new ManifestAssociatedFunction[](7); + manifest.validationFunctions = new ManifestAssociatedFunction[](5); manifest.validationFunctions[0] = ManifestAssociatedFunction({ - executionSelector: this.transferOwnership.selector, - associatedFunction: ownerValidationFunction - }); - manifest.validationFunctions[1] = ManifestAssociatedFunction({ executionSelector: IStandardExecutor.execute.selector, associatedFunction: ownerValidationFunction }); - manifest.validationFunctions[2] = ManifestAssociatedFunction({ + manifest.validationFunctions[1] = ManifestAssociatedFunction({ executionSelector: IStandardExecutor.executeBatch.selector, associatedFunction: ownerValidationFunction }); - manifest.validationFunctions[3] = ManifestAssociatedFunction({ + manifest.validationFunctions[2] = ManifestAssociatedFunction({ executionSelector: IPluginManager.installPlugin.selector, associatedFunction: ownerValidationFunction }); - manifest.validationFunctions[4] = ManifestAssociatedFunction({ + manifest.validationFunctions[3] = ManifestAssociatedFunction({ executionSelector: IPluginManager.uninstallPlugin.selector, associatedFunction: ownerValidationFunction }); - manifest.validationFunctions[5] = ManifestAssociatedFunction({ + manifest.validationFunctions[4] = ManifestAssociatedFunction({ executionSelector: UUPSUpgradeable.upgradeToAndCall.selector, associatedFunction: ownerValidationFunction }); - ManifestFunction memory alwaysAllowRuntime = ManifestFunction({ - functionType: ManifestAssociatedFunctionType.RUNTIME_VALIDATION_ALWAYS_ALLOW, - functionId: 0, // Unused. - dependencyIndex: 0 // Unused. - }); - manifest.validationFunctions[6] = ManifestAssociatedFunction({ - executionSelector: this.owner.selector, - associatedFunction: alwaysAllowRuntime - }); - manifest.signatureValidationFunctions = new uint8[](1); manifest.signatureValidationFunctions[0] = uint8(FunctionId.SIG_VALIDATION); diff --git a/test/account/AccountLoupe.t.sol b/test/account/AccountLoupe.t.sol index 9fbc1e21..34c914a7 100644 --- a/test/account/AccountLoupe.t.sol +++ b/test/account/AccountLoupe.t.sol @@ -73,9 +73,9 @@ contract AccountLoupeTest is AccountTestBase { } function test_pluginLoupe_getExecutionFunctionConfig_plugin() public { - bytes4[] memory selectorsToCheck = new bytes4[](2); - address[] memory expectedPluginAddress = new address[](2); - FunctionReference[] memory expectedValidations = new FunctionReference[](2); + bytes4[] memory selectorsToCheck = new bytes4[](1); + address[] memory expectedPluginAddress = new address[](1); + FunctionReference[] memory expectedValidations = new FunctionReference[](1); selectorsToCheck[0] = comprehensivePlugin.foo.selector; expectedPluginAddress[0] = address(comprehensivePlugin); @@ -83,10 +83,6 @@ contract AccountLoupeTest is AccountTestBase { address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.VALIDATION) ); - selectorsToCheck[1] = singleOwnerPlugin.transferOwnership.selector; - expectedPluginAddress[1] = address(singleOwnerPlugin); - expectedValidations[1] = ownerValidation; - for (uint256 i = 0; i < selectorsToCheck.length; i++) { IAccountLoupe.ExecutionFunctionConfig memory config = account1.getExecutionFunctionConfig(selectorsToCheck[i]); diff --git a/test/account/UpgradeableModularAccount.t.sol b/test/account/UpgradeableModularAccount.t.sol index 363d1654..c5a061e0 100644 --- a/test/account/UpgradeableModularAccount.t.sol +++ b/test/account/UpgradeableModularAccount.t.sol @@ -96,7 +96,10 @@ contract UpgradeableModularAccountTest is AccountTestBase { sender: address(account2), nonce: 0, initCode: abi.encodePacked(address(factory), abi.encodeCall(factory.createAccount, (owner2, 0))), - callData: abi.encodeCall(SingleOwnerPlugin.transferOwnership, (owner2)), + callData: abi.encodeCall( + UpgradeableModularAccount.execute, + (address(singleOwnerPlugin), 0, abi.encodeCall(SingleOwnerPlugin.transferOwnership, (owner2))) + ), accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), preVerificationGas: 0, gasFees: _encodeGas(1, 2), @@ -422,12 +425,14 @@ contract UpgradeableModularAccountTest is AccountTestBase { } function test_transferOwnership() public { - assertEq(SingleOwnerPlugin(address(account1)).owner(), owner1); + assertEq(singleOwnerPlugin.ownerOf(address(account1)), owner1); vm.prank(owner1); - SingleOwnerPlugin(address(account1)).transferOwnership(owner2); + account1.execute( + address(singleOwnerPlugin), 0, abi.encodeCall(SingleOwnerPlugin.transferOwnership, (owner2)) + ); - assertEq(SingleOwnerPlugin(address(account1)).owner(), owner2); + assertEq(singleOwnerPlugin.ownerOf(address(account1)), owner2); } function test_isValidSignature() public { diff --git a/test/utils/AccountTestBase.sol b/test/utils/AccountTestBase.sol index 5d9880f9..d8c890fe 100644 --- a/test/utils/AccountTestBase.sol +++ b/test/utils/AccountTestBase.sol @@ -37,7 +37,9 @@ abstract contract AccountTestBase is OptimizedTest { function _transferOwnershipToTest() internal { // Transfer ownership to test contract for easier invocation. vm.prank(owner1); - SingleOwnerPlugin(address(account1)).transferOwnership(address(this)); + account1.execute( + address(singleOwnerPlugin), 0, abi.encodeCall(SingleOwnerPlugin.transferOwnership, (address(this))) + ); } // helper function to compress 2 gas values into a single bytes32 From 0923c2178405359b41289fabc07b7d2c8ee0b04e Mon Sep 17 00:00:00 2001 From: Adam Egyed <5456061+adamegyed@users.noreply.github.com> Date: Fri, 31 May 2024 11:49:52 -0700 Subject: [PATCH 015/111] feat: [v0.8-develop]: move runtime validation always allow to execution function definition (#61) --- src/account/AccountStorage.sol | 11 ++- src/account/PluginManagerInternals.sol | 36 ++++---- src/account/UpgradeableModularAccount.sol | 35 ++++---- src/helpers/FunctionReferenceLib.sol | 7 -- src/interfaces/IPlugin.sol | 15 ++-- src/plugins/TokenReceiverPlugin.sol | 40 ++------- test/account/AccountExecHooks.t.sol | 14 +--- test/account/ManifestValidity.t.sol | 46 +--------- test/libraries/AccountStorage.t.sol | 8 +- .../plugins/BadTransferOwnershipPlugin.sol | 23 +---- test/mocks/plugins/ComprehensivePlugin.sol | 6 +- .../ExecFromPluginPermissionsMocks.sol | 56 ++++--------- test/mocks/plugins/ManifestValidityMocks.sol | 83 ++----------------- test/mocks/plugins/ReturnDataPluginMocks.sol | 48 +++-------- test/mocks/plugins/ValidationPluginMocks.sol | 16 ++-- 15 files changed, 111 insertions(+), 333 deletions(-) diff --git a/src/account/AccountStorage.sol b/src/account/AccountStorage.sol index 09a4a974..205af107 100644 --- a/src/account/AccountStorage.sol +++ b/src/account/AccountStorage.sol @@ -35,6 +35,10 @@ struct SelectorData { // The plugin that implements this execution function. // If this is a native function, the address must remain address(0). address plugin; + // Whether or not the function needs runtime validation, or can be called by anyone. + // 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 isPublic; // How many times a `PRE_HOOK_ALWAYS_DENY` has been added for this function. // Since that is the only type of hook that may overlap, we can use this to track the number of times it has // been applied, and whether or not the deny should apply. The size `uint48` was chosen somewhat arbitrarily, @@ -57,8 +61,7 @@ struct AccountStorage { mapping(address => PluginData) pluginData; // Execution functions and their associated functions mapping(bytes4 => SelectorData) selectorData; - // bytes24 key = address(calling plugin) || bytes4(selector of execution function) - mapping(bytes24 => bool) callPermitted; + mapping(address caller => mapping(bytes4 selector => bool)) callPermitted; // key = address(calling plugin) || target address mapping(IPlugin => mapping(address => PermittedExternalCallData)) permittedExternalCalls; // For ERC165 introspection @@ -77,10 +80,6 @@ function getAccountStorage() pure returns (AccountStorage storage _storage) { } } -function getPermittedCallKey(address addr, bytes4 selector) pure returns (bytes24) { - return bytes24(bytes20(addr)) | (bytes24(selector) >> 160); -} - using EnumerableSet for EnumerableSet.Bytes32Set; function toSetValue(FunctionReference functionReference) pure returns (bytes32) { diff --git a/src/account/PluginManagerInternals.sol b/src/account/PluginManagerInternals.sol index a3a108c1..7ded3176 100644 --- a/src/account/PluginManagerInternals.sol +++ b/src/account/PluginManagerInternals.sol @@ -21,7 +21,6 @@ import { getAccountStorage, SelectorData, toSetValue, - getPermittedCallKey, PermittedExternalCallData } from "./AccountStorage.sol"; @@ -60,7 +59,10 @@ abstract contract PluginManagerInternals is IPluginManager { // Storage update operations - function _setExecutionFunction(bytes4 selector, address plugin) internal notNullPlugin(plugin) { + function _setExecutionFunction(bytes4 selector, bool isPublic, address plugin) + internal + notNullPlugin(plugin) + { SelectorData storage _selectorData = getAccountStorage().selectorData[selector]; if (_selectorData.plugin != address(0)) { @@ -68,12 +70,14 @@ abstract contract PluginManagerInternals is IPluginManager { } _selectorData.plugin = plugin; + _selectorData.isPublic = isPublic; } function _removeExecutionFunction(bytes4 selector) internal { SelectorData storage _selectorData = getAccountStorage().selectorData[selector]; _selectorData.plugin = address(0); + _selectorData.isPublic = false; } function _addValidationFunction(bytes4 selector, FunctionReference validationFunction) @@ -212,7 +216,9 @@ abstract contract PluginManagerInternals is IPluginManager { length = manifest.executionFunctions.length; for (uint256 i = 0; i < length; ++i) { - _setExecutionFunction(manifest.executionFunctions[i], plugin); + bytes4 selector = manifest.executionFunctions[i].executionSelector; + bool isPublic = manifest.executionFunctions[i].isPublic; + _setExecutionFunction(selector, isPublic, plugin); } // Add installed plugin and selectors this plugin can call @@ -220,7 +226,7 @@ abstract contract PluginManagerInternals is IPluginManager { for (uint256 i = 0; i < length; ++i) { // If there are duplicates, this will just enable the flag again. This is not a problem, since the // boolean will be set to false twice during uninstall, which is fine. - _storage.callPermitted[getPermittedCallKey(plugin, manifest.permittedExecutionSelectors[i])] = true; + _storage.callPermitted[plugin][manifest.permittedExecutionSelectors[i]] = true; } // Add the permitted external calls to the account. @@ -254,10 +260,7 @@ abstract contract PluginManagerInternals is IPluginManager { _addValidationFunction( mv.executionSelector, _resolveManifestFunction( - mv.associatedFunction, - plugin, - dependencies, - ManifestAssociatedFunctionType.RUNTIME_VALIDATION_ALWAYS_ALLOW + mv.associatedFunction, plugin, dependencies, ManifestAssociatedFunctionType.NONE ) ); } @@ -379,10 +382,7 @@ abstract contract PluginManagerInternals is IPluginManager { _removeValidationFunction( mv.executionSelector, _resolveManifestFunction( - mv.associatedFunction, - plugin, - dependencies, - ManifestAssociatedFunctionType.RUNTIME_VALIDATION_ALWAYS_ALLOW + mv.associatedFunction, plugin, dependencies, ManifestAssociatedFunctionType.NONE ) ); } @@ -417,12 +417,13 @@ abstract contract PluginManagerInternals is IPluginManager { length = manifest.permittedExecutionSelectors.length; for (uint256 i = 0; i < length; ++i) { - _storage.callPermitted[getPermittedCallKey(plugin, manifest.permittedExecutionSelectors[i])] = false; + _storage.callPermitted[plugin][manifest.permittedExecutionSelectors[i]] = false; } length = manifest.executionFunctions.length; for (uint256 i = 0; i < length; ++i) { - _removeExecutionFunction(manifest.executionFunctions[i]); + bytes4 selector = manifest.executionFunctions[i].executionSelector; + _removeExecutionFunction(selector); } length = manifest.interfaceIds.length; @@ -466,13 +467,6 @@ abstract contract PluginManagerInternals is IPluginManager { revert InvalidPluginManifest(); } return dependencies[manifestFunction.dependencyIndex]; - } else if (manifestFunction.functionType == ManifestAssociatedFunctionType.RUNTIME_VALIDATION_ALWAYS_ALLOW) - { - if (allowedMagicValue == ManifestAssociatedFunctionType.RUNTIME_VALIDATION_ALWAYS_ALLOW) { - return FunctionReferenceLib._RUNTIME_VALIDATION_ALWAYS_ALLOW; - } else { - revert InvalidPluginManifest(); - } } else if (manifestFunction.functionType == ManifestAssociatedFunctionType.PRE_HOOK_ALWAYS_DENY) { if (allowedMagicValue == ManifestAssociatedFunctionType.PRE_HOOK_ALWAYS_DENY) { return FunctionReferenceLib._PRE_HOOK_ALWAYS_DENY; diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index b815e42f..1cd0d98e 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -23,7 +23,6 @@ import {AccountLoupe} from "./AccountLoupe.sol"; import { AccountStorage, getAccountStorage, - getPermittedCallKey, SelectorData, toSetValue, toFunctionReference, @@ -187,11 +186,9 @@ contract UpgradeableModularAccount is bytes4 selector = bytes4(data[:4]); address callingPlugin = msg.sender; - bytes24 execFromPluginKey = getPermittedCallKey(callingPlugin, selector); - AccountStorage storage _storage = getAccountStorage(); - if (!_storage.callPermitted[execFromPluginKey]) { + if (!_storage.callPermitted[callingPlugin][selector]) { revert ExecFromPluginNotPermitted(callingPlugin, selector); } @@ -375,10 +372,8 @@ contract UpgradeableModularAccount is PackedUserOperation calldata userOp, bytes32 userOpHash ) internal returns (uint256 validationData) { - if (userOpValidationFunction.isEmptyOrMagicValue()) { + if (userOpValidationFunction.isEmpty()) { // If the validation function is empty, then the call cannot proceed. - // Alternatively, the validation function may be set to the RUNTIME_VALIDATION_ALWAYS_ALLOW magic - // value, in which case we also revert. revert UserOpValidationFunctionMissing(selector); } @@ -446,18 +441,20 @@ contract UpgradeableModularAccount is // Identifier scope limiting { - if (!runtimeValidationFunction.isEmptyOrMagicValue()) { - (address plugin, uint8 functionId) = runtimeValidationFunction.unpack(); - // solhint-disable-next-line no-empty-blocks - try IValidation(plugin).validateRuntime(functionId, msg.sender, msg.value, msg.data) {} - catch (bytes memory revertReason) { - revert RuntimeValidationFunctionReverted(plugin, functionId, revertReason); - } - } else { - if (runtimeValidationFunction.isEmpty()) { - revert RuntimeValidationFunctionMissing(msg.sig); - } - // If _RUNTIME_VALIDATION_ALWAYS_ALLOW, just let the function finish. + if (_storage.selectorData[msg.sig].isPublic) { + // If the function is public, we don't need to check the runtime validation function. + return; + } + + if (runtimeValidationFunction.isEmpty()) { + revert RuntimeValidationFunctionMissing(msg.sig); + } + + (address plugin, uint8 functionId) = runtimeValidationFunction.unpack(); + // solhint-disable-next-line no-empty-blocks + try IValidation(plugin).validateRuntime(functionId, msg.sender, msg.value, msg.data) {} + catch (bytes memory revertReason) { + revert RuntimeValidationFunctionReverted(plugin, functionId, revertReason); } } } diff --git a/src/helpers/FunctionReferenceLib.sol b/src/helpers/FunctionReferenceLib.sol index e80992c9..7bddd94b 100644 --- a/src/helpers/FunctionReferenceLib.sol +++ b/src/helpers/FunctionReferenceLib.sol @@ -6,9 +6,6 @@ import {FunctionReference} from "../interfaces/IPluginManager.sol"; library FunctionReferenceLib { // Empty or unset function reference. FunctionReference internal constant _EMPTY_FUNCTION_REFERENCE = FunctionReference.wrap(bytes21(0)); - // Magic value for runtime validation functions that always allow access. - FunctionReference internal constant _RUNTIME_VALIDATION_ALWAYS_ALLOW = - FunctionReference.wrap(bytes21(uint168(1))); // Magic value for hooks that should always revert. FunctionReference internal constant _PRE_HOOK_ALWAYS_DENY = FunctionReference.wrap(bytes21(uint168(2))); @@ -30,10 +27,6 @@ library FunctionReferenceLib { return FunctionReference.unwrap(fr) != bytes21(0); } - function isEmptyOrMagicValue(FunctionReference fr) internal pure returns (bool) { - return FunctionReference.unwrap(fr) <= bytes21(uint168(2)); - } - function eq(FunctionReference a, FunctionReference b) internal pure returns (bool) { return FunctionReference.unwrap(a) == FunctionReference.unwrap(b); } diff --git a/src/interfaces/IPlugin.sol b/src/interfaces/IPlugin.sol index a229f51b..a74dfaf8 100644 --- a/src/interfaces/IPlugin.sol +++ b/src/interfaces/IPlugin.sol @@ -13,11 +13,6 @@ enum ManifestAssociatedFunctionType { SELF, // Function belongs to an external plugin provided as a dependency during plugin installation. DEPENDENCY, - // Resolves to a magic value to always bypass runtime validation for a given function. - // This is only assignable on runtime validation functions. If it were to be used on a user op validation function, - // it would risk burning gas from the account. When used as a hook in any hook location, it is equivalent to not - // setting a hook and is therefore disallowed. - RUNTIME_VALIDATION_ALWAYS_ALLOW, // Resolves to a magic value to always fail in a hook for a given function. // This is only assignable to pre execution hooks. It should not be used on validation functions themselves, because // this is equivalent to leaving the validation functions unset. It should not be used in post-exec hooks, because @@ -26,6 +21,14 @@ enum ManifestAssociatedFunctionType { } // forgefmt: disable-end +struct ManifestExecutionFunction { + // TODO(erc6900 spec): These fields can be packed into a single word + // The selector to install + bytes4 executionSelector; + // If true, the function won't need runtime validation, and can be called by anyone. + bool isPublic; +} + /// @dev For functions of type `ManifestAssociatedFunctionType.DEPENDENCY`, the MSCA MUST find the plugin address /// of the function at `dependencies[dependencyIndex]` during the call to `installPlugin(config)`. struct ManifestFunction { @@ -82,7 +85,7 @@ struct PluginManifest { // structs used in the manifest. bytes4[] dependencyInterfaceIds; // Execution functions defined in this plugin to be installed on the MSCA. - bytes4[] executionFunctions; + ManifestExecutionFunction[] executionFunctions; // Plugin execution functions already installed on the MSCA that this plugin will be able to call. bytes4[] permittedExecutionSelectors; // Boolean to indicate whether the plugin can call any external address. diff --git a/src/plugins/TokenReceiverPlugin.sol b/src/plugins/TokenReceiverPlugin.sol index 1a9e8feb..f3379f1a 100644 --- a/src/plugins/TokenReceiverPlugin.sol +++ b/src/plugins/TokenReceiverPlugin.sol @@ -4,14 +4,7 @@ pragma solidity ^0.8.25; import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; import {IERC1155Receiver} from "@openzeppelin/contracts/interfaces/IERC1155Receiver.sol"; -import { - IPlugin, - ManifestFunction, - ManifestAssociatedFunctionType, - ManifestAssociatedFunction, - PluginManifest, - PluginMetadata -} from "../interfaces/IPlugin.sol"; +import {IPlugin, ManifestExecutionFunction, PluginManifest, PluginMetadata} from "../interfaces/IPlugin.sol"; import {BasePlugin} from "./BasePlugin.sol"; /// @title Token Receiver Plugin @@ -65,30 +58,13 @@ contract TokenReceiverPlugin is BasePlugin, IERC721Receiver, IERC1155Receiver { function pluginManifest() external pure override returns (PluginManifest memory) { PluginManifest memory manifest; - manifest.executionFunctions = new bytes4[](3); - manifest.executionFunctions[0] = this.onERC721Received.selector; - manifest.executionFunctions[1] = this.onERC1155Received.selector; - manifest.executionFunctions[2] = this.onERC1155BatchReceived.selector; - - // Only runtime validationFunction is needed since callbacks come from token contracts only - ManifestFunction memory alwaysAllowRuntime = ManifestFunction({ - functionType: ManifestAssociatedFunctionType.RUNTIME_VALIDATION_ALWAYS_ALLOW, - functionId: 0, // Unused. - dependencyIndex: 0 // Unused. - }); - manifest.validationFunctions = new ManifestAssociatedFunction[](3); - manifest.validationFunctions[0] = ManifestAssociatedFunction({ - executionSelector: this.onERC721Received.selector, - associatedFunction: alwaysAllowRuntime - }); - manifest.validationFunctions[1] = ManifestAssociatedFunction({ - executionSelector: this.onERC1155Received.selector, - associatedFunction: alwaysAllowRuntime - }); - manifest.validationFunctions[2] = ManifestAssociatedFunction({ - executionSelector: this.onERC1155BatchReceived.selector, - associatedFunction: alwaysAllowRuntime - }); + manifest.executionFunctions = new ManifestExecutionFunction[](3); + manifest.executionFunctions[0] = + ManifestExecutionFunction({executionSelector: this.onERC721Received.selector, isPublic: true}); + manifest.executionFunctions[1] = + ManifestExecutionFunction({executionSelector: this.onERC1155Received.selector, isPublic: true}); + manifest.executionFunctions[2] = + ManifestExecutionFunction({executionSelector: this.onERC1155BatchReceived.selector, isPublic: true}); manifest.interfaceIds = new bytes4[](2); manifest.interfaceIds[0] = type(IERC721Receiver).interfaceId; diff --git a/test/account/AccountExecHooks.t.sol b/test/account/AccountExecHooks.t.sol index a1db86c6..3605ddd5 100644 --- a/test/account/AccountExecHooks.t.sol +++ b/test/account/AccountExecHooks.t.sol @@ -6,6 +6,7 @@ import { ManifestAssociatedFunctionType, ManifestAssociatedFunction, ManifestExecutionHook, + ManifestExecutionFunction, ManifestFunction, PluginManifest } from "../../src/interfaces/IPlugin.sol"; @@ -38,18 +39,7 @@ contract AccountExecHooksTest is AccountTestBase { function setUp() public { _transferOwnershipToTest(); - m1.executionFunctions.push(_EXEC_SELECTOR); - - m1.validationFunctions.push( - ManifestAssociatedFunction({ - executionSelector: _EXEC_SELECTOR, - associatedFunction: ManifestFunction({ - functionType: ManifestAssociatedFunctionType.RUNTIME_VALIDATION_ALWAYS_ALLOW, - functionId: 0, - dependencyIndex: 0 - }) - }) - ); + m1.executionFunctions.push(ManifestExecutionFunction({executionSelector: _EXEC_SELECTOR, isPublic: true})); } function test_preExecHook_install() public { diff --git a/test/account/ManifestValidity.t.sol b/test/account/ManifestValidity.t.sol index 21023ef3..8200a017 100644 --- a/test/account/ManifestValidity.t.sol +++ b/test/account/ManifestValidity.t.sol @@ -4,11 +4,7 @@ pragma solidity ^0.8.19; import {PluginManagerInternals} from "../../src/account/PluginManagerInternals.sol"; import {FunctionReference} from "../../src/helpers/FunctionReferenceLib.sol"; -import { - BadValidationMagicValue_PreValidationHook_Plugin, - BadHookMagicValue_UserOpValidationFunction_Plugin, - BadHookMagicValue_RuntimeValidationFunction_Plugin -} from "../mocks/plugins/ManifestValidityMocks.sol"; +import {BadHookMagicValue_ValidationFunction_Plugin} from "../mocks/plugins/ManifestValidityMocks.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; contract ManifestValidityTest is AccountTestBase { @@ -16,43 +12,9 @@ contract ManifestValidityTest is AccountTestBase { _transferOwnershipToTest(); } - // Tests that the plugin manager rejects a plugin with a pre-runtime validation hook set to "validation always - // allow" - function test_ManifestValidity_invalid_ValidationAlwaysAllow_PreValidationHook() public { - BadValidationMagicValue_PreValidationHook_Plugin plugin = - new BadValidationMagicValue_PreValidationHook_Plugin(); - - bytes32 manifestHash = keccak256(abi.encode(plugin.pluginManifest())); - - vm.expectRevert(abi.encodeWithSelector(PluginManagerInternals.InvalidPluginManifest.selector)); - account1.installPlugin({ - plugin: address(plugin), - manifestHash: manifestHash, - pluginInstallData: "", - dependencies: new FunctionReference[](0) - }); - } - - // Tests that the plugin manager rejects a plugin with a user op validationFunction set to "hook always deny" - function test_ManifestValidity_invalid_HookAlwaysDeny_UserOpValidation() public { - BadHookMagicValue_UserOpValidationFunction_Plugin plugin = - new BadHookMagicValue_UserOpValidationFunction_Plugin(); - - bytes32 manifestHash = keccak256(abi.encode(plugin.pluginManifest())); - - vm.expectRevert(abi.encodeWithSelector(PluginManagerInternals.InvalidPluginManifest.selector)); - account1.installPlugin({ - plugin: address(plugin), - manifestHash: manifestHash, - pluginInstallData: "", - dependencies: new FunctionReference[](0) - }); - } - - // Tests that the plugin manager rejects a plugin with a runtime validationFunction set to "hook always deny" - function test_ManifestValidity_invalid_HookAlwaysDeny_RuntimeValidationFunction() public { - BadHookMagicValue_RuntimeValidationFunction_Plugin plugin = - new BadHookMagicValue_RuntimeValidationFunction_Plugin(); + // Tests that the plugin manager rejects a plugin with a validation function set to "hook always deny" + function test_ManifestValidity_invalid_HookAlwaysDeny_Validation() public { + BadHookMagicValue_ValidationFunction_Plugin plugin = new BadHookMagicValue_ValidationFunction_Plugin(); bytes32 manifestHash = keccak256(abi.encode(plugin.pluginManifest())); diff --git a/test/libraries/AccountStorage.t.sol b/test/libraries/AccountStorage.t.sol index f59049a3..25594cd2 100644 --- a/test/libraries/AccountStorage.t.sol +++ b/test/libraries/AccountStorage.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; import {Test} from "forge-std/Test.sol"; -import {_ACCOUNT_STORAGE_SLOT, getPermittedCallKey} from "../../src/account/AccountStorage.sol"; +import {_ACCOUNT_STORAGE_SLOT} from "../../src/account/AccountStorage.sol"; import {AccountStorageInitializable} from "../../src/account/AccountStorageInitializable.sol"; import {MockDiamondStorageContract} from "../mocks/MockDiamondStorageContract.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; @@ -34,10 +34,4 @@ contract AccountStorageTest is Test { // post init slot should contains: packed(uint8 initialized = 1, bool initializing = 0) assertEq(uint256(vm.load(proxy, _ACCOUNT_STORAGE_SLOT)), uint256(1)); } - - function testFuzz_permittedCallKey(address addr, bytes4 selector) public { - bytes24 key = getPermittedCallKey(addr, selector); - assertEq(bytes20(addr), bytes20(key)); - assertEq(bytes4(selector), bytes4(key << 160)); - } } diff --git a/test/mocks/plugins/BadTransferOwnershipPlugin.sol b/test/mocks/plugins/BadTransferOwnershipPlugin.sol index 9d07687e..6561bb1b 100644 --- a/test/mocks/plugins/BadTransferOwnershipPlugin.sol +++ b/test/mocks/plugins/BadTransferOwnershipPlugin.sol @@ -1,13 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import { - ManifestFunction, - ManifestAssociatedFunctionType, - ManifestAssociatedFunction, - PluginManifest, - PluginMetadata -} from "../../../src/interfaces/IPlugin.sol"; +import {ManifestExecutionFunction, PluginManifest, PluginMetadata} from "../../../src/interfaces/IPlugin.sol"; import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; import {ISingleOwnerPlugin} from "../../../src/plugins/owner/ISingleOwnerPlugin.sol"; import {IPluginExecutor} from "../../../src/interfaces/IPluginExecutor.sol"; @@ -38,22 +32,13 @@ contract BadTransferOwnershipPlugin is BasePlugin { function pluginManifest() external pure override returns (PluginManifest memory) { PluginManifest memory manifest; - manifest.executionFunctions = new bytes4[](1); - manifest.executionFunctions[0] = this.evilTransferOwnership.selector; + manifest.executionFunctions = new ManifestExecutionFunction[](1); + manifest.executionFunctions[0] = + ManifestExecutionFunction({executionSelector: this.evilTransferOwnership.selector, isPublic: true}); manifest.permittedExecutionSelectors = new bytes4[](1); manifest.permittedExecutionSelectors[0] = ISingleOwnerPlugin.transferOwnership.selector; - manifest.validationFunctions = new ManifestAssociatedFunction[](1); - manifest.validationFunctions[0] = ManifestAssociatedFunction({ - executionSelector: this.evilTransferOwnership.selector, - associatedFunction: ManifestFunction({ - functionType: ManifestAssociatedFunctionType.RUNTIME_VALIDATION_ALWAYS_ALLOW, - functionId: 0, // Unused. - dependencyIndex: 0 // Unused. - }) - }); - return manifest; } diff --git a/test/mocks/plugins/ComprehensivePlugin.sol b/test/mocks/plugins/ComprehensivePlugin.sol index bdb82499..9aac5545 100644 --- a/test/mocks/plugins/ComprehensivePlugin.sol +++ b/test/mocks/plugins/ComprehensivePlugin.sol @@ -5,6 +5,7 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import { ManifestExecutionHook, + ManifestExecutionFunction, ManifestFunction, ManifestAssociatedFunctionType, ManifestAssociatedFunction, @@ -127,8 +128,9 @@ contract ComprehensivePlugin is IValidation, IValidationHook, IExecutionHook, Ba function pluginManifest() external pure override returns (PluginManifest memory) { PluginManifest memory manifest; - manifest.executionFunctions = new bytes4[](1); - manifest.executionFunctions[0] = this.foo.selector; + manifest.executionFunctions = new ManifestExecutionFunction[](1); + manifest.executionFunctions[0] = + ManifestExecutionFunction({executionSelector: this.foo.selector, isPublic: false}); ManifestFunction memory fooValidationFunction = ManifestFunction({ functionType: ManifestAssociatedFunctionType.SELF, diff --git a/test/mocks/plugins/ExecFromPluginPermissionsMocks.sol b/test/mocks/plugins/ExecFromPluginPermissionsMocks.sol index ae775dd0..11970dfa 100644 --- a/test/mocks/plugins/ExecFromPluginPermissionsMocks.sol +++ b/test/mocks/plugins/ExecFromPluginPermissionsMocks.sol @@ -2,9 +2,7 @@ pragma solidity ^0.8.19; import { - ManifestFunction, - ManifestAssociatedFunctionType, - ManifestAssociatedFunction, + ManifestExecutionFunction, ManifestExternalCallPermission, PluginManifest, PluginMetadata @@ -36,32 +34,21 @@ contract EFPCallerPlugin is BasePlugin { function _getManifest() internal view returns (PluginManifest memory) { PluginManifest memory manifest; - manifest.executionFunctions = new bytes4[](11); - manifest.executionFunctions[0] = this.useEFPPermissionAllowed.selector; - manifest.executionFunctions[1] = this.useEFPPermissionNotAllowed.selector; - manifest.executionFunctions[2] = this.setNumberCounter1.selector; - manifest.executionFunctions[3] = this.getNumberCounter1.selector; - manifest.executionFunctions[4] = this.incrementCounter1.selector; - manifest.executionFunctions[5] = this.setNumberCounter2.selector; - manifest.executionFunctions[6] = this.getNumberCounter2.selector; - manifest.executionFunctions[7] = this.incrementCounter2.selector; - manifest.executionFunctions[8] = this.setNumberCounter3.selector; - manifest.executionFunctions[9] = this.getNumberCounter3.selector; - manifest.executionFunctions[10] = this.incrementCounter3.selector; - - manifest.validationFunctions = new ManifestAssociatedFunction[](11); - - ManifestFunction memory alwaysAllowValidationFunction = ManifestFunction({ - functionType: ManifestAssociatedFunctionType.RUNTIME_VALIDATION_ALWAYS_ALLOW, - functionId: 0, - dependencyIndex: 0 - }); + manifest.executionFunctions = new ManifestExecutionFunction[](11); + manifest.executionFunctions[0].executionSelector = this.useEFPPermissionAllowed.selector; + manifest.executionFunctions[1].executionSelector = this.useEFPPermissionNotAllowed.selector; + manifest.executionFunctions[2].executionSelector = this.setNumberCounter1.selector; + manifest.executionFunctions[3].executionSelector = this.getNumberCounter1.selector; + manifest.executionFunctions[4].executionSelector = this.incrementCounter1.selector; + manifest.executionFunctions[5].executionSelector = this.setNumberCounter2.selector; + manifest.executionFunctions[6].executionSelector = this.getNumberCounter2.selector; + manifest.executionFunctions[7].executionSelector = this.incrementCounter2.selector; + manifest.executionFunctions[8].executionSelector = this.setNumberCounter3.selector; + manifest.executionFunctions[9].executionSelector = this.getNumberCounter3.selector; + manifest.executionFunctions[10].executionSelector = this.incrementCounter3.selector; for (uint256 i = 0; i < manifest.executionFunctions.length; i++) { - manifest.validationFunctions[i] = ManifestAssociatedFunction({ - executionSelector: manifest.executionFunctions[i], - associatedFunction: alwaysAllowValidationFunction - }); + manifest.executionFunctions[i].isPublic = true; } // Request permission only for "foo", but not "bar", from ResultCreatorPlugin @@ -198,18 +185,9 @@ contract EFPCallerPluginAnyExternal is BasePlugin { function pluginManifest() external pure override returns (PluginManifest memory) { PluginManifest memory manifest; - manifest.executionFunctions = new bytes4[](1); - manifest.executionFunctions[0] = this.passthroughExecute.selector; - - manifest.validationFunctions = new ManifestAssociatedFunction[](1); - manifest.validationFunctions[0] = ManifestAssociatedFunction({ - executionSelector: this.passthroughExecute.selector, - associatedFunction: ManifestFunction({ - functionType: ManifestAssociatedFunctionType.RUNTIME_VALIDATION_ALWAYS_ALLOW, - functionId: 0, - dependencyIndex: 0 - }) - }); + manifest.executionFunctions = new ManifestExecutionFunction[](1); + manifest.executionFunctions[0] = + ManifestExecutionFunction({executionSelector: this.passthroughExecute.selector, isPublic: true}); manifest.permitAnyExternalAddress = true; diff --git a/test/mocks/plugins/ManifestValidityMocks.sol b/test/mocks/plugins/ManifestValidityMocks.sol index 2ea94f3c..302226c0 100644 --- a/test/mocks/plugins/ManifestValidityMocks.sol +++ b/test/mocks/plugins/ManifestValidityMocks.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.19; import { ManifestFunction, + ManifestExecutionFunction, ManifestAssociatedFunctionType, ManifestAssociatedFunction, PluginManifest, @@ -12,7 +13,7 @@ import { import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; // solhint-disable-next-line contract-name-camelcase -contract BadValidationMagicValue_PreValidationHook_Plugin is BasePlugin { +contract BadHookMagicValue_ValidationFunction_Plugin is BasePlugin { function onInstall(bytes calldata) external override {} function onUninstall(bytes calldata) external override {} @@ -24,83 +25,9 @@ contract BadValidationMagicValue_PreValidationHook_Plugin is BasePlugin { function pluginManifest() external pure override returns (PluginManifest memory) { PluginManifest memory manifest; - manifest.executionFunctions = new bytes4[](1); - manifest.executionFunctions[0] = this.foo.selector; - - manifest.validationFunctions = new ManifestAssociatedFunction[](1); - manifest.validationFunctions[0] = ManifestAssociatedFunction({ - executionSelector: this.foo.selector, - associatedFunction: ManifestFunction({ - functionType: ManifestAssociatedFunctionType.SELF, - functionId: 0, - dependencyIndex: 0 - }) - }); - - manifest.preValidationHooks = new ManifestAssociatedFunction[](1); - // Illegal assignment: validation always allow only usable on runtime validation functions - manifest.preValidationHooks[0] = ManifestAssociatedFunction({ - executionSelector: this.foo.selector, - associatedFunction: ManifestFunction({ - functionType: ManifestAssociatedFunctionType.RUNTIME_VALIDATION_ALWAYS_ALLOW, - functionId: 0, - dependencyIndex: 0 - }) - }); - - return manifest; - } - - function pluginMetadata() external pure override returns (PluginMetadata memory) {} -} - -// solhint-disable-next-line contract-name-camelcase -contract BadHookMagicValue_UserOpValidationFunction_Plugin is BasePlugin { - function onInstall(bytes calldata) external override {} - - function onUninstall(bytes calldata) external override {} - - function foo() external pure returns (bytes32) { - return keccak256("bar"); - } - - function pluginManifest() external pure override returns (PluginManifest memory) { - PluginManifest memory manifest; - - manifest.executionFunctions = new bytes4[](1); - manifest.executionFunctions[0] = this.foo.selector; - - manifest.validationFunctions = new ManifestAssociatedFunction[](1); - manifest.validationFunctions[0] = ManifestAssociatedFunction({ - executionSelector: this.foo.selector, - associatedFunction: ManifestFunction({ - functionType: ManifestAssociatedFunctionType.PRE_HOOK_ALWAYS_DENY, - functionId: 0, - dependencyIndex: 0 - }) - }); - - return manifest; - } - - function pluginMetadata() external pure override returns (PluginMetadata memory) {} -} - -// solhint-disable-next-line contract-name-camelcase -contract BadHookMagicValue_RuntimeValidationFunction_Plugin is BasePlugin { - function onInstall(bytes calldata) external override {} - - function onUninstall(bytes calldata) external override {} - - function foo() external pure returns (bytes32) { - return keccak256("bar"); - } - - function pluginManifest() external pure override returns (PluginManifest memory) { - PluginManifest memory manifest; - - manifest.executionFunctions = new bytes4[](1); - manifest.executionFunctions[0] = this.foo.selector; + manifest.executionFunctions = new ManifestExecutionFunction[](1); + manifest.executionFunctions[0] = + ManifestExecutionFunction({executionSelector: this.foo.selector, isPublic: false}); manifest.validationFunctions = new ManifestAssociatedFunction[](1); manifest.validationFunctions[0] = ManifestAssociatedFunction({ diff --git a/test/mocks/plugins/ReturnDataPluginMocks.sol b/test/mocks/plugins/ReturnDataPluginMocks.sol index 766bb903..788cb4c7 100644 --- a/test/mocks/plugins/ReturnDataPluginMocks.sol +++ b/test/mocks/plugins/ReturnDataPluginMocks.sol @@ -2,9 +2,7 @@ pragma solidity ^0.8.19; import { - ManifestFunction, - ManifestAssociatedFunctionType, - ManifestAssociatedFunction, + ManifestExecutionFunction, ManifestExternalCallPermission, PluginManifest, PluginMetadata @@ -39,19 +37,11 @@ contract ResultCreatorPlugin is BasePlugin { function pluginManifest() external pure override returns (PluginManifest memory) { PluginManifest memory manifest; - manifest.executionFunctions = new bytes4[](2); - manifest.executionFunctions[0] = this.foo.selector; - manifest.executionFunctions[1] = this.bar.selector; - - manifest.validationFunctions = new ManifestAssociatedFunction[](1); - manifest.validationFunctions[0] = ManifestAssociatedFunction({ - executionSelector: this.foo.selector, - associatedFunction: ManifestFunction({ - functionType: ManifestAssociatedFunctionType.RUNTIME_VALIDATION_ALWAYS_ALLOW, - functionId: 0, - dependencyIndex: 0 - }) - }); + manifest.executionFunctions = new ManifestExecutionFunction[](2); + manifest.executionFunctions[0] = + ManifestExecutionFunction({executionSelector: this.foo.selector, isPublic: true}); + manifest.executionFunctions[1] = + ManifestExecutionFunction({executionSelector: this.bar.selector, isPublic: false}); return manifest; } @@ -117,27 +107,11 @@ contract ResultConsumerPlugin is BasePlugin { function _innerPluginManifest() internal view returns (PluginManifest memory) { PluginManifest memory manifest; - manifest.executionFunctions = new bytes4[](2); - manifest.executionFunctions[0] = this.checkResultEFPFallback.selector; - manifest.executionFunctions[1] = this.checkResultEFPExternal.selector; - - manifest.validationFunctions = new ManifestAssociatedFunction[](2); - manifest.validationFunctions[0] = ManifestAssociatedFunction({ - executionSelector: this.checkResultEFPFallback.selector, - associatedFunction: ManifestFunction({ - functionType: ManifestAssociatedFunctionType.RUNTIME_VALIDATION_ALWAYS_ALLOW, - functionId: 0, - dependencyIndex: 0 - }) - }); - manifest.validationFunctions[1] = ManifestAssociatedFunction({ - executionSelector: this.checkResultEFPExternal.selector, - associatedFunction: ManifestFunction({ - functionType: ManifestAssociatedFunctionType.RUNTIME_VALIDATION_ALWAYS_ALLOW, - functionId: 0, - dependencyIndex: 0 - }) - }); + manifest.executionFunctions = new ManifestExecutionFunction[](2); + manifest.executionFunctions[0] = + ManifestExecutionFunction({executionSelector: this.checkResultEFPFallback.selector, isPublic: true}); + manifest.executionFunctions[1] = + ManifestExecutionFunction({executionSelector: this.checkResultEFPExternal.selector, isPublic: true}); manifest.permittedExecutionSelectors = new bytes4[](1); manifest.permittedExecutionSelectors[0] = ResultCreatorPlugin.foo.selector; diff --git a/test/mocks/plugins/ValidationPluginMocks.sol b/test/mocks/plugins/ValidationPluginMocks.sol index 9e7909df..554f589f 100644 --- a/test/mocks/plugins/ValidationPluginMocks.sol +++ b/test/mocks/plugins/ValidationPluginMocks.sol @@ -5,6 +5,7 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import { ManifestFunction, + ManifestExecutionFunction, ManifestAssociatedFunctionType, ManifestAssociatedFunction, PluginMetadata, @@ -93,8 +94,9 @@ contract MockUserOpValidationPlugin is MockBaseUserOpValidationPlugin { function pluginManifest() external pure override returns (PluginManifest memory) { PluginManifest memory manifest; - manifest.executionFunctions = new bytes4[](1); - manifest.executionFunctions[0] = this.foo.selector; + manifest.executionFunctions = new ManifestExecutionFunction[](1); + manifest.executionFunctions[0] = + ManifestExecutionFunction({executionSelector: this.foo.selector, isPublic: false}); manifest.validationFunctions = new ManifestAssociatedFunction[](1); manifest.validationFunctions[0] = ManifestAssociatedFunction({ @@ -131,8 +133,9 @@ contract MockUserOpValidation1HookPlugin is MockBaseUserOpValidationPlugin { function pluginManifest() external pure override returns (PluginManifest memory) { PluginManifest memory manifest; - manifest.executionFunctions = new bytes4[](1); - manifest.executionFunctions[0] = this.bar.selector; + manifest.executionFunctions = new ManifestExecutionFunction[](1); + manifest.executionFunctions[0] = + ManifestExecutionFunction({executionSelector: this.bar.selector, isPublic: false}); ManifestFunction memory userOpValidationFunctionRef = ManifestFunction({ functionType: ManifestAssociatedFunctionType.SELF, @@ -183,8 +186,9 @@ contract MockUserOpValidation2HookPlugin is MockBaseUserOpValidationPlugin { function pluginManifest() external pure override returns (PluginManifest memory) { PluginManifest memory manifest; - manifest.executionFunctions = new bytes4[](1); - manifest.executionFunctions[0] = this.baz.selector; + manifest.executionFunctions = new ManifestExecutionFunction[](1); + manifest.executionFunctions[0] = + ManifestExecutionFunction({executionSelector: this.baz.selector, isPublic: false}); ManifestFunction memory userOpValidationFunctionRef = ManifestFunction({ functionType: ManifestAssociatedFunctionType.SELF, From 3d54094959fbc7d178a3d6390bd13c19106c34f7 Mon Sep 17 00:00:00 2001 From: Adam Egyed <5456061+adamegyed@users.noreply.github.com> Date: Mon, 10 Jun 2024 14:43:41 -0400 Subject: [PATCH 016/111] feat: [v0.8-develop] multi validation in user op signature (#62) --- src/account/AccountLoupe.sol | 37 +++--- src/account/AccountStorage.sol | 2 +- src/account/PluginManagerInternals.sol | 11 +- src/account/UpgradeableModularAccount.sol | 124 +++++++++++++----- src/interfaces/IAccountLoupe.sol | 17 ++- src/interfaces/IPluginExecutor.sol | 10 ++ src/interfaces/IValidation.sol | 8 +- src/plugins/owner/ISingleOwnerPlugin.sol | 2 +- src/plugins/owner/SingleOwnerPlugin.sol | 12 +- test/account/AccountExecHooks.t.sol | 4 + test/account/AccountLoupe.t.sol | 54 ++++---- test/account/AccountReturnData.t.sol | 21 ++- .../ExecuteFromPluginPermissions.t.sol | 3 + test/account/ManifestValidity.t.sol | 1 + test/account/MultiValidation.t.sol | 119 +++++++++++++++++ test/account/UpgradeableModularAccount.t.sol | 42 +++--- test/account/ValidationIntersection.t.sol | 33 ++++- test/mocks/plugins/ComprehensivePlugin.sol | 6 +- test/mocks/plugins/ValidationPluginMocks.sol | 2 +- test/plugin/SingleOwnerPlugin.t.sol | 13 +- test/plugin/TokenReceiverPlugin.t.sol | 7 +- test/utils/AccountTestBase.sol | 13 +- 22 files changed, 400 insertions(+), 141 deletions(-) create mode 100644 test/account/MultiValidation.t.sol diff --git a/src/account/AccountLoupe.sol b/src/account/AccountLoupe.sol index ca4da1dd..9e9053fe 100644 --- a/src/account/AccountLoupe.sol +++ b/src/account/AccountLoupe.sol @@ -7,42 +7,38 @@ import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet import {IAccountLoupe, ExecutionHook} from "../interfaces/IAccountLoupe.sol"; import {FunctionReference, IPluginManager} from "../interfaces/IPluginManager.sol"; import {IStandardExecutor} from "../interfaces/IStandardExecutor.sol"; -import { - AccountStorage, - getAccountStorage, - SelectorData, - toFunctionReferenceArray, - toExecutionHook -} from "./AccountStorage.sol"; +import {getAccountStorage, SelectorData, toFunctionReferenceArray, toExecutionHook} from "./AccountStorage.sol"; abstract contract AccountLoupe is IAccountLoupe { using EnumerableSet for EnumerableSet.Bytes32Set; using EnumerableSet for EnumerableSet.AddressSet; /// @inheritdoc IAccountLoupe - function getExecutionFunctionConfig(bytes4 selector) - external - view - returns (ExecutionFunctionConfig memory config) - { - AccountStorage storage _storage = getAccountStorage(); - + function getExecutionFunctionHandler(bytes4 selector) external view override returns (address plugin) { if ( selector == IStandardExecutor.execute.selector || selector == IStandardExecutor.executeBatch.selector || selector == UUPSUpgradeable.upgradeToAndCall.selector || selector == IPluginManager.installPlugin.selector || selector == IPluginManager.uninstallPlugin.selector ) { - config.plugin = address(this); - } else { - config.plugin = _storage.selectorData[selector].plugin; + return address(this); } - config.validationFunction = _storage.selectorData[selector].validation; + return getAccountStorage().selectorData[selector].plugin; + } + + /// @inheritdoc IAccountLoupe + function getValidations(bytes4 selector) external view override returns (FunctionReference[] memory) { + return toFunctionReferenceArray(getAccountStorage().selectorData[selector].validations); } /// @inheritdoc IAccountLoupe - function getExecutionHooks(bytes4 selector) external view returns (ExecutionHook[] memory execHooks) { + function getExecutionHooks(bytes4 selector) + external + view + override + returns (ExecutionHook[] memory execHooks) + { SelectorData storage selectorData = getAccountStorage().selectorData[selector]; uint256 executionHooksLength = selectorData.executionHooks.length(); @@ -59,6 +55,7 @@ abstract contract AccountLoupe is IAccountLoupe { function getPreValidationHooks(bytes4 selector) external view + override returns (FunctionReference[] memory preValidationHooks) { preValidationHooks = @@ -66,7 +63,7 @@ abstract contract AccountLoupe is IAccountLoupe { } /// @inheritdoc IAccountLoupe - function getInstalledPlugins() external view returns (address[] memory pluginAddresses) { + function getInstalledPlugins() external view override returns (address[] memory pluginAddresses) { pluginAddresses = getAccountStorage().plugins.values(); } } diff --git a/src/account/AccountStorage.sol b/src/account/AccountStorage.sol index 205af107..4319f470 100644 --- a/src/account/AccountStorage.sol +++ b/src/account/AccountStorage.sol @@ -45,7 +45,7 @@ struct SelectorData { // but it packs alongside `plugin` while still leaving some other space in the slot for future packing. uint48 denyExecutionCount; // User operation validation and runtime validation share a function reference. - FunctionReference validation; + EnumerableSet.Bytes32Set validations; // The pre validation hooks for this function selector. EnumerableSet.Bytes32Set preValidationHooks; // The execution hooks for this function selector. diff --git a/src/account/PluginManagerInternals.sol b/src/account/PluginManagerInternals.sol index 7ded3176..cfdbef16 100644 --- a/src/account/PluginManagerInternals.sol +++ b/src/account/PluginManagerInternals.sol @@ -86,11 +86,12 @@ abstract contract PluginManagerInternals is IPluginManager { { SelectorData storage _selectorData = getAccountStorage().selectorData[selector]; - if (_selectorData.validation.notEmpty()) { + // Fail on duplicate validation functions. Otherwise, dependency validation functions could shadow + // non-depdency validation functions. Then, if a either plugin is uninstall, it would cause a partial + // uninstall of the other. + if (!_selectorData.validations.add(toSetValue(validationFunction))) { revert ValidationFunctionAlreadySet(selector, validationFunction); } - - _selectorData.validation = validationFunction; } function _removeValidationFunction(bytes4 selector, FunctionReference validationFunction) @@ -99,7 +100,9 @@ abstract contract PluginManagerInternals is IPluginManager { { SelectorData storage _selectorData = getAccountStorage().selectorData[selector]; - _selectorData.validation = FunctionReferenceLib._EMPTY_FUNCTION_REFERENCE; + // May ignore return value, as the manifest hash is validated to ensure that the validation function + // exists. + _selectorData.validations.remove(toSetValue(validationFunction)); } function _addExecHooks( diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 1cd0d98e..69a11cbd 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -81,7 +81,7 @@ contract UpgradeableModularAccount is // Wraps execution of a native function with runtime validation and hooks // Used for upgradeTo, upgradeToAndCall, execute, executeBatch, installPlugin, uninstallPlugin modifier wrapNativeFunction() { - _doRuntimeValidationIfNotFromEP(); + _checkPermittedCallerIfNotFromEP(); PostExecToRun[] memory postExecHooks = _doPreExecHooks(msg.sig, msg.data); @@ -133,7 +133,7 @@ contract UpgradeableModularAccount is revert UnrecognizedFunction(msg.sig); } - _doRuntimeValidationIfNotFromEP(); + _checkPermittedCallerIfNotFromEP(); PostExecToRun[] memory postExecHooks; // Cache post-exec hooks in memory @@ -262,6 +262,42 @@ contract UpgradeableModularAccount is return returnData; } + /// @inheritdoc IPluginExecutor + function executeWithAuthorization(bytes calldata data, bytes calldata authorization) + external + payable + returns (bytes memory) + { + bytes4 execSelector = bytes4(data[:4]); + + // Revert if the provided `authorization` less than 21 bytes long, rather than right-padding. + FunctionReference runtimeValidationFunction = FunctionReference.wrap(bytes21(authorization[:21])); + + AccountStorage storage _storage = getAccountStorage(); + + // check if that runtime validation function is allowed to be called + if (_storage.selectorData[execSelector].denyExecutionCount > 0) { + revert AlwaysDenyRule(); + } + if (!_storage.selectorData[execSelector].validations.contains(toSetValue(runtimeValidationFunction))) { + revert RuntimeValidationFunctionMissing(execSelector); + } + + _doRuntimeValidation(runtimeValidationFunction, data, authorization[21:]); + + // If runtime validation passes, execute the call + + (bool success, bytes memory returnData) = address(this).call(data); + + if (!success) { + assembly ("memory-safe") { + revert(add(returnData, 32), mload(returnData)) + } + } + + return returnData; + } + /// @inheritdoc IPluginManager function installPlugin( address plugin, @@ -360,18 +396,28 @@ contract UpgradeableModularAccount is revert AlwaysDenyRule(); } - FunctionReference userOpValidationFunction = getAccountStorage().selectorData[selector].validation; + // Revert if the provided `authorization` less than 21 bytes long, rather than right-padding. + FunctionReference userOpValidationFunction = FunctionReference.wrap(bytes21(userOp.signature[:21])); - validationData = _doUserOpValidation(selector, userOpValidationFunction, userOp, userOpHash); + if (!getAccountStorage().selectorData[selector].validations.contains(toSetValue(userOpValidationFunction))) + { + revert UserOpValidationFunctionMissing(selector); + } + + validationData = + _doUserOpValidation(selector, userOpValidationFunction, userOp, userOp.signature[21:], userOpHash); } // To support gas estimation, we don't fail early when the failure is caused by a signature failure function _doUserOpValidation( bytes4 selector, FunctionReference userOpValidationFunction, - PackedUserOperation calldata userOp, + PackedUserOperation memory userOp, + bytes calldata signature, bytes32 userOpHash ) internal returns (uint256 validationData) { + userOp.signature = signature; + if (userOpValidationFunction.isEmpty()) { // If the validation function is empty, then the call cannot proceed. revert UserOpValidationFunctionMissing(selector); @@ -412,50 +458,40 @@ contract UpgradeableModularAccount is } } - function _doRuntimeValidationIfNotFromEP() internal { - AccountStorage storage _storage = getAccountStorage(); - - if (_storage.selectorData[msg.sig].denyExecutionCount > 0) { - revert AlwaysDenyRule(); - } - - if (msg.sender == address(_ENTRY_POINT)) return; - - FunctionReference runtimeValidationFunction = _storage.selectorData[msg.sig].validation; + function _doRuntimeValidation( + FunctionReference runtimeValidationFunction, + bytes calldata callData, + bytes calldata authorizationData + ) internal { // run all preRuntimeValidation hooks EnumerableSet.Bytes32Set storage preRuntimeValidationHooks = - getAccountStorage().selectorData[msg.sig].preValidationHooks; + getAccountStorage().selectorData[bytes4(callData[:4])].preValidationHooks; uint256 preRuntimeValidationHooksLength = preRuntimeValidationHooks.length(); for (uint256 i = 0; i < preRuntimeValidationHooksLength; ++i) { bytes32 key = preRuntimeValidationHooks.at(i); FunctionReference preRuntimeValidationHook = toFunctionReference(key); - (address plugin, uint8 functionId) = preRuntimeValidationHook.unpack(); + (address hookPlugin, uint8 hookFunctionId) = preRuntimeValidationHook.unpack(); + try IValidationHook(hookPlugin).preRuntimeValidationHook( + hookFunctionId, msg.sender, msg.value, callData + ) + // forgefmt: disable-start // solhint-disable-next-line no-empty-blocks - try IValidationHook(plugin).preRuntimeValidationHook(functionId, msg.sender, msg.value, msg.data) {} - catch (bytes memory revertReason) { - revert PreRuntimeValidationHookFailed(plugin, functionId, revertReason); + {} catch (bytes memory revertReason) { + // forgefmt: disable-end + revert PreRuntimeValidationHookFailed(hookPlugin, hookFunctionId, revertReason); } } - // Identifier scope limiting - { - if (_storage.selectorData[msg.sig].isPublic) { - // If the function is public, we don't need to check the runtime validation function. - return; - } - - if (runtimeValidationFunction.isEmpty()) { - revert RuntimeValidationFunctionMissing(msg.sig); - } + (address plugin, uint8 functionId) = runtimeValidationFunction.unpack(); - (address plugin, uint8 functionId) = runtimeValidationFunction.unpack(); - // solhint-disable-next-line no-empty-blocks - try IValidation(plugin).validateRuntime(functionId, msg.sender, msg.value, msg.data) {} - catch (bytes memory revertReason) { - revert RuntimeValidationFunctionReverted(plugin, functionId, revertReason); - } + try IValidation(plugin).validateRuntime(functionId, msg.sender, msg.value, callData, authorizationData) + // forgefmt: disable-start + // solhint-disable-next-line no-empty-blocks + {} catch (bytes memory revertReason) { + // forgefmt: disable-end + revert RuntimeValidationFunctionReverted(plugin, functionId, revertReason); } } @@ -536,4 +572,20 @@ contract UpgradeableModularAccount is // solhint-disable-next-line no-empty-blocks function _authorizeUpgrade(address newImplementation) internal override {} + + function _checkPermittedCallerIfNotFromEP() internal view { + AccountStorage storage _storage = getAccountStorage(); + + if (_storage.selectorData[msg.sig].denyExecutionCount > 0) { + revert AlwaysDenyRule(); + } + if ( + msg.sender == address(_ENTRY_POINT) || msg.sender == address(this) + || _storage.selectorData[msg.sig].isPublic + ) return; + + if (!_storage.callPermitted[msg.sender][msg.sig]) { + revert ExecFromPluginNotPermitted(msg.sender, msg.sig); + } + } } diff --git a/src/interfaces/IAccountLoupe.sol b/src/interfaces/IAccountLoupe.sol index a1b3c15f..b474149c 100644 --- a/src/interfaces/IAccountLoupe.sol +++ b/src/interfaces/IAccountLoupe.sol @@ -12,17 +12,16 @@ struct ExecutionHook { } interface IAccountLoupe { - /// @notice Config for an execution function, given a selector. - struct ExecutionFunctionConfig { - address plugin; - FunctionReference validationFunction; - } - - /// @notice Get the validation functions and plugin address for a selector. + /// @notice Get the plugin address for a selector. /// @dev If the selector is a native function, the plugin address will be the address of the account. /// @param selector The selector to get the configuration for. - /// @return The configuration for this selector. - function getExecutionFunctionConfig(bytes4 selector) external view returns (ExecutionFunctionConfig memory); + /// @return plugin The plugin address for this selector. + function getExecutionFunctionHandler(bytes4 selector) external view returns (address plugin); + + /// @notice Get the validation functions for a selector. + /// @param selector The selector to get the validation functions for. + /// @return The validation functions for this selector. + function getValidations(bytes4 selector) external view returns (FunctionReference[] memory); /// @notice Get the pre and post execution hooks for a selector. /// @param selector The selector to get the hooks for. diff --git a/src/interfaces/IPluginExecutor.sol b/src/interfaces/IPluginExecutor.sol index e1989958..2ff5ed11 100644 --- a/src/interfaces/IPluginExecutor.sol +++ b/src/interfaces/IPluginExecutor.sol @@ -20,4 +20,14 @@ interface IPluginExecutor { external payable returns (bytes memory); + + /// @notice Execute a call using a specified runtime validation, as given in the first 21 bytes of + /// `authorization`. + /// @param data The calldata to send to the account. + /// @param authorization The authorization data to use for the call. The first 21 bytes specifies which runtime + /// validation to use, and the rest is sent as a parameter to runtime validation. + function executeWithAuthorization(bytes calldata data, bytes calldata authorization) + external + payable + returns (bytes memory); } diff --git a/src/interfaces/IValidation.sol b/src/interfaces/IValidation.sol index b4edddcc..b3adcd3d 100644 --- a/src/interfaces/IValidation.sol +++ b/src/interfaces/IValidation.sol @@ -23,7 +23,13 @@ interface IValidation is IPlugin { /// @param sender The caller address. /// @param value The call value. /// @param data The calldata sent. - function validateRuntime(uint8 functionId, address sender, uint256 value, bytes calldata data) external; + function validateRuntime( + uint8 functionId, + 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. diff --git a/src/plugins/owner/ISingleOwnerPlugin.sol b/src/plugins/owner/ISingleOwnerPlugin.sol index 6d80eb50..57bcac80 100644 --- a/src/plugins/owner/ISingleOwnerPlugin.sol +++ b/src/plugins/owner/ISingleOwnerPlugin.sol @@ -5,7 +5,7 @@ import {IValidation} from "../../interfaces/IValidation.sol"; interface ISingleOwnerPlugin is IValidation { enum FunctionId { - VALIDATION_OWNER_OR_SELF, + VALIDATION_OWNER, SIG_VALIDATION } diff --git a/src/plugins/owner/SingleOwnerPlugin.sol b/src/plugins/owner/SingleOwnerPlugin.sol index bebbad21..dbdd41b2 100644 --- a/src/plugins/owner/SingleOwnerPlugin.sol +++ b/src/plugins/owner/SingleOwnerPlugin.sol @@ -79,8 +79,12 @@ contract SingleOwnerPlugin is ISingleOwnerPlugin, BasePlugin { } /// @inheritdoc IValidation - function validateRuntime(uint8 functionId, address sender, uint256, bytes calldata) external view override { - if (functionId == uint8(FunctionId.VALIDATION_OWNER_OR_SELF)) { + function validateRuntime(uint8 functionId, address sender, uint256, bytes calldata, bytes calldata) + external + view + override + { + if (functionId == uint8(FunctionId.VALIDATION_OWNER)) { // Validate that the sender is the owner of the account or self. if (sender != _owners[msg.sender] && sender != msg.sender) { revert NotAuthorized(); @@ -97,7 +101,7 @@ contract SingleOwnerPlugin is ISingleOwnerPlugin, BasePlugin { override returns (uint256) { - if (functionId == uint8(FunctionId.VALIDATION_OWNER_OR_SELF)) { + if (functionId == uint8(FunctionId.VALIDATION_OWNER)) { // Validate the user op signature against the owner. (address signer,,) = (userOpHash.toEthSignedMessageHash()).tryRecover(userOp.signature); if (signer == address(0) || signer != _owners[msg.sender]) { @@ -154,7 +158,7 @@ contract SingleOwnerPlugin is ISingleOwnerPlugin, BasePlugin { ManifestFunction memory ownerValidationFunction = ManifestFunction({ functionType: ManifestAssociatedFunctionType.SELF, - functionId: uint8(FunctionId.VALIDATION_OWNER_OR_SELF), + functionId: uint8(FunctionId.VALIDATION_OWNER), dependencyIndex: 0 // Unused. }); manifest.validationFunctions = new ManifestAssociatedFunction[](5); diff --git a/test/account/AccountExecHooks.t.sol b/test/account/AccountExecHooks.t.sol index 3605ddd5..ee229e3e 100644 --- a/test/account/AccountExecHooks.t.sol +++ b/test/account/AccountExecHooks.t.sol @@ -228,6 +228,7 @@ contract AccountExecHooksTest is AccountTestBase { vm.expectEmit(true, true, true, true); emit PluginInstalled(address(mockPlugin1), manifestHash1, new FunctionReference[](0)); + vm.prank(address(entryPoint)); account1.installPlugin({ plugin: address(mockPlugin1), manifestHash: manifestHash1, @@ -251,6 +252,7 @@ contract AccountExecHooksTest is AccountTestBase { vm.expectEmit(true, true, true, true); emit PluginInstalled(address(mockPlugin1), manifestHash1, new FunctionReference[](0)); + vm.prank(address(entryPoint)); account1.installPlugin({ plugin: address(mockPlugin1), manifestHash: manifestHash1, @@ -274,6 +276,7 @@ contract AccountExecHooksTest is AccountTestBase { vm.expectEmit(true, true, true, true); emit PluginInstalled(address(mockPlugin2), manifestHash2, new FunctionReference[](0)); + vm.prank(address(entryPoint)); account1.installPlugin({ plugin: address(mockPlugin2), manifestHash: manifestHash2, @@ -288,6 +291,7 @@ contract AccountExecHooksTest is AccountTestBase { vm.expectEmit(true, true, true, true); emit PluginUninstalled(address(plugin), true); + vm.prank(address(entryPoint)); account1.uninstallPlugin(address(plugin), bytes(""), bytes("")); } } diff --git a/test/account/AccountLoupe.t.sol b/test/account/AccountLoupe.t.sol index 34c914a7..a6ed44eb 100644 --- a/test/account/AccountLoupe.t.sol +++ b/test/account/AccountLoupe.t.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.19; import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; -import {IAccountLoupe, ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; +import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; import {IPluginManager} from "../../src/interfaces/IPluginManager.sol"; import {IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; import {ISingleOwnerPlugin} from "../../src/plugins/owner/ISingleOwnerPlugin.sol"; @@ -25,10 +25,11 @@ contract AccountLoupeTest is AccountTestBase { comprehensivePlugin = new ComprehensivePlugin(); bytes32 manifestHash = keccak256(abi.encode(comprehensivePlugin.pluginManifest())); + vm.prank(address(entryPoint)); account1.installPlugin(address(comprehensivePlugin), manifestHash, "", new FunctionReference[](0)); ownerValidation = FunctionReferenceLib.pack( - address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER_OR_SELF) + address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) ); } @@ -41,60 +42,59 @@ contract AccountLoupeTest is AccountTestBase { assertEq(plugins[1], address(comprehensivePlugin)); } - function test_pluginLoupe_getExecutionFunctionConfig_native() public { + function test_pluginLoupe_getExecutionFunctionHandler_native() public { bytes4[] memory selectorsToCheck = new bytes4[](5); - FunctionReference[] memory expectedValidations = new FunctionReference[](5); selectorsToCheck[0] = IStandardExecutor.execute.selector; - expectedValidations[0] = ownerValidation; selectorsToCheck[1] = IStandardExecutor.executeBatch.selector; - expectedValidations[1] = ownerValidation; selectorsToCheck[2] = UUPSUpgradeable.upgradeToAndCall.selector; - expectedValidations[2] = ownerValidation; selectorsToCheck[3] = IPluginManager.installPlugin.selector; - expectedValidations[3] = ownerValidation; selectorsToCheck[4] = IPluginManager.uninstallPlugin.selector; - expectedValidations[4] = ownerValidation; for (uint256 i = 0; i < selectorsToCheck.length; i++) { - IAccountLoupe.ExecutionFunctionConfig memory config = - account1.getExecutionFunctionConfig(selectorsToCheck[i]); + address plugin = account1.getExecutionFunctionHandler(selectorsToCheck[i]); - assertEq(config.plugin, address(account1)); - assertEq( - FunctionReference.unwrap(config.validationFunction), - FunctionReference.unwrap(expectedValidations[i]) - ); + assertEq(plugin, address(account1)); } } function test_pluginLoupe_getExecutionFunctionConfig_plugin() public { bytes4[] memory selectorsToCheck = new bytes4[](1); address[] memory expectedPluginAddress = new address[](1); - FunctionReference[] memory expectedValidations = new FunctionReference[](1); selectorsToCheck[0] = comprehensivePlugin.foo.selector; expectedPluginAddress[0] = address(comprehensivePlugin); - expectedValidations[0] = FunctionReferenceLib.pack( - address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.VALIDATION) - ); for (uint256 i = 0; i < selectorsToCheck.length; i++) { - IAccountLoupe.ExecutionFunctionConfig memory config = - account1.getExecutionFunctionConfig(selectorsToCheck[i]); + address plugin = account1.getExecutionFunctionHandler(selectorsToCheck[i]); - assertEq(config.plugin, expectedPluginAddress[i]); - assertEq( - FunctionReference.unwrap(config.validationFunction), - FunctionReference.unwrap(expectedValidations[i]) - ); + assertEq(plugin, expectedPluginAddress[i]); } } + function test_pluginLoupe_getValidationFunctions() public { + FunctionReference[] memory validations = account1.getValidations(comprehensivePlugin.foo.selector); + + assertEq(validations.length, 1); + assertEq( + FunctionReference.unwrap(validations[0]), + FunctionReference.unwrap( + FunctionReferenceLib.pack( + address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.VALIDATION) + ) + ) + ); + + validations = account1.getValidations(account1.execute.selector); + + assertEq(validations.length, 1); + assertEq(FunctionReference.unwrap(validations[0]), FunctionReference.unwrap(ownerValidation)); + } + function test_pluginLoupe_getExecutionHooks() public { ExecutionHook[] memory hooks = account1.getExecutionHooks(comprehensivePlugin.foo.selector); ExecutionHook[3] memory expectedHooks = [ diff --git a/test/account/AccountReturnData.t.sol b/test/account/AccountReturnData.t.sol index 46463ed1..085fa4bf 100644 --- a/test/account/AccountReturnData.t.sol +++ b/test/account/AccountReturnData.t.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.19; import {FunctionReference} from "../../src/helpers/FunctionReferenceLib.sol"; import {Call} from "../../src/interfaces/IStandardExecutor.sol"; +import {ISingleOwnerPlugin} from "../../src/plugins/owner/ISingleOwnerPlugin.sol"; import { RegularResultContract, @@ -26,6 +27,7 @@ contract AccountReturnDataTest is AccountTestBase { // Add the result creator plugin to the account bytes32 resultCreatorManifestHash = keccak256(abi.encode(resultCreatorPlugin.pluginManifest())); + vm.prank(address(entryPoint)); account1.installPlugin({ plugin: address(resultCreatorPlugin), manifestHash: resultCreatorManifestHash, @@ -34,6 +36,7 @@ contract AccountReturnDataTest is AccountTestBase { }); // Add the result consumer plugin to the account bytes32 resultConsumerManifestHash = keccak256(abi.encode(resultConsumerPlugin.pluginManifest())); + vm.prank(address(entryPoint)); account1.installPlugin({ plugin: address(resultConsumerPlugin), manifestHash: resultConsumerManifestHash, @@ -51,10 +54,15 @@ contract AccountReturnDataTest is AccountTestBase { // Tests the ability to read the results of contracts called via IStandardExecutor.execute function test_returnData_singular_execute() public { - bytes memory returnData = - account1.execute(address(regularResultContract), 0, abi.encodeCall(RegularResultContract.foo, ())); + bytes memory returnData = account1.executeWithAuthorization( + abi.encodeCall( + account1.execute, + (address(regularResultContract), 0, abi.encodeCall(RegularResultContract.foo, ())) + ), + abi.encodePacked(singleOwnerPlugin, ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) + ); - bytes32 result = abi.decode(returnData, (bytes32)); + bytes32 result = abi.decode(abi.decode(returnData, (bytes)), (bytes32)); assertEq(result, keccak256("bar")); } @@ -73,7 +81,12 @@ contract AccountReturnDataTest is AccountTestBase { data: abi.encodeCall(RegularResultContract.bar, ()) }); - bytes[] memory returnDatas = account1.executeBatch(calls); + bytes memory retData = account1.executeWithAuthorization( + abi.encodeCall(account1.executeBatch, (calls)), + abi.encodePacked(singleOwnerPlugin, ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) + ); + + bytes[] memory returnDatas = abi.decode(retData, (bytes[])); bytes32 result1 = abi.decode(returnDatas[0], (bytes32)); bytes32 result2 = abi.decode(returnDatas[1], (bytes32)); diff --git a/test/account/ExecuteFromPluginPermissions.t.sol b/test/account/ExecuteFromPluginPermissions.t.sol index 1e9f17e2..6a95ba18 100644 --- a/test/account/ExecuteFromPluginPermissions.t.sol +++ b/test/account/ExecuteFromPluginPermissions.t.sol @@ -35,6 +35,7 @@ contract ExecuteFromPluginPermissionsTest is AccountTestBase { // Add the result creator plugin to the account bytes32 resultCreatorManifestHash = keccak256(abi.encode(resultCreatorPlugin.pluginManifest())); + vm.prank(address(entryPoint)); account1.installPlugin({ plugin: address(resultCreatorPlugin), manifestHash: resultCreatorManifestHash, @@ -43,6 +44,7 @@ contract ExecuteFromPluginPermissionsTest is AccountTestBase { }); // Add the EFP caller plugin to the account bytes32 efpCallerManifestHash = keccak256(abi.encode(efpCallerPlugin.pluginManifest())); + vm.prank(address(entryPoint)); account1.installPlugin({ plugin: address(efpCallerPlugin), manifestHash: efpCallerManifestHash, @@ -53,6 +55,7 @@ contract ExecuteFromPluginPermissionsTest is AccountTestBase { // Add the EFP caller plugin with any external permissions to the account bytes32 efpCallerAnyExternalManifestHash = keccak256(abi.encode(efpCallerPluginAnyExternal.pluginManifest())); + vm.prank(address(entryPoint)); account1.installPlugin({ plugin: address(efpCallerPluginAnyExternal), manifestHash: efpCallerAnyExternalManifestHash, diff --git a/test/account/ManifestValidity.t.sol b/test/account/ManifestValidity.t.sol index 8200a017..08c2609d 100644 --- a/test/account/ManifestValidity.t.sol +++ b/test/account/ManifestValidity.t.sol @@ -18,6 +18,7 @@ contract ManifestValidityTest is AccountTestBase { bytes32 manifestHash = keccak256(abi.encode(plugin.pluginManifest())); + vm.prank(address(entryPoint)); vm.expectRevert(abi.encodeWithSelector(PluginManagerInternals.InvalidPluginManifest.selector)); account1.installPlugin({ plugin: address(plugin), diff --git a/test/account/MultiValidation.t.sol b/test/account/MultiValidation.t.sol new file mode 100644 index 00000000..9ca70857 --- /dev/null +++ b/test/account/MultiValidation.t.sol @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.21; + +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; +import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; + +import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; + +import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; +import {FunctionReference} from "../../src/interfaces/IPluginManager.sol"; +import {IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; +import {FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; +import {SingleOwnerPlugin} from "../../src/plugins/owner/SingleOwnerPlugin.sol"; +import {ISingleOwnerPlugin} from "../../src/plugins/owner/ISingleOwnerPlugin.sol"; + +import {AccountTestBase} from "../utils/AccountTestBase.sol"; + +contract MultiValidationTest is AccountTestBase { + using ECDSA for bytes32; + using MessageHashUtils for bytes32; + + SingleOwnerPlugin public validator2; + + address public owner2; + uint256 public owner2Key; + + uint256 public constant CALL_GAS_LIMIT = 50000; + uint256 public constant VERIFICATION_GAS_LIMIT = 1200000; + + function setUp() public { + validator2 = new SingleOwnerPlugin(); + + (owner2, owner2Key) = makeAddrAndKey("owner2"); + } + + function test_overlappingValidationInstall() public { + bytes32 manifestHash = keccak256(abi.encode(validator2.pluginManifest())); + vm.prank(address(entryPoint)); + account1.installPlugin(address(validator2), manifestHash, abi.encode(owner2), new FunctionReference[](0)); + + FunctionReference[] memory validations = new FunctionReference[](2); + validations[0] = FunctionReferenceLib.pack( + address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) + ); + validations[1] = + FunctionReferenceLib.pack(address(validator2), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER)); + FunctionReference[] memory validations2 = account1.getValidations(IStandardExecutor.execute.selector); + assertEq(validations2.length, 2); + assertEq(FunctionReference.unwrap(validations2[0]), FunctionReference.unwrap(validations[0])); + assertEq(FunctionReference.unwrap(validations2[1]), FunctionReference.unwrap(validations[1])); + } + + function test_runtimeValidation_specify() public { + test_overlappingValidationInstall(); + + // Assert that the runtime validation can be specified. + + vm.prank(owner1); + vm.expectRevert( + abi.encodeWithSelector( + UpgradeableModularAccount.RuntimeValidationFunctionReverted.selector, + address(validator2), + 0, + abi.encodeWithSignature("NotAuthorized()") + ) + ); + account1.executeWithAuthorization( + abi.encodeCall(IStandardExecutor.execute, (address(0), 0, "")), + abi.encodePacked(address(validator2), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER)) + ); + + vm.prank(owner2); + account1.executeWithAuthorization( + abi.encodeCall(IStandardExecutor.execute, (address(0), 0, "")), + abi.encodePacked(address(validator2), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER)) + ); + } + + function test_userOpValidation_specify() public { + test_overlappingValidationInstall(); + + // Assert that the userOp validation can be specified. + + PackedUserOperation memory userOp = PackedUserOperation({ + sender: address(account1), + nonce: 0, + initCode: "", + callData: abi.encodeCall(UpgradeableModularAccount.execute, (address(0), 0, "")), + accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), + preVerificationGas: 0, + gasFees: _encodeGas(1, 1), + paymasterAndData: "", + signature: "" + }); + + // Generate signature + bytes32 userOpHash = entryPoint.getUserOpHash(userOp); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); + userOp.signature = + abi.encodePacked(address(validator2), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER), r, s, v); + + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); + userOps[0] = userOp; + + entryPoint.handleOps(userOps, beneficiary); + + // Sign with owner 1, expect fail + + userOp.nonce = 1; + (v, r, s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); + userOp.signature = + abi.encodePacked(address(validator2), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER), r, s, v); + + userOps[0] = userOp; + vm.expectRevert(abi.encodeWithSelector(IEntryPoint.FailedOp.selector, 0, "AA24 signature error")); + entryPoint.handleOps(userOps, beneficiary); + } +} diff --git a/test/account/UpgradeableModularAccount.t.sol b/test/account/UpgradeableModularAccount.t.sol index c5a061e0..1cbcb78d 100644 --- a/test/account/UpgradeableModularAccount.t.sol +++ b/test/account/UpgradeableModularAccount.t.sol @@ -10,7 +10,7 @@ import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; import {PluginManagerInternals} from "../../src/account/PluginManagerInternals.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {FunctionReference} from "../../src/helpers/FunctionReferenceLib.sol"; +import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; import {IPlugin, PluginManifest} from "../../src/interfaces/IPlugin.sol"; import {IAccountLoupe} from "../../src/interfaces/IAccountLoupe.sol"; import {IPluginManager} from "../../src/interfaces/IPluginManager.sol"; @@ -39,6 +39,8 @@ contract UpgradeableModularAccountTest is AccountTestBase { Counter public counter; PluginManifest public manifest; + FunctionReference public ownerValidation; + uint256 public constant CALL_GAS_LIMIT = 50000; uint256 public constant VERIFICATION_GAS_LIMIT = 1200000; @@ -59,6 +61,10 @@ contract UpgradeableModularAccountTest is AccountTestBase { vm.deal(ethRecipient, 1 wei); counter = new Counter(); counter.increment(); // amoritze away gas cost of zero->nonzero transition + + ownerValidation = FunctionReferenceLib.pack( + address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) + ); } function test_deployAccount() public { @@ -81,7 +87,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = abi.encodePacked(r, s, v); + userOp.signature = abi.encodePacked(ownerValidation, r, s, v); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -110,7 +116,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = abi.encodePacked(r, s, v); + userOp.signature = abi.encodePacked(ownerValidation, r, s, v); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -136,7 +142,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = abi.encodePacked(r, s, v); + userOp.signature = abi.encodePacked(ownerValidation, r, s, v); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -162,7 +168,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = abi.encodePacked(r, s, v); + userOp.signature = abi.encodePacked(ownerValidation, r, s, v); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -190,7 +196,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = abi.encodePacked(r, s, v); + userOp.signature = abi.encodePacked(ownerValidation, r, s, v); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -221,7 +227,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = abi.encodePacked(r, s, v); + userOp.signature = abi.encodePacked(ownerValidation, r, s, v); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -233,7 +239,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { } function test_installPlugin() public { - vm.startPrank(owner1); + vm.startPrank(address(entryPoint)); bytes32 manifestHash = keccak256(abi.encode(tokenReceiverPlugin.pluginManifest())); @@ -253,7 +259,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { } function test_installPlugin_ExecuteFromPlugin_PermittedExecSelectorNotInstalled() public { - vm.startPrank(owner1); + vm.startPrank(address(entryPoint)); PluginManifest memory m; m.permittedExecutionSelectors = new bytes4[](1); @@ -271,7 +277,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { } function test_installPlugin_invalidManifest() public { - vm.startPrank(owner1); + vm.startPrank(address(entryPoint)); vm.expectRevert(abi.encodeWithSelector(PluginManagerInternals.InvalidPluginManifest.selector)); IPluginManager(account1).installPlugin({ @@ -283,7 +289,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { } function test_installPlugin_interfaceNotSupported() public { - vm.startPrank(owner1); + vm.startPrank(address(entryPoint)); address badPlugin = address(1); vm.expectRevert( @@ -298,7 +304,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { } function test_installPlugin_alreadyInstalled() public { - vm.startPrank(owner1); + vm.startPrank(address(entryPoint)); bytes32 manifestHash = keccak256(abi.encode(tokenReceiverPlugin.pluginManifest())); IPluginManager(account1).installPlugin({ @@ -322,7 +328,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { } function test_uninstallPlugin_default() public { - vm.startPrank(owner1); + vm.startPrank(address(entryPoint)); ComprehensivePlugin plugin = new ComprehensivePlugin(); bytes32 manifestHash = keccak256(abi.encode(plugin.pluginManifest())); @@ -342,7 +348,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { } function test_uninstallPlugin_manifestParameter() public { - vm.startPrank(owner1); + vm.startPrank(address(entryPoint)); ComprehensivePlugin plugin = new ComprehensivePlugin(); bytes memory serializedManifest = abi.encode(plugin.pluginManifest()); @@ -367,7 +373,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { } function test_uninstallPlugin_invalidManifestFails() public { - vm.startPrank(owner1); + vm.startPrank(address(entryPoint)); ComprehensivePlugin plugin = new ComprehensivePlugin(); bytes memory serializedManifest = abi.encode(plugin.pluginManifest()); @@ -395,7 +401,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { } function _installPluginWithExecHooks() internal returns (MockPlugin plugin) { - vm.startPrank(owner2); + vm.startPrank(address(entryPoint)); plugin = new MockPlugin(manifest); bytes32 manifestHash = keccak256(abi.encode(plugin.pluginManifest())); @@ -411,7 +417,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { } function test_upgradeToAndCall() public { - vm.startPrank(owner1); + vm.startPrank(address(entryPoint)); UpgradeableModularAccount account3 = new UpgradeableModularAccount(entryPoint); bytes32 slot = account3.proxiableUUID(); @@ -427,7 +433,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { function test_transferOwnership() public { assertEq(singleOwnerPlugin.ownerOf(address(account1)), owner1); - vm.prank(owner1); + vm.prank(address(entryPoint)); account1.execute( address(singleOwnerPlugin), 0, abi.encodeCall(SingleOwnerPlugin.transferOwnership, (owner2)) ); diff --git a/test/account/ValidationIntersection.t.sol b/test/account/ValidationIntersection.t.sol index 7d451730..9c54e7b0 100644 --- a/test/account/ValidationIntersection.t.sol +++ b/test/account/ValidationIntersection.t.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.19; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {FunctionReference} from "../../src/helpers/FunctionReferenceLib.sol"; +import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; import { MockBaseUserOpValidationPlugin, @@ -21,12 +21,31 @@ contract ValidationIntersectionTest is AccountTestBase { MockUserOpValidation1HookPlugin public oneHookPlugin; MockUserOpValidation2HookPlugin public twoHookPlugin; + FunctionReference public noHookValidation; + FunctionReference public oneHookValidation; + FunctionReference public twoHookValidation; + function setUp() public { noHookPlugin = new MockUserOpValidationPlugin(); oneHookPlugin = new MockUserOpValidation1HookPlugin(); twoHookPlugin = new MockUserOpValidation2HookPlugin(); - vm.startPrank(address(owner1)); + noHookValidation = FunctionReferenceLib.pack({ + addr: address(noHookPlugin), + functionId: uint8(MockBaseUserOpValidationPlugin.FunctionId.USER_OP_VALIDATION) + }); + + oneHookValidation = FunctionReferenceLib.pack({ + addr: address(oneHookPlugin), + functionId: uint8(MockBaseUserOpValidationPlugin.FunctionId.USER_OP_VALIDATION) + }); + + twoHookValidation = FunctionReferenceLib.pack({ + addr: address(twoHookPlugin), + functionId: uint8(MockBaseUserOpValidationPlugin.FunctionId.USER_OP_VALIDATION) + }); + + vm.startPrank(address(entryPoint)); account1.installPlugin({ plugin: address(noHookPlugin), manifestHash: keccak256(abi.encode(noHookPlugin.pluginManifest())), @@ -53,6 +72,7 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(noHookPlugin.foo.selector); + userOp.signature = abi.encodePacked(noHookValidation); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); @@ -69,6 +89,7 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(oneHookPlugin.bar.selector); + userOp.signature = abi.encodePacked(oneHookValidation); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); @@ -86,6 +107,7 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(oneHookPlugin.bar.selector); + userOp.signature = abi.encodePacked(oneHookValidation); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); @@ -108,6 +130,7 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(oneHookPlugin.bar.selector); + userOp.signature = abi.encodePacked(oneHookValidation); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); @@ -129,6 +152,7 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(oneHookPlugin.bar.selector); + userOp.signature = abi.encodePacked(oneHookValidation); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); @@ -148,6 +172,7 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(oneHookPlugin.bar.selector); + userOp.signature = abi.encodePacked(oneHookValidation); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); @@ -172,6 +197,7 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(oneHookPlugin.bar.selector); + userOp.signature = abi.encodePacked(oneHookValidation); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); @@ -195,6 +221,7 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(oneHookPlugin.bar.selector); + userOp.signature = abi.encodePacked(oneHookValidation); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); @@ -218,6 +245,7 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(twoHookPlugin.baz.selector); + userOp.signature = abi.encodePacked(twoHookValidation); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); @@ -236,6 +264,7 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(twoHookPlugin.baz.selector); + userOp.signature = abi.encodePacked(twoHookValidation); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); diff --git a/test/mocks/plugins/ComprehensivePlugin.sol b/test/mocks/plugins/ComprehensivePlugin.sol index 9aac5545..ec40368f 100644 --- a/test/mocks/plugins/ComprehensivePlugin.sol +++ b/test/mocks/plugins/ComprehensivePlugin.sol @@ -84,7 +84,11 @@ contract ComprehensivePlugin is IValidation, IValidationHook, IExecutionHook, Ba revert NotImplemented(); } - function validateRuntime(uint8 functionId, address, uint256, bytes calldata) external pure override { + function validateRuntime(uint8 functionId, address, uint256, bytes calldata, bytes calldata) + external + pure + override + { if (functionId == uint8(FunctionId.VALIDATION)) { return; } diff --git a/test/mocks/plugins/ValidationPluginMocks.sol b/test/mocks/plugins/ValidationPluginMocks.sol index 554f589f..443ee0bb 100644 --- a/test/mocks/plugins/ValidationPluginMocks.sol +++ b/test/mocks/plugins/ValidationPluginMocks.sol @@ -71,7 +71,7 @@ abstract contract MockBaseUserOpValidationPlugin is IValidation, IValidationHook revert NotImplemented(); } - function validateRuntime(uint8, address, uint256, bytes calldata) external pure override { + function validateRuntime(uint8, address, uint256, bytes calldata, bytes calldata) external pure override { revert NotImplemented(); } } diff --git a/test/plugin/SingleOwnerPlugin.t.sol b/test/plugin/SingleOwnerPlugin.t.sol index 71afbdbf..41997591 100644 --- a/test/plugin/SingleOwnerPlugin.t.sol +++ b/test/plugin/SingleOwnerPlugin.t.sol @@ -114,11 +114,11 @@ contract SingleOwnerPluginTest is OptimizedTest { assertEq(address(0), plugin.owner()); plugin.transferOwnership(owner1); assertEq(owner1, plugin.owner()); - plugin.validateRuntime(uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER_OR_SELF), owner1, 0, ""); + plugin.validateRuntime(uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER), owner1, 0, "", ""); vm.startPrank(b); vm.expectRevert(ISingleOwnerPlugin.NotAuthorized.selector); - plugin.validateRuntime(uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER_OR_SELF), owner1, 0, ""); + plugin.validateRuntime(uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER), owner1, 0, "", ""); } function testFuzz_validateUserOpSig(string memory salt, PackedUserOperation memory userOp) public { @@ -133,9 +133,8 @@ contract SingleOwnerPluginTest is OptimizedTest { userOp.signature = abi.encodePacked(r, s, v); // sig check should fail - uint256 success = plugin.validateUserOp( - uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER_OR_SELF), userOp, userOpHash - ); + uint256 success = + plugin.validateUserOp(uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER), userOp, userOpHash); assertEq(success, 1); // transfer ownership to signer @@ -143,9 +142,7 @@ contract SingleOwnerPluginTest is OptimizedTest { assertEq(signer, plugin.owner()); // sig check should pass - success = plugin.validateUserOp( - uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER_OR_SELF), userOp, userOpHash - ); + success = plugin.validateUserOp(uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER), userOp, userOpHash); assertEq(success, 0); } diff --git a/test/plugin/TokenReceiverPlugin.t.sol b/test/plugin/TokenReceiverPlugin.t.sol index 7a7433af..0e111020 100644 --- a/test/plugin/TokenReceiverPlugin.t.sol +++ b/test/plugin/TokenReceiverPlugin.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; +import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.sol"; import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; @@ -15,6 +15,7 @@ import {MockERC1155} from "../mocks/MockERC1155.sol"; import {OptimizedTest} from "../utils/OptimizedTest.sol"; contract TokenReceiverPluginTest is OptimizedTest, IERC1155Receiver { + EntryPoint public entryPoint; UpgradeableModularAccount public acct; TokenReceiverPlugin public plugin; @@ -32,7 +33,8 @@ contract TokenReceiverPluginTest is OptimizedTest, IERC1155Receiver { uint256 internal constant _BATCH_TOKEN_IDS = 5; function setUp() public { - MSCAFactoryFixture factory = new MSCAFactoryFixture(IEntryPoint(address(0)), _deploySingleOwnerPlugin()); + entryPoint = new EntryPoint(); + MSCAFactoryFixture factory = new MSCAFactoryFixture(entryPoint, _deploySingleOwnerPlugin()); acct = factory.createAccount(address(this), 0); plugin = _deployTokenReceiverPlugin(); @@ -53,6 +55,7 @@ contract TokenReceiverPluginTest is OptimizedTest, IERC1155Receiver { function _initPlugin() internal { bytes32 manifestHash = keccak256(abi.encode(plugin.pluginManifest())); + vm.prank(address(entryPoint)); acct.installPlugin(address(plugin), manifestHash, "", new FunctionReference[](0)); } diff --git a/test/utils/AccountTestBase.sol b/test/utils/AccountTestBase.sol index d8c890fe..6eca6626 100644 --- a/test/utils/AccountTestBase.sol +++ b/test/utils/AccountTestBase.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.19; import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; +import {ISingleOwnerPlugin} from "../../src/plugins/owner/ISingleOwnerPlugin.sol"; import {SingleOwnerPlugin} from "../../src/plugins/owner/SingleOwnerPlugin.sol"; import {OptimizedTest} from "./OptimizedTest.sol"; @@ -37,8 +38,16 @@ abstract contract AccountTestBase is OptimizedTest { function _transferOwnershipToTest() internal { // Transfer ownership to test contract for easier invocation. vm.prank(owner1); - account1.execute( - address(singleOwnerPlugin), 0, abi.encodeCall(SingleOwnerPlugin.transferOwnership, (address(this))) + account1.executeWithAuthorization( + abi.encodeCall( + account1.execute, + ( + address(singleOwnerPlugin), + 0, + abi.encodeCall(SingleOwnerPlugin.transferOwnership, (address(this))) + ) + ), + abi.encodePacked(address(singleOwnerPlugin), ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) ); } From 81bef34b369648f4f340a95b4e2c3d0c477ff1c0 Mon Sep 17 00:00:00 2001 From: fangting-alchemy <119372438+fangting-alchemy@users.noreply.github.com> Date: Tue, 18 Jun 2024 16:13:00 -0700 Subject: [PATCH 017/111] feat: Add various native function checks for plugin installation (#75) --- src/account/PluginManagerInternals.sol | 24 ++++++++++ src/helpers/KnownSelectors.sol | 63 ++++++++++++++++++++++++++ test/libraries/KnowSelectors.t.sol | 23 ++++++++++ 3 files changed, 110 insertions(+) create mode 100644 src/helpers/KnownSelectors.sol create mode 100644 test/libraries/KnowSelectors.t.sol diff --git a/src/account/PluginManagerInternals.sol b/src/account/PluginManagerInternals.sol index cfdbef16..1df1599b 100644 --- a/src/account/PluginManagerInternals.sol +++ b/src/account/PluginManagerInternals.sol @@ -23,6 +23,7 @@ import { toSetValue, PermittedExternalCallData } from "./AccountStorage.sol"; +import {KnownSelectors} from "../helpers/KnownSelectors.sol"; abstract contract PluginManagerInternals is IPluginManager { using EnumerableSet for EnumerableSet.Bytes32Set; @@ -30,10 +31,13 @@ abstract contract PluginManagerInternals is IPluginManager { using FunctionReferenceLib for FunctionReference; error ArrayLengthMismatch(); + error Erc4337FunctionNotAllowed(bytes4 selector); error ExecutionFunctionAlreadySet(bytes4 selector); error InvalidDependenciesProvided(); error InvalidPluginManifest(); + error IPluginFunctionNotAllowed(bytes4 selector); error MissingPluginDependency(address dependency); + error NativeFunctionNotAllowed(bytes4 selector); error NullFunctionReference(); error NullPlugin(); error PluginAlreadyInstalled(address plugin); @@ -69,6 +73,26 @@ abstract contract PluginManagerInternals is IPluginManager { revert ExecutionFunctionAlreadySet(selector); } + // Make sure incoming execution function does not collide with any native functions (data are stored on the + // account implementation contract) + if (KnownSelectors.isNativeFunction(selector)) { + revert NativeFunctionNotAllowed(selector); + } + + // Make sure incoming execution function is not a function in IPlugin + if (KnownSelectors.isIPluginFunction(selector)) { + revert IPluginFunctionNotAllowed(selector); + } + + // Also make sure it doesn't collide with functions defined by ERC-4337 + // and called by the entry point. This prevents a malicious plugin from + // sneaking in a function with the same selector as e.g. + // `validatePaymasterUserOp` and turning the account into their own + // personal paymaster. + if (KnownSelectors.isErc4337Function(selector)) { + revert Erc4337FunctionNotAllowed(selector); + } + _selectorData.plugin = plugin; _selectorData.isPublic = isPublic; } diff --git a/src/helpers/KnownSelectors.sol b/src/helpers/KnownSelectors.sol new file mode 100644 index 00000000..6d737172 --- /dev/null +++ b/src/helpers/KnownSelectors.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.25; + +import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; +import {IAccount} from "@eth-infinitism/account-abstraction/interfaces/IAccount.sol"; +import {IAggregator} from "@eth-infinitism/account-abstraction/interfaces/IAggregator.sol"; +import {IPaymaster} from "@eth-infinitism/account-abstraction/interfaces/IPaymaster.sol"; + +import {IAccountLoupe} from "../interfaces/IAccountLoupe.sol"; +import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; +import {IPlugin} from "../interfaces/IPlugin.sol"; +import {IPluginExecutor} from "../interfaces/IPluginExecutor.sol"; +import {IPluginManager} from "../interfaces/IPluginManager.sol"; +import {IStandardExecutor} from "../interfaces/IStandardExecutor.sol"; +import {IValidation} from "../interfaces/IValidation.sol"; +import {IValidationHook} from "../interfaces/IValidationHook.sol"; + +/// @dev Library to help to check if a selector is a know function selector of the modular account or ERC-4337 +/// contract. +library KnownSelectors { + function isNativeFunction(bytes4 selector) internal pure returns (bool) { + return + // check against IAccount methods + selector == IAccount.validateUserOp.selector + // check against IPluginManager methods + || selector == IPluginManager.installPlugin.selector || selector == IPluginManager.uninstallPlugin.selector + // check against IERC165 methods + || selector == IERC165.supportsInterface.selector + // check against UUPSUpgradeable methods + || selector == UUPSUpgradeable.proxiableUUID.selector + || selector == UUPSUpgradeable.upgradeToAndCall.selector + // check against IStandardExecutor methods + || selector == IStandardExecutor.execute.selector || selector == IStandardExecutor.executeBatch.selector + // check against IPluginExecutor methods + || selector == IPluginExecutor.executeFromPlugin.selector + || selector == IPluginExecutor.executeFromPluginExternal.selector + || selector == IPluginExecutor.executeWithAuthorization.selector + // check against IAccountLoupe methods + || selector == IAccountLoupe.getExecutionFunctionHandler.selector + || selector == IAccountLoupe.getValidations.selector + || selector == IAccountLoupe.getExecutionHooks.selector + || selector == IAccountLoupe.getPreValidationHooks.selector + || selector == IAccountLoupe.getInstalledPlugins.selector; + } + + function isErc4337Function(bytes4 selector) internal pure returns (bool) { + return selector == IAggregator.validateSignatures.selector + || selector == IAggregator.validateUserOpSignature.selector + || selector == IAggregator.aggregateSignatures.selector + || selector == IPaymaster.validatePaymasterUserOp.selector || selector == IPaymaster.postOp.selector; + } + + function isIPluginFunction(bytes4 selector) internal pure returns (bool) { + return selector == IPlugin.onInstall.selector || selector == IPlugin.onUninstall.selector + || selector == IPlugin.pluginManifest.selector || selector == IPlugin.pluginMetadata.selector + || selector == IExecutionHook.preExecutionHook.selector + || selector == IExecutionHook.postExecutionHook.selector || selector == IValidation.validateUserOp.selector + || selector == IValidation.validateRuntime.selector || selector == IValidation.validateSignature.selector + || selector == IValidationHook.preUserOpValidationHook.selector + || selector == IValidationHook.preRuntimeValidationHook.selector; + } +} diff --git a/test/libraries/KnowSelectors.t.sol b/test/libraries/KnowSelectors.t.sol new file mode 100644 index 00000000..893b831b --- /dev/null +++ b/test/libraries/KnowSelectors.t.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.25; + +import {Test} from "forge-std/Test.sol"; +import {IAccount} from "@eth-infinitism/account-abstraction/interfaces/IAccount.sol"; +import {IPaymaster} from "@eth-infinitism/account-abstraction/interfaces/IPaymaster.sol"; + +import {KnownSelectors} from "../../src/helpers/KnownSelectors.sol"; +import {IPlugin} from "../../src/interfaces/IPlugin.sol"; + +contract KnownSelectorsTest is Test { + function test_isNativeFunction() public { + assertTrue(KnownSelectors.isNativeFunction(IAccount.validateUserOp.selector)); + } + + function test_isErc4337Function() public { + assertTrue(KnownSelectors.isErc4337Function(IPaymaster.validatePaymasterUserOp.selector)); + } + + function test_isIPluginFunction() public { + assertTrue(KnownSelectors.isIPluginFunction(IPlugin.pluginMetadata.selector)); + } +} From c0a1e9da6dd1b96cf20ae6e0d10253be2f58637a Mon Sep 17 00:00:00 2001 From: Adam Egyed <5456061+adamegyed@users.noreply.github.com> Date: Wed, 19 Jun 2024 10:58:56 -0400 Subject: [PATCH 018/111] feat: [v0.8-develop, experimental] default validation (#63) --- foundry.toml | 2 +- src/account/AccountStorage.sol | 4 + src/account/PluginManager2.sol | 68 +++++++++++++ src/account/PluginManagerInternals.sol | 7 +- src/account/UpgradeableModularAccount.sol | 96 +++++++++++++++++-- src/interfaces/IPlugin.sol | 24 ++--- src/interfaces/IPluginManager.sol | 34 ++++++- src/plugins/TokenReceiverPlugin.sol | 21 ++-- test/account/AccountExecHooks.t.sol | 8 +- test/account/AccountReturnData.t.sol | 8 +- test/account/DefaultValidationTest.t.sol | 82 ++++++++++++++++ test/account/MultiValidation.t.sol | 22 ++++- test/account/UpgradeableModularAccount.t.sol | 12 +-- test/account/ValidationIntersection.t.sol | 20 ++-- .../mocks/DefaultValidationFactoryFixture.sol | 81 ++++++++++++++++ .../plugins/BadTransferOwnershipPlugin.sol | 7 +- test/mocks/plugins/ComprehensivePlugin.sol | 7 +- .../ExecFromPluginPermissionsMocks.sol | 7 +- test/mocks/plugins/ManifestValidityMocks.sol | 7 +- test/mocks/plugins/ReturnDataPluginMocks.sol | 28 ++++-- test/mocks/plugins/ValidationPluginMocks.sol | 21 ++-- test/utils/AccountTestBase.sol | 9 +- 22 files changed, 499 insertions(+), 76 deletions(-) create mode 100644 src/account/PluginManager2.sol create mode 100644 test/account/DefaultValidationTest.t.sol create mode 100644 test/mocks/DefaultValidationFactoryFixture.sol diff --git a/foundry.toml b/foundry.toml index 5aad32a4..67d9eb7c 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,5 +1,5 @@ [profile.default] -solc = '0.8.25' +solc = '0.8.26' via_ir = false src = 'src' test = 'test' diff --git a/src/account/AccountStorage.sol b/src/account/AccountStorage.sol index 4319f470..f463a629 100644 --- a/src/account/AccountStorage.sol +++ b/src/account/AccountStorage.sol @@ -39,6 +39,8 @@ struct SelectorData { // 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 isPublic; + // Whether or not a default validation function may be used to validate this function. + bool allowDefaultValidation; // How many times a `PRE_HOOK_ALWAYS_DENY` has been added for this function. // Since that is the only type of hook that may overlap, we can use this to track the number of times it has // been applied, and whether or not the deny should apply. The size `uint48` was chosen somewhat arbitrarily, @@ -68,6 +70,8 @@ struct AccountStorage { mapping(bytes4 => uint256) supportedIfaces; // Installed plugins capable of signature validation. EnumerableSet.Bytes32Set signatureValidations; + // Todo: merge this with other validation storage? + EnumerableSet.Bytes32Set defaultValidations; } // TODO: Change how pre-validation hooks work to allow association with validation, rather than selector. diff --git a/src/account/PluginManager2.sol b/src/account/PluginManager2.sol new file mode 100644 index 00000000..c8945ef1 --- /dev/null +++ b/src/account/PluginManager2.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.25; + +import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; + +import {IPlugin} from "../interfaces/IPlugin.sol"; +import {FunctionReference} from "../interfaces/IPluginManager.sol"; +import {FunctionReferenceLib} from "../helpers/FunctionReferenceLib.sol"; +import {AccountStorage, getAccountStorage, toSetValue} from "./AccountStorage.sol"; + +// Temporary additional functions for a user-controlled install flow for validation functions. +abstract contract PluginManager2 { + using EnumerableSet for EnumerableSet.Bytes32Set; + + error DefaultValidationAlreadySet(address plugin, uint8 functionId); + error ValidationAlreadySet(bytes4 selector, address plugin, uint8 functionId); + error ValidationNotSet(bytes4 selector, address plugin, uint8 functionId); + + function _installValidation( + address plugin, + uint8 functionId, + bool isDefault, + bytes4[] memory selectors, + bytes calldata installData + ) internal { + FunctionReference validationFunction = FunctionReferenceLib.pack(plugin, functionId); + + AccountStorage storage _storage = getAccountStorage(); + + if (isDefault) { + if (!_storage.defaultValidations.add(toSetValue(validationFunction))) { + revert DefaultValidationAlreadySet(plugin, functionId); + } + } + + for (uint256 i = 0; i < selectors.length; ++i) { + bytes4 selector = selectors[i]; + if (!_storage.selectorData[selector].validations.add(toSetValue(validationFunction))) { + revert ValidationAlreadySet(selector, plugin, functionId); + } + } + + IPlugin(plugin).onInstall(installData); + } + + function _uninstallValidation( + address plugin, + uint8 functionId, + bytes4[] calldata selectors, + bytes calldata uninstallData + ) internal { + FunctionReference validationFunction = FunctionReferenceLib.pack(plugin, functionId); + + AccountStorage storage _storage = getAccountStorage(); + + // Ignore return value - remove if present, do nothing otherwise. + _storage.defaultValidations.remove(toSetValue(validationFunction)); + + for (uint256 i = 0; i < selectors.length; ++i) { + bytes4 selector = selectors[i]; + if (!_storage.selectorData[selector].validations.remove(toSetValue(validationFunction))) { + revert ValidationNotSet(selector, plugin, functionId); + } + } + + IPlugin(plugin).onUninstall(uninstallData); + } +} diff --git a/src/account/PluginManagerInternals.sol b/src/account/PluginManagerInternals.sol index 1df1599b..d8966275 100644 --- a/src/account/PluginManagerInternals.sol +++ b/src/account/PluginManagerInternals.sol @@ -63,7 +63,7 @@ abstract contract PluginManagerInternals is IPluginManager { // Storage update operations - function _setExecutionFunction(bytes4 selector, bool isPublic, address plugin) + function _setExecutionFunction(bytes4 selector, bool isPublic, bool allowDefaultValidation, address plugin) internal notNullPlugin(plugin) { @@ -95,6 +95,7 @@ abstract contract PluginManagerInternals is IPluginManager { _selectorData.plugin = plugin; _selectorData.isPublic = isPublic; + _selectorData.allowDefaultValidation = allowDefaultValidation; } function _removeExecutionFunction(bytes4 selector) internal { @@ -102,6 +103,7 @@ abstract contract PluginManagerInternals is IPluginManager { _selectorData.plugin = address(0); _selectorData.isPublic = false; + _selectorData.allowDefaultValidation = false; } function _addValidationFunction(bytes4 selector, FunctionReference validationFunction) @@ -245,7 +247,8 @@ abstract contract PluginManagerInternals is IPluginManager { for (uint256 i = 0; i < length; ++i) { bytes4 selector = manifest.executionFunctions[i].executionSelector; bool isPublic = manifest.executionFunctions[i].isPublic; - _setExecutionFunction(selector, isPublic, plugin); + bool allowDefaultValidation = manifest.executionFunctions[i].allowDefaultValidation; + _setExecutionFunction(selector, isPublic, allowDefaultValidation, plugin); } // Add installed plugin and selectors this plugin can call diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 69a11cbd..047e73c0 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -30,6 +30,7 @@ import { } from "./AccountStorage.sol"; import {AccountStorageInitializable} from "./AccountStorageInitializable.sol"; import {PluginManagerInternals} from "./PluginManagerInternals.sol"; +import {PluginManager2} from "./PluginManager2.sol"; contract UpgradeableModularAccount is AccountExecutor, @@ -41,6 +42,7 @@ contract UpgradeableModularAccount is IPluginExecutor, IStandardExecutor, PluginManagerInternals, + PluginManager2, UUPSUpgradeable { using EnumerableSet for EnumerableSet.Bytes32Set; @@ -77,6 +79,7 @@ contract UpgradeableModularAccount is error UnexpectedAggregator(address plugin, uint8 functionId, address aggregator); error UnrecognizedFunction(bytes4 selector); error UserOpValidationFunctionMissing(bytes4 selector); + error ValidationDoesNotApply(bytes4 selector, address plugin, uint8 functionId, bool isDefault); // Wraps execution of a native function with runtime validation and hooks // Used for upgradeTo, upgradeToAndCall, execute, executeBatch, installPlugin, uninstallPlugin @@ -155,6 +158,7 @@ contract UpgradeableModularAccount is } /// @inheritdoc IStandardExecutor + /// @notice May be validated by a default validation. function execute(address target, uint256 value, bytes calldata data) external payable @@ -166,6 +170,7 @@ contract UpgradeableModularAccount is } /// @inheritdoc IStandardExecutor + /// @notice May be validated by a default validation function. function executeBatch(Call[] calldata calls) external payable @@ -279,11 +284,12 @@ contract UpgradeableModularAccount is if (_storage.selectorData[execSelector].denyExecutionCount > 0) { revert AlwaysDenyRule(); } - if (!_storage.selectorData[execSelector].validations.contains(toSetValue(runtimeValidationFunction))) { - revert RuntimeValidationFunctionMissing(execSelector); - } - _doRuntimeValidation(runtimeValidationFunction, data, authorization[21:]); + // Check if the runtime validation function is allowed to be called + bool isDefaultValidation = uint8(authorization[21]) == 1; + _checkIfValidationApplies(execSelector, runtimeValidationFunction, isDefaultValidation); + + _doRuntimeValidation(runtimeValidationFunction, data, authorization[22:]); // If runtime validation passes, execute the call @@ -299,6 +305,7 @@ contract UpgradeableModularAccount is } /// @inheritdoc IPluginManager + /// @notice May be validated by a default validation. function installPlugin( address plugin, bytes32 manifestHash, @@ -309,6 +316,7 @@ contract UpgradeableModularAccount is } /// @inheritdoc IPluginManager + /// @notice May be validated by a default validation. function uninstallPlugin(address plugin, bytes calldata config, bytes calldata pluginUninstallData) external override @@ -325,6 +333,42 @@ contract UpgradeableModularAccount is _uninstallPlugin(plugin, manifest, pluginUninstallData); } + /// @notice Initializes the account with a validation function added to the default pool. + /// TODO: remove and merge with regular initialization, after we figure out a better install/uninstall workflow + /// with user install configs. + /// @dev This function is only callable once, and only by the EntryPoint. + + function initializeDefaultValidation(address plugin, uint8 functionId, bytes calldata installData) + external + initializer + { + _installValidation(plugin, functionId, true, new bytes4[](0), installData); + emit ModularAccountInitialized(_ENTRY_POINT); + } + + /// @inheritdoc IPluginManager + /// @notice May be validated by a default validation. + function installValidation( + address plugin, + uint8 functionId, + bool isDefault, + bytes4[] calldata selectors, + bytes calldata installData + ) external wrapNativeFunction { + _installValidation(plugin, functionId, isDefault, selectors, installData); + } + + /// @inheritdoc IPluginManager + /// @notice May be validated by a default validation. + function uninstallValidation( + address plugin, + uint8 functionId, + bytes4[] calldata selectors, + bytes calldata uninstallData + ) external wrapNativeFunction { + _uninstallValidation(plugin, functionId, selectors, uninstallData); + } + /// @notice ERC165 introspection /// @dev returns true for `IERC165.interfaceId` and false for `0xFFFFFFFF` /// @param interfaceId interface id to check against @@ -341,6 +385,7 @@ contract UpgradeableModularAccount is } /// @inheritdoc UUPSUpgradeable + /// @notice May be validated by a default validation. function upgradeToAndCall(address newImplementation, bytes memory data) public payable @@ -398,14 +443,12 @@ contract UpgradeableModularAccount is // Revert if the provided `authorization` less than 21 bytes long, rather than right-padding. FunctionReference userOpValidationFunction = FunctionReference.wrap(bytes21(userOp.signature[:21])); + bool isDefaultValidation = uint8(userOp.signature[21]) == 1; - if (!getAccountStorage().selectorData[selector].validations.contains(toSetValue(userOpValidationFunction))) - { - revert UserOpValidationFunctionMissing(selector); - } + _checkIfValidationApplies(selector, userOpValidationFunction, isDefaultValidation); validationData = - _doUserOpValidation(selector, userOpValidationFunction, userOp, userOp.signature[21:], userOpHash); + _doUserOpValidation(selector, userOpValidationFunction, userOp, userOp.signature[22:], userOpHash); } // To support gas estimation, we don't fail early when the failure is caused by a signature failure @@ -573,6 +616,41 @@ contract UpgradeableModularAccount is // solhint-disable-next-line no-empty-blocks function _authorizeUpgrade(address newImplementation) internal override {} + function _checkIfValidationApplies(bytes4 selector, FunctionReference validationFunction, bool isDefault) + internal + view + { + AccountStorage storage _storage = getAccountStorage(); + + // Check that the provided validation function is applicable to the selector + if (isDefault) { + if ( + !_defaultValidationAllowed(selector) + || !_storage.defaultValidations.contains(toSetValue(validationFunction)) + ) { + revert UserOpValidationFunctionMissing(selector); + } + } else { + // Not default validation, but per-selector + if (!getAccountStorage().selectorData[selector].validations.contains(toSetValue(validationFunction))) { + revert UserOpValidationFunctionMissing(selector); + } + } + } + + function _defaultValidationAllowed(bytes4 selector) internal view returns (bool) { + if ( + selector == this.execute.selector || selector == this.executeBatch.selector + || selector == this.installPlugin.selector || selector == this.uninstallPlugin.selector + || selector == this.installValidation.selector || selector == this.uninstallValidation.selector + || selector == this.upgradeToAndCall.selector + ) { + return true; + } + + return getAccountStorage().selectorData[selector].allowDefaultValidation; + } + function _checkPermittedCallerIfNotFromEP() internal view { AccountStorage storage _storage = getAccountStorage(); diff --git a/src/interfaces/IPlugin.sol b/src/interfaces/IPlugin.sol index a74dfaf8..e9ea8045 100644 --- a/src/interfaces/IPlugin.sol +++ b/src/interfaces/IPlugin.sol @@ -27,6 +27,8 @@ struct ManifestExecutionFunction { bytes4 executionSelector; // If true, the function won't need runtime validation, and can be called by anyone. bool isPublic; + // If true, the function can be validated by a default validation function. + bool allowDefaultValidation; } /// @dev For functions of type `ManifestAssociatedFunctionType.DEPENDENCY`, the MSCA MUST find the plugin address @@ -77,15 +79,12 @@ struct PluginMetadata { /// @dev A struct describing how the plugin should be installed on a modular account. struct PluginManifest { - // List of ERC-165 interface IDs to add to account to support introspection checks. This MUST NOT include - // IPlugin's interface ID. - bytes4[] interfaceIds; - // If this plugin depends on other plugins' validation functions, the interface IDs of those plugins MUST be - // provided here, with its position in the array matching the `dependencyIndex` members of `ManifestFunction` - // structs used in the manifest. - bytes4[] dependencyInterfaceIds; // Execution functions defined in this plugin to be installed on the MSCA. ManifestExecutionFunction[] executionFunctions; + ManifestAssociatedFunction[] validationFunctions; + ManifestAssociatedFunction[] preValidationHooks; + ManifestExecutionHook[] executionHooks; + uint8[] signatureValidationFunctions; // Plugin execution functions already installed on the MSCA that this plugin will be able to call. bytes4[] permittedExecutionSelectors; // Boolean to indicate whether the plugin can call any external address. @@ -94,10 +93,13 @@ struct PluginManifest { // plugin MUST still be able to spend up to the balance that it sends to the account in the same call. bool canSpendNativeToken; ManifestExternalCallPermission[] permittedExternalCalls; - ManifestAssociatedFunction[] validationFunctions; - ManifestAssociatedFunction[] preValidationHooks; - ManifestExecutionHook[] executionHooks; - uint8[] signatureValidationFunctions; + // List of ERC-165 interface IDs to add to account to support introspection checks. This MUST NOT include + // IPlugin's interface ID. + bytes4[] interfaceIds; + // If this plugin depends on other plugins' validation functions, the interface IDs of those plugins MUST be + // provided here, with its position in the array matching the `dependencyIndex` members of `ManifestFunction` + // structs used in the manifest. + bytes4[] dependencyInterfaceIds; } interface IPlugin is IERC165 { diff --git a/src/interfaces/IPluginManager.sol b/src/interfaces/IPluginManager.sol index dd4dc672..1c21aadd 100644 --- a/src/interfaces/IPluginManager.sol +++ b/src/interfaces/IPluginManager.sol @@ -22,8 +22,40 @@ interface IPluginManager { FunctionReference[] calldata dependencies ) external; + /// @notice Temporary install function - pending a different user-supplied install config & manifest validation + /// path. + /// Installs a validation function across a set of execution selectors, and optionally mark it as a default + /// validation. + /// TODO: remove or update. + /// @dev This does not validate anything against the manifest - the caller must ensure validity. + /// @param plugin The plugin to install. + /// @param functionId The function ID of the validation function to install. + /// @param isDefault Whether the validation function applies for all selectors in the default pool. + /// @param selectors The selectors to install the validation function for. + /// @param installData Optional data to be decoded and used by the plugin to setup initial plugin state. + function installValidation( + address plugin, + uint8 functionId, + bool isDefault, + bytes4[] calldata selectors, + bytes calldata installData + ) external; + + /// @notice Uninstall a validation function from a set of execution selectors. + /// TODO: remove or update. + /// @param plugin The plugin to uninstall. + /// @param functionId The function ID of the validation function to uninstall. + /// @param selectors The selectors to uninstall the validation function for. + /// @param uninstallData Optional data to be decoded and used by the plugin to clear plugin data for the + /// account. + function uninstallValidation( + address plugin, + uint8 functionId, + bytes4[] calldata selectors, + bytes calldata uninstallData + ) external; + /// @notice Uninstall a plugin from the modular account. - /// @dev Uninstalling owner plugins outside of a replace operation via executeBatch risks losing the account! /// @param plugin The plugin to uninstall. /// @param config An optional, implementation-specific field that accounts may use to ensure consistency /// guarantees. diff --git a/src/plugins/TokenReceiverPlugin.sol b/src/plugins/TokenReceiverPlugin.sol index f3379f1a..5bfddc0f 100644 --- a/src/plugins/TokenReceiverPlugin.sol +++ b/src/plugins/TokenReceiverPlugin.sol @@ -59,12 +59,21 @@ contract TokenReceiverPlugin is BasePlugin, IERC721Receiver, IERC1155Receiver { PluginManifest memory manifest; manifest.executionFunctions = new ManifestExecutionFunction[](3); - manifest.executionFunctions[0] = - ManifestExecutionFunction({executionSelector: this.onERC721Received.selector, isPublic: true}); - manifest.executionFunctions[1] = - ManifestExecutionFunction({executionSelector: this.onERC1155Received.selector, isPublic: true}); - manifest.executionFunctions[2] = - ManifestExecutionFunction({executionSelector: this.onERC1155BatchReceived.selector, isPublic: true}); + manifest.executionFunctions[0] = ManifestExecutionFunction({ + executionSelector: this.onERC721Received.selector, + isPublic: true, + allowDefaultValidation: false + }); + manifest.executionFunctions[1] = ManifestExecutionFunction({ + executionSelector: this.onERC1155Received.selector, + isPublic: true, + allowDefaultValidation: false + }); + manifest.executionFunctions[2] = ManifestExecutionFunction({ + executionSelector: this.onERC1155BatchReceived.selector, + isPublic: true, + allowDefaultValidation: false + }); manifest.interfaceIds = new bytes4[](2); manifest.interfaceIds[0] = type(IERC721Receiver).interfaceId; diff --git a/test/account/AccountExecHooks.t.sol b/test/account/AccountExecHooks.t.sol index ee229e3e..3d8a0c69 100644 --- a/test/account/AccountExecHooks.t.sol +++ b/test/account/AccountExecHooks.t.sol @@ -39,7 +39,13 @@ contract AccountExecHooksTest is AccountTestBase { function setUp() public { _transferOwnershipToTest(); - m1.executionFunctions.push(ManifestExecutionFunction({executionSelector: _EXEC_SELECTOR, isPublic: true})); + m1.executionFunctions.push( + ManifestExecutionFunction({ + executionSelector: _EXEC_SELECTOR, + isPublic: true, + allowDefaultValidation: false + }) + ); } function test_preExecHook_install() public { diff --git a/test/account/AccountReturnData.t.sol b/test/account/AccountReturnData.t.sol index 085fa4bf..b3d2419a 100644 --- a/test/account/AccountReturnData.t.sol +++ b/test/account/AccountReturnData.t.sol @@ -59,7 +59,9 @@ contract AccountReturnDataTest is AccountTestBase { account1.execute, (address(regularResultContract), 0, abi.encodeCall(RegularResultContract.foo, ())) ), - abi.encodePacked(singleOwnerPlugin, ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) + abi.encodePacked( + singleOwnerPlugin, ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER, SELECTOR_ASSOCIATED_VALIDATION + ) ); bytes32 result = abi.decode(abi.decode(returnData, (bytes)), (bytes32)); @@ -83,7 +85,9 @@ contract AccountReturnDataTest is AccountTestBase { bytes memory retData = account1.executeWithAuthorization( abi.encodeCall(account1.executeBatch, (calls)), - abi.encodePacked(singleOwnerPlugin, ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) + abi.encodePacked( + singleOwnerPlugin, ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER, SELECTOR_ASSOCIATED_VALIDATION + ) ); bytes[] memory returnDatas = abi.decode(retData, (bytes[])); diff --git a/test/account/DefaultValidationTest.t.sol b/test/account/DefaultValidationTest.t.sol new file mode 100644 index 00000000..fc93060d --- /dev/null +++ b/test/account/DefaultValidationTest.t.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.25; + +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; +import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; + +import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; +import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; +import {ISingleOwnerPlugin} from "../../src/plugins/owner/ISingleOwnerPlugin.sol"; + +import {AccountTestBase} from "../utils/AccountTestBase.sol"; +import {DefaultValidationFactoryFixture} from "../mocks/DefaultValidationFactoryFixture.sol"; + +contract DefaultValidationTest is AccountTestBase { + using MessageHashUtils for bytes32; + + DefaultValidationFactoryFixture public defaultValidationFactoryFixture; + + uint256 public constant CALL_GAS_LIMIT = 50000; + uint256 public constant VERIFICATION_GAS_LIMIT = 1200000; + + FunctionReference public ownerValidation; + + address public ethRecipient; + + function setUp() public { + defaultValidationFactoryFixture = new DefaultValidationFactoryFixture(entryPoint, singleOwnerPlugin); + + account1 = UpgradeableModularAccount(payable(defaultValidationFactoryFixture.getAddress(owner1, 0))); + + vm.deal(address(account1), 100 ether); + + ethRecipient = makeAddr("ethRecipient"); + vm.deal(ethRecipient, 1 wei); + + ownerValidation = FunctionReferenceLib.pack( + address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) + ); + } + + function test_defaultValidation_userOp_simple() public { + PackedUserOperation memory userOp = PackedUserOperation({ + sender: address(account1), + nonce: 0, + initCode: abi.encodePacked( + defaultValidationFactoryFixture, + abi.encodeCall(DefaultValidationFactoryFixture.createAccount, (owner1, 0)) + ), + callData: abi.encodeCall(UpgradeableModularAccount.execute, (ethRecipient, 1 wei, "")), + accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), + preVerificationGas: 0, + gasFees: _encodeGas(1, 1), + paymasterAndData: "", + signature: "" + }); + + // Generate signature + bytes32 userOpHash = entryPoint.getUserOpHash(userOp); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); + userOp.signature = abi.encodePacked(ownerValidation, DEFAULT_VALIDATION, r, s, v); + + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); + userOps[0] = userOp; + + entryPoint.handleOps(userOps, beneficiary); + + assertEq(ethRecipient.balance, 2 wei); + } + + function test_defaultValidation_runtime_simple() public { + // Deploy the account first + defaultValidationFactoryFixture.createAccount(owner1, 0); + + vm.prank(owner1); + account1.executeWithAuthorization( + abi.encodeCall(UpgradeableModularAccount.execute, (ethRecipient, 1 wei, "")), + abi.encodePacked(ownerValidation, DEFAULT_VALIDATION) + ); + + assertEq(ethRecipient.balance, 2 wei); + } +} diff --git a/test/account/MultiValidation.t.sol b/test/account/MultiValidation.t.sol index 9ca70857..9b22f5a0 100644 --- a/test/account/MultiValidation.t.sol +++ b/test/account/MultiValidation.t.sol @@ -67,13 +67,21 @@ contract MultiValidationTest is AccountTestBase { ); account1.executeWithAuthorization( abi.encodeCall(IStandardExecutor.execute, (address(0), 0, "")), - abi.encodePacked(address(validator2), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER)) + abi.encodePacked( + address(validator2), + uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER), + SELECTOR_ASSOCIATED_VALIDATION + ) ); vm.prank(owner2); account1.executeWithAuthorization( abi.encodeCall(IStandardExecutor.execute, (address(0), 0, "")), - abi.encodePacked(address(validator2), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER)) + abi.encodePacked( + address(validator2), + uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER), + SELECTOR_ASSOCIATED_VALIDATION + ) ); } @@ -97,8 +105,14 @@ contract MultiValidationTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = - abi.encodePacked(address(validator2), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER), r, s, v); + userOp.signature = abi.encodePacked( + address(validator2), + SELECTOR_ASSOCIATED_VALIDATION, + uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER), + r, + s, + v + ); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; diff --git a/test/account/UpgradeableModularAccount.t.sol b/test/account/UpgradeableModularAccount.t.sol index 1cbcb78d..15ad262e 100644 --- a/test/account/UpgradeableModularAccount.t.sol +++ b/test/account/UpgradeableModularAccount.t.sol @@ -87,7 +87,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = abi.encodePacked(ownerValidation, r, s, v); + userOp.signature = abi.encodePacked(ownerValidation, SELECTOR_ASSOCIATED_VALIDATION, r, s, v); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -116,7 +116,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = abi.encodePacked(ownerValidation, r, s, v); + userOp.signature = abi.encodePacked(ownerValidation, SELECTOR_ASSOCIATED_VALIDATION, r, s, v); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -142,7 +142,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = abi.encodePacked(ownerValidation, r, s, v); + userOp.signature = abi.encodePacked(ownerValidation, SELECTOR_ASSOCIATED_VALIDATION, r, s, v); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -168,7 +168,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = abi.encodePacked(ownerValidation, r, s, v); + userOp.signature = abi.encodePacked(ownerValidation, SELECTOR_ASSOCIATED_VALIDATION, r, s, v); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -196,7 +196,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = abi.encodePacked(ownerValidation, r, s, v); + userOp.signature = abi.encodePacked(ownerValidation, SELECTOR_ASSOCIATED_VALIDATION, r, s, v); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -227,7 +227,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = abi.encodePacked(ownerValidation, r, s, v); + userOp.signature = abi.encodePacked(ownerValidation, SELECTOR_ASSOCIATED_VALIDATION, r, s, v); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; diff --git a/test/account/ValidationIntersection.t.sol b/test/account/ValidationIntersection.t.sol index 9c54e7b0..de0e7ef1 100644 --- a/test/account/ValidationIntersection.t.sol +++ b/test/account/ValidationIntersection.t.sol @@ -72,7 +72,7 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(noHookPlugin.foo.selector); - userOp.signature = abi.encodePacked(noHookValidation); + userOp.signature = abi.encodePacked(noHookValidation, SELECTOR_ASSOCIATED_VALIDATION); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); @@ -89,7 +89,7 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(oneHookPlugin.bar.selector); - userOp.signature = abi.encodePacked(oneHookValidation); + userOp.signature = abi.encodePacked(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); @@ -107,7 +107,7 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(oneHookPlugin.bar.selector); - userOp.signature = abi.encodePacked(oneHookValidation); + userOp.signature = abi.encodePacked(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); @@ -130,7 +130,7 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(oneHookPlugin.bar.selector); - userOp.signature = abi.encodePacked(oneHookValidation); + userOp.signature = abi.encodePacked(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); @@ -152,7 +152,7 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(oneHookPlugin.bar.selector); - userOp.signature = abi.encodePacked(oneHookValidation); + userOp.signature = abi.encodePacked(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); @@ -172,7 +172,7 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(oneHookPlugin.bar.selector); - userOp.signature = abi.encodePacked(oneHookValidation); + userOp.signature = abi.encodePacked(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); @@ -197,7 +197,7 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(oneHookPlugin.bar.selector); - userOp.signature = abi.encodePacked(oneHookValidation); + userOp.signature = abi.encodePacked(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); @@ -221,7 +221,7 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(oneHookPlugin.bar.selector); - userOp.signature = abi.encodePacked(oneHookValidation); + userOp.signature = abi.encodePacked(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); @@ -245,7 +245,7 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(twoHookPlugin.baz.selector); - userOp.signature = abi.encodePacked(twoHookValidation); + userOp.signature = abi.encodePacked(twoHookValidation, SELECTOR_ASSOCIATED_VALIDATION); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); @@ -264,7 +264,7 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(twoHookPlugin.baz.selector); - userOp.signature = abi.encodePacked(twoHookValidation); + userOp.signature = abi.encodePacked(twoHookValidation, SELECTOR_ASSOCIATED_VALIDATION); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); diff --git a/test/mocks/DefaultValidationFactoryFixture.sol b/test/mocks/DefaultValidationFactoryFixture.sol new file mode 100644 index 00000000..5fbefe7c --- /dev/null +++ b/test/mocks/DefaultValidationFactoryFixture.sol @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; + +import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; +import {ISingleOwnerPlugin} from "../../src/plugins/owner/ISingleOwnerPlugin.sol"; +import {SingleOwnerPlugin} from "../../src/plugins/owner/SingleOwnerPlugin.sol"; + +import {OptimizedTest} from "../utils/OptimizedTest.sol"; + +contract DefaultValidationFactoryFixture is OptimizedTest { + UpgradeableModularAccount public accountImplementation; + SingleOwnerPlugin public singleOwnerPlugin; + bytes32 private immutable _PROXY_BYTECODE_HASH; + + uint32 public constant UNSTAKE_DELAY = 1 weeks; + + IEntryPoint public entryPoint; + + address public self; + + bytes32 public singleOwnerPluginManifestHash; + + constructor(IEntryPoint _entryPoint, SingleOwnerPlugin _singleOwnerPlugin) { + entryPoint = _entryPoint; + accountImplementation = _deployUpgradeableModularAccount(_entryPoint); + _PROXY_BYTECODE_HASH = keccak256( + abi.encodePacked(type(ERC1967Proxy).creationCode, abi.encode(address(accountImplementation), "")) + ); + singleOwnerPlugin = _singleOwnerPlugin; + self = address(this); + // The manifest hash is set this way in this factory just for testing purposes. + // For production factories the manifest hashes should be passed as a constructor argument. + singleOwnerPluginManifestHash = keccak256(abi.encode(singleOwnerPlugin.pluginManifest())); + } + + /** + * create an account, and return its address. + * returns the address even if the account is already deployed. + * Note that during user operation execution, this method is called only if the account is not deployed. + * This method returns an existing account address so that entryPoint.getSenderAddress() would work even after + * account creation + */ + function createAccount(address owner, uint256 salt) public returns (UpgradeableModularAccount) { + address addr = Create2.computeAddress(getSalt(owner, salt), _PROXY_BYTECODE_HASH); + + // short circuit if exists + if (addr.code.length == 0) { + bytes memory pluginInstallData = abi.encode(owner); + // not necessary to check return addr since next call will fail if so + new ERC1967Proxy{salt: getSalt(owner, salt)}(address(accountImplementation), ""); + + // point proxy to actual implementation and init plugins + UpgradeableModularAccount(payable(addr)).initializeDefaultValidation( + address(singleOwnerPlugin), + uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER), + pluginInstallData + ); + } + + return UpgradeableModularAccount(payable(addr)); + } + + /** + * calculate the counterfactual address of this account as it would be returned by createAccount() + */ + function getAddress(address owner, uint256 salt) public view returns (address) { + return Create2.computeAddress(getSalt(owner, salt), _PROXY_BYTECODE_HASH); + } + + function addStake() external payable { + entryPoint.addStake{value: msg.value}(UNSTAKE_DELAY); + } + + function getSalt(address owner, uint256 salt) public pure returns (bytes32) { + return keccak256(abi.encodePacked(owner, salt)); + } +} diff --git a/test/mocks/plugins/BadTransferOwnershipPlugin.sol b/test/mocks/plugins/BadTransferOwnershipPlugin.sol index 6561bb1b..dcb9a935 100644 --- a/test/mocks/plugins/BadTransferOwnershipPlugin.sol +++ b/test/mocks/plugins/BadTransferOwnershipPlugin.sol @@ -33,8 +33,11 @@ contract BadTransferOwnershipPlugin is BasePlugin { PluginManifest memory manifest; manifest.executionFunctions = new ManifestExecutionFunction[](1); - manifest.executionFunctions[0] = - ManifestExecutionFunction({executionSelector: this.evilTransferOwnership.selector, isPublic: true}); + manifest.executionFunctions[0] = ManifestExecutionFunction({ + executionSelector: this.evilTransferOwnership.selector, + isPublic: true, + allowDefaultValidation: false + }); manifest.permittedExecutionSelectors = new bytes4[](1); manifest.permittedExecutionSelectors[0] = ISingleOwnerPlugin.transferOwnership.selector; diff --git a/test/mocks/plugins/ComprehensivePlugin.sol b/test/mocks/plugins/ComprehensivePlugin.sol index ec40368f..8257a2b8 100644 --- a/test/mocks/plugins/ComprehensivePlugin.sol +++ b/test/mocks/plugins/ComprehensivePlugin.sol @@ -133,8 +133,11 @@ contract ComprehensivePlugin is IValidation, IValidationHook, IExecutionHook, Ba PluginManifest memory manifest; manifest.executionFunctions = new ManifestExecutionFunction[](1); - manifest.executionFunctions[0] = - ManifestExecutionFunction({executionSelector: this.foo.selector, isPublic: false}); + manifest.executionFunctions[0] = ManifestExecutionFunction({ + executionSelector: this.foo.selector, + isPublic: false, + allowDefaultValidation: false + }); ManifestFunction memory fooValidationFunction = ManifestFunction({ functionType: ManifestAssociatedFunctionType.SELF, diff --git a/test/mocks/plugins/ExecFromPluginPermissionsMocks.sol b/test/mocks/plugins/ExecFromPluginPermissionsMocks.sol index 11970dfa..1d9f566f 100644 --- a/test/mocks/plugins/ExecFromPluginPermissionsMocks.sol +++ b/test/mocks/plugins/ExecFromPluginPermissionsMocks.sol @@ -186,8 +186,11 @@ contract EFPCallerPluginAnyExternal is BasePlugin { PluginManifest memory manifest; manifest.executionFunctions = new ManifestExecutionFunction[](1); - manifest.executionFunctions[0] = - ManifestExecutionFunction({executionSelector: this.passthroughExecute.selector, isPublic: true}); + manifest.executionFunctions[0] = ManifestExecutionFunction({ + executionSelector: this.passthroughExecute.selector, + isPublic: true, + allowDefaultValidation: false + }); manifest.permitAnyExternalAddress = true; diff --git a/test/mocks/plugins/ManifestValidityMocks.sol b/test/mocks/plugins/ManifestValidityMocks.sol index 302226c0..d5c8ce1a 100644 --- a/test/mocks/plugins/ManifestValidityMocks.sol +++ b/test/mocks/plugins/ManifestValidityMocks.sol @@ -26,8 +26,11 @@ contract BadHookMagicValue_ValidationFunction_Plugin is BasePlugin { PluginManifest memory manifest; manifest.executionFunctions = new ManifestExecutionFunction[](1); - manifest.executionFunctions[0] = - ManifestExecutionFunction({executionSelector: this.foo.selector, isPublic: false}); + manifest.executionFunctions[0] = ManifestExecutionFunction({ + executionSelector: this.foo.selector, + isPublic: false, + allowDefaultValidation: false + }); manifest.validationFunctions = new ManifestAssociatedFunction[](1); manifest.validationFunctions[0] = ManifestAssociatedFunction({ diff --git a/test/mocks/plugins/ReturnDataPluginMocks.sol b/test/mocks/plugins/ReturnDataPluginMocks.sol index 788cb4c7..5ab3a811 100644 --- a/test/mocks/plugins/ReturnDataPluginMocks.sol +++ b/test/mocks/plugins/ReturnDataPluginMocks.sol @@ -38,10 +38,16 @@ contract ResultCreatorPlugin is BasePlugin { PluginManifest memory manifest; manifest.executionFunctions = new ManifestExecutionFunction[](2); - manifest.executionFunctions[0] = - ManifestExecutionFunction({executionSelector: this.foo.selector, isPublic: true}); - manifest.executionFunctions[1] = - ManifestExecutionFunction({executionSelector: this.bar.selector, isPublic: false}); + manifest.executionFunctions[0] = ManifestExecutionFunction({ + executionSelector: this.foo.selector, + isPublic: true, + allowDefaultValidation: false + }); + manifest.executionFunctions[1] = ManifestExecutionFunction({ + executionSelector: this.bar.selector, + isPublic: false, + allowDefaultValidation: false + }); return manifest; } @@ -108,10 +114,16 @@ contract ResultConsumerPlugin is BasePlugin { PluginManifest memory manifest; manifest.executionFunctions = new ManifestExecutionFunction[](2); - manifest.executionFunctions[0] = - ManifestExecutionFunction({executionSelector: this.checkResultEFPFallback.selector, isPublic: true}); - manifest.executionFunctions[1] = - ManifestExecutionFunction({executionSelector: this.checkResultEFPExternal.selector, isPublic: true}); + manifest.executionFunctions[0] = ManifestExecutionFunction({ + executionSelector: this.checkResultEFPFallback.selector, + isPublic: true, + allowDefaultValidation: false + }); + manifest.executionFunctions[1] = ManifestExecutionFunction({ + executionSelector: this.checkResultEFPExternal.selector, + isPublic: true, + allowDefaultValidation: false + }); manifest.permittedExecutionSelectors = new bytes4[](1); manifest.permittedExecutionSelectors[0] = ResultCreatorPlugin.foo.selector; diff --git a/test/mocks/plugins/ValidationPluginMocks.sol b/test/mocks/plugins/ValidationPluginMocks.sol index 443ee0bb..770c6791 100644 --- a/test/mocks/plugins/ValidationPluginMocks.sol +++ b/test/mocks/plugins/ValidationPluginMocks.sol @@ -95,8 +95,11 @@ contract MockUserOpValidationPlugin is MockBaseUserOpValidationPlugin { PluginManifest memory manifest; manifest.executionFunctions = new ManifestExecutionFunction[](1); - manifest.executionFunctions[0] = - ManifestExecutionFunction({executionSelector: this.foo.selector, isPublic: false}); + manifest.executionFunctions[0] = ManifestExecutionFunction({ + executionSelector: this.foo.selector, + isPublic: false, + allowDefaultValidation: false + }); manifest.validationFunctions = new ManifestAssociatedFunction[](1); manifest.validationFunctions[0] = ManifestAssociatedFunction({ @@ -134,8 +137,11 @@ contract MockUserOpValidation1HookPlugin is MockBaseUserOpValidationPlugin { PluginManifest memory manifest; manifest.executionFunctions = new ManifestExecutionFunction[](1); - manifest.executionFunctions[0] = - ManifestExecutionFunction({executionSelector: this.bar.selector, isPublic: false}); + manifest.executionFunctions[0] = ManifestExecutionFunction({ + executionSelector: this.bar.selector, + isPublic: false, + allowDefaultValidation: false + }); ManifestFunction memory userOpValidationFunctionRef = ManifestFunction({ functionType: ManifestAssociatedFunctionType.SELF, @@ -187,8 +193,11 @@ contract MockUserOpValidation2HookPlugin is MockBaseUserOpValidationPlugin { PluginManifest memory manifest; manifest.executionFunctions = new ManifestExecutionFunction[](1); - manifest.executionFunctions[0] = - ManifestExecutionFunction({executionSelector: this.baz.selector, isPublic: false}); + manifest.executionFunctions[0] = ManifestExecutionFunction({ + executionSelector: this.baz.selector, + isPublic: false, + allowDefaultValidation: false + }); ManifestFunction memory userOpValidationFunctionRef = ManifestFunction({ functionType: ManifestAssociatedFunctionType.SELF, diff --git a/test/utils/AccountTestBase.sol b/test/utils/AccountTestBase.sol index 6eca6626..059e9cac 100644 --- a/test/utils/AccountTestBase.sol +++ b/test/utils/AccountTestBase.sol @@ -23,6 +23,9 @@ abstract contract AccountTestBase is OptimizedTest { uint256 public owner1Key; UpgradeableModularAccount public account1; + uint8 public constant SELECTOR_ASSOCIATED_VALIDATION = 0; + uint8 public constant DEFAULT_VALIDATION = 1; + constructor() { entryPoint = new EntryPoint(); (owner1, owner1Key) = makeAddrAndKey("owner1"); @@ -47,7 +50,11 @@ abstract contract AccountTestBase is OptimizedTest { abi.encodeCall(SingleOwnerPlugin.transferOwnership, (address(this))) ) ), - abi.encodePacked(address(singleOwnerPlugin), ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) + abi.encodePacked( + address(singleOwnerPlugin), + ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER, + SELECTOR_ASSOCIATED_VALIDATION + ) ); } From 18261cb5e2413286e0c071d2c4daa6d4a5fcf114 Mon Sep 17 00:00:00 2001 From: Adam Egyed <5456061+adamegyed@users.noreply.github.com> Date: Thu, 20 Jun 2024 16:35:22 -0400 Subject: [PATCH 019/111] feat: [v0.8-develop] associate pre-validation hooks with validation functions (#64) --- src/account/AccountLoupe.sol | 4 +- src/account/AccountStorage.sol | 27 ++--- src/account/PluginManager2.sol | 92 +++++++++++---- src/account/PluginManagerInternals.sol | 87 +------------- src/account/UpgradeableModularAccount.sol | 50 +++----- src/interfaces/IAccountLoupe.sol | 4 +- src/interfaces/IPlugin.sol | 8 +- src/interfaces/IPluginManager.sol | 20 ++-- test/account/AccountExecHooks.t.sol | 110 ------------------ test/account/AccountLoupe.t.sol | 16 ++- test/account/ManifestValidity.t.sol | 30 ----- test/account/ValidationIntersection.t.sol | 25 ++++ .../mocks/DefaultValidationFactoryFixture.sol | 6 +- test/mocks/plugins/ComprehensivePlugin.sol | 35 ------ test/mocks/plugins/ManifestValidityMocks.sol | 49 -------- test/mocks/plugins/ValidationPluginMocks.sol | 28 ----- 16 files changed, 159 insertions(+), 432 deletions(-) delete mode 100644 test/account/ManifestValidity.t.sol delete mode 100644 test/mocks/plugins/ManifestValidityMocks.sol diff --git a/src/account/AccountLoupe.sol b/src/account/AccountLoupe.sol index 9e9053fe..9cde18b4 100644 --- a/src/account/AccountLoupe.sol +++ b/src/account/AccountLoupe.sol @@ -52,14 +52,14 @@ abstract contract AccountLoupe is IAccountLoupe { } /// @inheritdoc IAccountLoupe - function getPreValidationHooks(bytes4 selector) + function getPreValidationHooks(FunctionReference validationFunction) external view override returns (FunctionReference[] memory preValidationHooks) { preValidationHooks = - toFunctionReferenceArray(getAccountStorage().selectorData[selector].preValidationHooks); + toFunctionReferenceArray(getAccountStorage().validationData[validationFunction].preValidationHooks); } /// @inheritdoc IAccountLoupe diff --git a/src/account/AccountStorage.sol b/src/account/AccountStorage.sol index f463a629..61ddabb3 100644 --- a/src/account/AccountStorage.sol +++ b/src/account/AccountStorage.sol @@ -41,17 +41,19 @@ struct SelectorData { bool isPublic; // Whether or not a default validation function may be used to validate this function. bool allowDefaultValidation; - // How many times a `PRE_HOOK_ALWAYS_DENY` has been added for this function. - // Since that is the only type of hook that may overlap, we can use this to track the number of times it has - // been applied, and whether or not the deny should apply. The size `uint48` was chosen somewhat arbitrarily, - // but it packs alongside `plugin` while still leaving some other space in the slot for future packing. - uint48 denyExecutionCount; - // User operation validation and runtime validation share a function reference. + // The execution hooks for this function selector. + EnumerableSet.Bytes32Set executionHooks; + // Which validation functions are associated with this function selector. EnumerableSet.Bytes32Set validations; +} + +struct ValidationData { + // Whether or not this validation can be used as a default validation function. + bool isDefault; + // Whether or not this validation is a signature validator. + bool isSignatureValidation; // The pre validation hooks for this function selector. EnumerableSet.Bytes32Set preValidationHooks; - // The execution hooks for this function selector. - EnumerableSet.Bytes32Set executionHooks; } struct AccountStorage { @@ -63,21 +65,14 @@ struct AccountStorage { mapping(address => PluginData) pluginData; // Execution functions and their associated functions mapping(bytes4 => SelectorData) selectorData; + mapping(FunctionReference validationFunction => ValidationData) validationData; mapping(address caller => mapping(bytes4 selector => bool)) callPermitted; // key = address(calling plugin) || target address mapping(IPlugin => mapping(address => PermittedExternalCallData)) permittedExternalCalls; // For ERC165 introspection mapping(bytes4 => uint256) supportedIfaces; - // Installed plugins capable of signature validation. - EnumerableSet.Bytes32Set signatureValidations; - // Todo: merge this with other validation storage? - EnumerableSet.Bytes32Set defaultValidations; } -// TODO: Change how pre-validation hooks work to allow association with validation, rather than selector. -// Pre signature validation hooks -// mapping(FunctionReference => EnumerableSet.Bytes32Set) preSignatureValidationHooks; - function getAccountStorage() pure returns (AccountStorage storage _storage) { assembly ("memory-safe") { _storage.slot := _ACCOUNT_STORAGE_SLOT diff --git a/src/account/PluginManager2.sol b/src/account/PluginManager2.sol index c8945ef1..9e73e306 100644 --- a/src/account/PluginManager2.sol +++ b/src/account/PluginManager2.sol @@ -6,63 +6,109 @@ import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet import {IPlugin} from "../interfaces/IPlugin.sol"; import {FunctionReference} from "../interfaces/IPluginManager.sol"; import {FunctionReferenceLib} from "../helpers/FunctionReferenceLib.sol"; -import {AccountStorage, getAccountStorage, toSetValue} from "./AccountStorage.sol"; +import {AccountStorage, getAccountStorage, toSetValue, toFunctionReference} from "./AccountStorage.sol"; // Temporary additional functions for a user-controlled install flow for validation functions. abstract contract PluginManager2 { using EnumerableSet for EnumerableSet.Bytes32Set; - error DefaultValidationAlreadySet(address plugin, uint8 functionId); - error ValidationAlreadySet(bytes4 selector, address plugin, uint8 functionId); - error ValidationNotSet(bytes4 selector, address plugin, uint8 functionId); + error DefaultValidationAlreadySet(FunctionReference validationFunction); + error PreValidationAlreadySet(FunctionReference validationFunction, FunctionReference preValidationFunction); + error ValidationAlreadySet(bytes4 selector, FunctionReference validationFunction); + error ValidationNotSet(bytes4 selector, FunctionReference validationFunction); function _installValidation( - address plugin, - uint8 functionId, + FunctionReference validationFunction, bool isDefault, bytes4[] memory selectors, - bytes calldata installData - ) internal { - FunctionReference validationFunction = FunctionReferenceLib.pack(plugin, functionId); - + bytes calldata installData, + bytes memory preValidationHooks + ) + // TODO: flag for signature validation + internal + { AccountStorage storage _storage = getAccountStorage(); + if (preValidationHooks.length > 0) { + (FunctionReference[] memory preValidationFunctions, bytes[] memory initDatas) = + abi.decode(preValidationHooks, (FunctionReference[], bytes[])); + + for (uint256 i = 0; i < preValidationFunctions.length; ++i) { + FunctionReference preValidationFunction = preValidationFunctions[i]; + + if ( + !_storage.validationData[validationFunction].preValidationHooks.add( + toSetValue(preValidationFunction) + ) + ) { + revert PreValidationAlreadySet(validationFunction, preValidationFunction); + } + + if (initDatas[i].length > 0) { + (address preValidationPlugin,) = FunctionReferenceLib.unpack(preValidationFunction); + IPlugin(preValidationPlugin).onInstall(initDatas[i]); + } + } + } + if (isDefault) { - if (!_storage.defaultValidations.add(toSetValue(validationFunction))) { - revert DefaultValidationAlreadySet(plugin, functionId); + if (_storage.validationData[validationFunction].isDefault) { + revert DefaultValidationAlreadySet(validationFunction); } + _storage.validationData[validationFunction].isDefault = true; } for (uint256 i = 0; i < selectors.length; ++i) { bytes4 selector = selectors[i]; if (!_storage.selectorData[selector].validations.add(toSetValue(validationFunction))) { - revert ValidationAlreadySet(selector, plugin, functionId); + revert ValidationAlreadySet(selector, validationFunction); } } - IPlugin(plugin).onInstall(installData); + if (installData.length > 0) { + (address plugin,) = FunctionReferenceLib.unpack(validationFunction); + IPlugin(plugin).onInstall(installData); + } } function _uninstallValidation( - address plugin, - uint8 functionId, + FunctionReference validationFunction, bytes4[] calldata selectors, - bytes calldata uninstallData + bytes calldata uninstallData, + bytes calldata preValidationHookUninstallData ) internal { - FunctionReference validationFunction = FunctionReferenceLib.pack(plugin, functionId); - AccountStorage storage _storage = getAccountStorage(); - // Ignore return value - remove if present, do nothing otherwise. - _storage.defaultValidations.remove(toSetValue(validationFunction)); + _storage.validationData[validationFunction].isDefault = false; + _storage.validationData[validationFunction].isSignatureValidation = false; + bytes[] memory preValidationHookUninstallDatas = abi.decode(preValidationHookUninstallData, (bytes[])); + + // Clear pre validation hooks + EnumerableSet.Bytes32Set storage preValidationHooks = + _storage.validationData[validationFunction].preValidationHooks; + while (preValidationHooks.length() > 0) { + FunctionReference preValidationFunction = toFunctionReference(preValidationHooks.at(0)); + preValidationHooks.remove(toSetValue(preValidationFunction)); + (address preValidationPlugin,) = FunctionReferenceLib.unpack(preValidationFunction); + if (preValidationHookUninstallDatas[0].length > 0) { + IPlugin(preValidationPlugin).onUninstall(preValidationHookUninstallDatas[0]); + } + } + + // Because this function also calls `onUninstall`, and removes the default flag from validation, we must + // assume these selectors passed in to be exhaustive. + // TODO: consider enforcing this from user-supplied install config. for (uint256 i = 0; i < selectors.length; ++i) { bytes4 selector = selectors[i]; if (!_storage.selectorData[selector].validations.remove(toSetValue(validationFunction))) { - revert ValidationNotSet(selector, plugin, functionId); + revert ValidationNotSet(selector, validationFunction); } } - IPlugin(plugin).onUninstall(uninstallData); + if (uninstallData.length > 0) { + (address plugin,) = FunctionReferenceLib.unpack(validationFunction); + IPlugin(plugin).onUninstall(uninstallData); + } } } diff --git a/src/account/PluginManagerInternals.sol b/src/account/PluginManagerInternals.sol index d8966275..4db38b55 100644 --- a/src/account/PluginManagerInternals.sol +++ b/src/account/PluginManagerInternals.sol @@ -113,7 +113,7 @@ abstract contract PluginManagerInternals is IPluginManager { SelectorData storage _selectorData = getAccountStorage().selectorData[selector]; // Fail on duplicate validation functions. Otherwise, dependency validation functions could shadow - // non-depdency validation functions. Then, if a either plugin is uninstall, it would cause a partial + // non-depdency validation functions. Then, if a either plugin is uninstalled, it would cause a partial // uninstall of the other. if (!_selectorData.validations.add(toSetValue(validationFunction))) { revert ValidationFunctionAlreadySet(selector, validationFunction); @@ -157,33 +157,6 @@ abstract contract PluginManagerInternals is IPluginManager { ); } - function _addPreValidationHook(bytes4 selector, FunctionReference preValidationHook) - internal - notNullFunction(preValidationHook) - { - SelectorData storage _selectorData = getAccountStorage().selectorData[selector]; - if (preValidationHook.eq(FunctionReferenceLib._PRE_HOOK_ALWAYS_DENY)) { - // Increment `denyExecutionCount`, because this pre validation hook may be applied multiple times. - _selectorData.denyExecutionCount += 1; - return; - } - _selectorData.preValidationHooks.add(toSetValue(preValidationHook)); - } - - function _removePreValidationHook(bytes4 selector, FunctionReference preValidationHook) - internal - notNullFunction(preValidationHook) - { - SelectorData storage _selectorData = getAccountStorage().selectorData[selector]; - if (preValidationHook.eq(FunctionReferenceLib._PRE_HOOK_ALWAYS_DENY)) { - // Decrement `denyExecutionCount`, because this pre exec hook may be applied multiple times. - _selectorData.denyExecutionCount -= 1; - return; - } - // May ignore return value, as the manifest hash is validated to ensure that the hook exists. - _selectorData.preValidationHooks.remove(toSetValue(preValidationHook)); - } - function _installPlugin( address plugin, bytes32 manifestHash, @@ -288,10 +261,7 @@ abstract contract PluginManagerInternals is IPluginManager { for (uint256 i = 0; i < length; ++i) { ManifestAssociatedFunction memory mv = manifest.validationFunctions[i]; _addValidationFunction( - mv.executionSelector, - _resolveManifestFunction( - mv.associatedFunction, plugin, dependencies, ManifestAssociatedFunctionType.NONE - ) + mv.executionSelector, _resolveManifestFunction(mv.associatedFunction, plugin, dependencies) ); } @@ -299,24 +269,7 @@ abstract contract PluginManagerInternals is IPluginManager { for (uint256 i = 0; i < length; ++i) { FunctionReference signatureValidationFunction = FunctionReferenceLib.pack(plugin, manifest.signatureValidationFunctions[i]); - _storage.signatureValidations.add(toSetValue(signatureValidationFunction)); - } - - // Hooks are not allowed to be provided as dependencies, so we use an empty array for resolving them. - FunctionReference[] memory emptyDependencies; - - length = manifest.preValidationHooks.length; - for (uint256 i = 0; i < length; ++i) { - ManifestAssociatedFunction memory mh = manifest.preValidationHooks[i]; - _addPreValidationHook( - mh.executionSelector, - _resolveManifestFunction( - mh.associatedFunction, - plugin, - emptyDependencies, - ManifestAssociatedFunctionType.PRE_HOOK_ALWAYS_DENY - ) - ); + _storage.validationData[signatureValidationFunction].isSignatureValidation = true; } length = manifest.executionHooks.length; @@ -375,9 +328,6 @@ abstract contract PluginManagerInternals is IPluginManager { // Remove components according to the manifest, in reverse order (by component type) of their installation. - // Hooks are not allowed to be provided as dependencies, so we use an empty array for resolving them. - FunctionReference[] memory emptyDependencies; - length = manifest.executionHooks.length; for (uint256 i = 0; i < length; ++i) { ManifestExecutionHook memory mh = manifest.executionHooks[i]; @@ -385,35 +335,18 @@ abstract contract PluginManagerInternals is IPluginManager { _removeExecHooks(mh.executionSelector, hookFunction, mh.isPreHook, mh.isPostHook); } - length = manifest.preValidationHooks.length; - for (uint256 i = 0; i < length; ++i) { - ManifestAssociatedFunction memory mh = manifest.preValidationHooks[i]; - _removePreValidationHook( - mh.executionSelector, - _resolveManifestFunction( - mh.associatedFunction, - plugin, - emptyDependencies, - ManifestAssociatedFunctionType.PRE_HOOK_ALWAYS_DENY - ) - ); - } - length = manifest.signatureValidationFunctions.length; for (uint256 i = 0; i < length; ++i) { FunctionReference signatureValidationFunction = FunctionReferenceLib.pack(plugin, manifest.signatureValidationFunctions[i]); - _storage.signatureValidations.remove(toSetValue(signatureValidationFunction)); + _storage.validationData[signatureValidationFunction].isSignatureValidation = false; } length = manifest.validationFunctions.length; for (uint256 i = 0; i < length; ++i) { ManifestAssociatedFunction memory mv = manifest.validationFunctions[i]; _removeValidationFunction( - mv.executionSelector, - _resolveManifestFunction( - mv.associatedFunction, plugin, dependencies, ManifestAssociatedFunctionType.NONE - ) + mv.executionSelector, _resolveManifestFunction(mv.associatedFunction, plugin, dependencies) ); } @@ -486,9 +419,7 @@ abstract contract PluginManagerInternals is IPluginManager { function _resolveManifestFunction( ManifestFunction memory manifestFunction, address plugin, - FunctionReference[] memory dependencies, - // Indicates which magic value, if any, is permissible for the function to resolve. - ManifestAssociatedFunctionType allowedMagicValue + FunctionReference[] memory dependencies ) internal pure returns (FunctionReference) { if (manifestFunction.functionType == ManifestAssociatedFunctionType.SELF) { return FunctionReferenceLib.pack(plugin, manifestFunction.functionId); @@ -497,12 +428,6 @@ abstract contract PluginManagerInternals is IPluginManager { revert InvalidPluginManifest(); } return dependencies[manifestFunction.dependencyIndex]; - } else if (manifestFunction.functionType == ManifestAssociatedFunctionType.PRE_HOOK_ALWAYS_DENY) { - if (allowedMagicValue == ManifestAssociatedFunctionType.PRE_HOOK_ALWAYS_DENY) { - return FunctionReferenceLib._PRE_HOOK_ALWAYS_DENY; - } else { - revert InvalidPluginManifest(); - } } return FunctionReferenceLib._EMPTY_FUNCTION_REFERENCE; // Empty checks are done elsewhere } diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 047e73c0..c603b0ca 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -65,7 +65,6 @@ contract UpgradeableModularAccount is event ModularAccountInitialized(IEntryPoint indexed entryPoint); - error AlwaysDenyRule(); error AuthorizeUpgradeReverted(bytes revertReason); error ExecFromPluginNotPermitted(address plugin, bytes4 selector); error ExecFromPluginExternalNotPermitted(address plugin, address target, uint256 value, bytes data); @@ -278,13 +277,6 @@ contract UpgradeableModularAccount is // Revert if the provided `authorization` less than 21 bytes long, rather than right-padding. FunctionReference runtimeValidationFunction = FunctionReference.wrap(bytes21(authorization[:21])); - AccountStorage storage _storage = getAccountStorage(); - - // check if that runtime validation function is allowed to be called - if (_storage.selectorData[execSelector].denyExecutionCount > 0) { - revert AlwaysDenyRule(); - } - // Check if the runtime validation function is allowed to be called bool isDefaultValidation = uint8(authorization[21]) == 1; _checkIfValidationApplies(execSelector, runtimeValidationFunction, isDefaultValidation); @@ -338,35 +330,35 @@ contract UpgradeableModularAccount is /// with user install configs. /// @dev This function is only callable once, and only by the EntryPoint. - function initializeDefaultValidation(address plugin, uint8 functionId, bytes calldata installData) + function initializeDefaultValidation(FunctionReference validationFunction, bytes calldata installData) external initializer { - _installValidation(plugin, functionId, true, new bytes4[](0), installData); + _installValidation(validationFunction, true, new bytes4[](0), installData, bytes("")); emit ModularAccountInitialized(_ENTRY_POINT); } /// @inheritdoc IPluginManager /// @notice May be validated by a default validation. function installValidation( - address plugin, - uint8 functionId, + FunctionReference validationFunction, bool isDefault, - bytes4[] calldata selectors, - bytes calldata installData + bytes4[] memory selectors, + bytes calldata installData, + bytes calldata preValidationHooks ) external wrapNativeFunction { - _installValidation(plugin, functionId, isDefault, selectors, installData); + _installValidation(validationFunction, isDefault, selectors, installData, preValidationHooks); } /// @inheritdoc IPluginManager /// @notice May be validated by a default validation. function uninstallValidation( - address plugin, - uint8 functionId, + FunctionReference validationFunction, bytes4[] calldata selectors, - bytes calldata uninstallData + bytes calldata uninstallData, + bytes calldata preValidationHookUninstallData ) external wrapNativeFunction { - _uninstallValidation(plugin, functionId, selectors, uninstallData); + _uninstallValidation(validationFunction, selectors, uninstallData, preValidationHookUninstallData); } /// @notice ERC165 introspection @@ -402,7 +394,7 @@ contract UpgradeableModularAccount is FunctionReference sigValidation = FunctionReference.wrap(bytes21(signature)); (address plugin, uint8 functionId) = sigValidation.unpack(); - if (!_storage.signatureValidations.contains(toSetValue(sigValidation))) { + if (!_storage.validationData[sigValidation].isSignatureValidation) { revert SignatureValidationInvalid(plugin, functionId); } @@ -435,12 +427,6 @@ contract UpgradeableModularAccount is } bytes4 selector = bytes4(userOp.callData); - AccountStorage storage _storage = getAccountStorage(); - - if (_storage.selectorData[selector].denyExecutionCount > 0) { - revert AlwaysDenyRule(); - } - // Revert if the provided `authorization` less than 21 bytes long, rather than right-padding. FunctionReference userOpValidationFunction = FunctionReference.wrap(bytes21(userOp.signature[:21])); bool isDefaultValidation = uint8(userOp.signature[21]) == 1; @@ -470,7 +456,7 @@ contract UpgradeableModularAccount is // Do preUserOpValidation hooks EnumerableSet.Bytes32Set storage preUserOpValidationHooks = - getAccountStorage().selectorData[selector].preValidationHooks; + getAccountStorage().validationData[userOpValidationFunction].preValidationHooks; uint256 preUserOpValidationHooksLength = preUserOpValidationHooks.length(); for (uint256 i = 0; i < preUserOpValidationHooksLength; ++i) { @@ -508,7 +494,7 @@ contract UpgradeableModularAccount is ) internal { // run all preRuntimeValidation hooks EnumerableSet.Bytes32Set storage preRuntimeValidationHooks = - getAccountStorage().selectorData[bytes4(callData[:4])].preValidationHooks; + getAccountStorage().validationData[runtimeValidationFunction].preValidationHooks; uint256 preRuntimeValidationHooksLength = preRuntimeValidationHooks.length(); for (uint256 i = 0; i < preRuntimeValidationHooksLength; ++i) { @@ -624,10 +610,7 @@ contract UpgradeableModularAccount is // Check that the provided validation function is applicable to the selector if (isDefault) { - if ( - !_defaultValidationAllowed(selector) - || !_storage.defaultValidations.contains(toSetValue(validationFunction)) - ) { + if (!_defaultValidationAllowed(selector) || !_storage.validationData[validationFunction].isDefault) { revert UserOpValidationFunctionMissing(selector); } } else { @@ -654,9 +637,6 @@ contract UpgradeableModularAccount is function _checkPermittedCallerIfNotFromEP() internal view { AccountStorage storage _storage = getAccountStorage(); - if (_storage.selectorData[msg.sig].denyExecutionCount > 0) { - revert AlwaysDenyRule(); - } if ( msg.sender == address(_ENTRY_POINT) || msg.sender == address(this) || _storage.selectorData[msg.sig].isPublic diff --git a/src/interfaces/IAccountLoupe.sol b/src/interfaces/IAccountLoupe.sol index b474149c..490b216c 100644 --- a/src/interfaces/IAccountLoupe.sol +++ b/src/interfaces/IAccountLoupe.sol @@ -29,9 +29,9 @@ interface IAccountLoupe { function getExecutionHooks(bytes4 selector) external view returns (ExecutionHook[] memory); /// @notice Get the pre user op and runtime validation hooks associated with a selector. - /// @param selector The selector to get the hooks for. + /// @param validationFunction The validation function to get the hooks for. /// @return preValidationHooks The pre validation hooks for this selector. - function getPreValidationHooks(bytes4 selector) + function getPreValidationHooks(FunctionReference validationFunction) external view returns (FunctionReference[] memory preValidationHooks); diff --git a/src/interfaces/IPlugin.sol b/src/interfaces/IPlugin.sol index e9ea8045..b34e924f 100644 --- a/src/interfaces/IPlugin.sol +++ b/src/interfaces/IPlugin.sol @@ -12,12 +12,7 @@ enum ManifestAssociatedFunctionType { // Function belongs to this plugin. SELF, // Function belongs to an external plugin provided as a dependency during plugin installation. - DEPENDENCY, - // Resolves to a magic value to always fail in a hook for a given function. - // This is only assignable to pre execution hooks. It should not be used on validation functions themselves, because - // this is equivalent to leaving the validation functions unset. It should not be used in post-exec hooks, because - // if it is known to always revert, that should happen as early as possible to save gas. - PRE_HOOK_ALWAYS_DENY + DEPENDENCY } // forgefmt: disable-end @@ -82,7 +77,6 @@ struct PluginManifest { // Execution functions defined in this plugin to be installed on the MSCA. ManifestExecutionFunction[] executionFunctions; ManifestAssociatedFunction[] validationFunctions; - ManifestAssociatedFunction[] preValidationHooks; ManifestExecutionHook[] executionHooks; uint8[] signatureValidationFunctions; // Plugin execution functions already installed on the MSCA that this plugin will be able to call. diff --git a/src/interfaces/IPluginManager.sol b/src/interfaces/IPluginManager.sol index 1c21aadd..717e1fa0 100644 --- a/src/interfaces/IPluginManager.sol +++ b/src/interfaces/IPluginManager.sol @@ -28,31 +28,29 @@ interface IPluginManager { /// validation. /// TODO: remove or update. /// @dev This does not validate anything against the manifest - the caller must ensure validity. - /// @param plugin The plugin to install. - /// @param functionId The function ID of the validation function to install. + /// @param validationFunction The validation function to install. /// @param isDefault Whether the validation function applies for all selectors in the default pool. /// @param selectors The selectors to install the validation function for. /// @param installData Optional data to be decoded and used by the plugin to setup initial plugin state. function installValidation( - address plugin, - uint8 functionId, + FunctionReference validationFunction, bool isDefault, - bytes4[] calldata selectors, - bytes calldata installData + bytes4[] memory selectors, + bytes calldata installData, + bytes calldata preValidationHooks ) external; /// @notice Uninstall a validation function from a set of execution selectors. /// TODO: remove or update. - /// @param plugin The plugin to uninstall. - /// @param functionId The function ID of the validation function to uninstall. + /// @param validationFunction The validation function to uninstall. /// @param selectors The selectors to uninstall the validation function for. /// @param uninstallData Optional data to be decoded and used by the plugin to clear plugin data for the /// account. function uninstallValidation( - address plugin, - uint8 functionId, + FunctionReference validationFunction, bytes4[] calldata selectors, - bytes calldata uninstallData + bytes calldata uninstallData, + bytes calldata preValidationHookUninstallData ) external; /// @notice Uninstall a plugin from the modular account. diff --git a/test/account/AccountExecHooks.t.sol b/test/account/AccountExecHooks.t.sol index 3d8a0c69..842cf84c 100644 --- a/test/account/AccountExecHooks.t.sol +++ b/test/account/AccountExecHooks.t.sol @@ -3,16 +3,12 @@ pragma solidity ^0.8.19; import { IPlugin, - ManifestAssociatedFunctionType, - ManifestAssociatedFunction, ManifestExecutionHook, ManifestExecutionFunction, - ManifestFunction, PluginManifest } from "../../src/interfaces/IPlugin.sol"; import {IExecutionHook} from "../../src/interfaces/IExecutionHook.sol"; import {FunctionReference} from "../../src/helpers/FunctionReferenceLib.sol"; -import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {MockPlugin} from "../mocks/MockPlugin.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; @@ -166,64 +162,6 @@ contract AccountExecHooksTest is AccountTestBase { _uninstallPlugin(mockPlugin1); } - function test_overlappingPreValidationHooks_install() public { - // Install the first plugin. - _installPlugin1WithPreValidationHook( - _EXEC_SELECTOR, - ManifestFunction({ - functionType: ManifestAssociatedFunctionType.PRE_HOOK_ALWAYS_DENY, - functionId: 0, - dependencyIndex: 0 - }) - ); - - // Expect the call to fail due to the "always deny" pre hook. - vm.breakpoint("a"); - (bool success, bytes memory retData) = address(account1).call(abi.encodeWithSelector(_EXEC_SELECTOR)); - assertFalse(success); - assertEq(retData, abi.encodeWithSelector(UpgradeableModularAccount.AlwaysDenyRule.selector)); - - // Install a second plugin that applies the same pre hook on the same selector. - _installPlugin2WithPreValidationHook( - _EXEC_SELECTOR, - ManifestFunction({ - functionType: ManifestAssociatedFunctionType.PRE_HOOK_ALWAYS_DENY, - functionId: 0, - dependencyIndex: 0 - }) - ); - - // Still expect the call to fail. - (success, retData) = address(account1).call(abi.encodeWithSelector(_EXEC_SELECTOR)); - assertFalse(success); - assertEq(retData, abi.encodeWithSelector(UpgradeableModularAccount.AlwaysDenyRule.selector)); - - vm.stopPrank(); - } - - function test_overlappingPreValidationHooks_uninstall() public { - test_overlappingPreValidationHooks_install(); - - // Uninstall the second plugin. - _uninstallPlugin(mockPlugin2); - - // Expect the pre validation hook of "always deny" to still exist. - (bool success, bytes memory retData) = address(account1).call(abi.encodeWithSelector(_EXEC_SELECTOR)); - assertFalse(success); - assertEq(retData, abi.encodeWithSelector(UpgradeableModularAccount.AlwaysDenyRule.selector)); - - // Uninstall the first plugin. - _uninstallPlugin(mockPlugin1); - - // // Execution selector should no longer exist. - (success, retData) = address(account1).call(abi.encodeWithSelector(_EXEC_SELECTOR)); - assertFalse(success); - assertEq( - retData, - abi.encodeWithSelector(UpgradeableModularAccount.UnrecognizedFunction.selector, _EXEC_SELECTOR) - ); - } - function _installPlugin1WithHooks(ManifestExecutionHook memory execHooks) internal { m1.executionHooks.push(execHooks); mockPlugin1 = new MockPlugin(m1); @@ -243,54 +181,6 @@ contract AccountExecHooksTest is AccountTestBase { }); } - function _installPlugin1WithPreValidationHook(bytes4 selector, ManifestFunction memory preValidationHook) - internal - { - m1.preValidationHooks.push( - ManifestAssociatedFunction({executionSelector: selector, associatedFunction: preValidationHook}) - ); - - mockPlugin1 = new MockPlugin(m1); - manifestHash1 = keccak256(abi.encode(mockPlugin1.pluginManifest())); - - vm.expectEmit(true, true, true, true); - emit ReceivedCall(abi.encodeCall(IPlugin.onInstall, (bytes(""))), 0); - vm.expectEmit(true, true, true, true); - emit PluginInstalled(address(mockPlugin1), manifestHash1, new FunctionReference[](0)); - - vm.prank(address(entryPoint)); - account1.installPlugin({ - plugin: address(mockPlugin1), - manifestHash: manifestHash1, - pluginInstallData: bytes(""), - dependencies: new FunctionReference[](0) - }); - } - - function _installPlugin2WithPreValidationHook(bytes4 selector, ManifestFunction memory preValidationHook) - internal - { - m2.preValidationHooks.push( - ManifestAssociatedFunction({executionSelector: selector, associatedFunction: preValidationHook}) - ); - - mockPlugin2 = new MockPlugin(m2); - manifestHash2 = keccak256(abi.encode(mockPlugin2.pluginManifest())); - - vm.expectEmit(true, true, true, true); - emit ReceivedCall(abi.encodeCall(IPlugin.onInstall, (bytes(""))), 0); - vm.expectEmit(true, true, true, true); - emit PluginInstalled(address(mockPlugin2), manifestHash2, new FunctionReference[](0)); - - vm.prank(address(entryPoint)); - account1.installPlugin({ - plugin: address(mockPlugin2), - manifestHash: manifestHash2, - pluginInstallData: bytes(""), - dependencies: new FunctionReference[](0) - }); - } - function _uninstallPlugin(MockPlugin plugin) internal { vm.expectEmit(true, true, true, true); emit ReceivedCall(abi.encodeCall(IPlugin.onUninstall, (bytes(""))), 0); diff --git a/test/account/AccountLoupe.t.sol b/test/account/AccountLoupe.t.sol index a6ed44eb..fa92ab00 100644 --- a/test/account/AccountLoupe.t.sol +++ b/test/account/AccountLoupe.t.sol @@ -31,6 +31,20 @@ contract AccountLoupeTest is AccountTestBase { ownerValidation = FunctionReferenceLib.pack( address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) ); + + FunctionReference[] memory preValidationHooks = new FunctionReference[](2); + preValidationHooks[0] = FunctionReferenceLib.pack( + address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.PRE_VALIDATION_HOOK_1) + ); + preValidationHooks[1] = FunctionReferenceLib.pack( + address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.PRE_VALIDATION_HOOK_2) + ); + + bytes[] memory installDatas = new bytes[](2); + vm.prank(address(entryPoint)); + account1.installValidation( + ownerValidation, true, new bytes4[](0), bytes(""), abi.encode(preValidationHooks, installDatas) + ); } function test_pluginLoupe_getInstalledPlugins_initial() public { @@ -133,7 +147,7 @@ contract AccountLoupeTest is AccountTestBase { } function test_pluginLoupe_getValidationHooks() public { - FunctionReference[] memory hooks = account1.getPreValidationHooks(comprehensivePlugin.foo.selector); + FunctionReference[] memory hooks = account1.getPreValidationHooks(ownerValidation); assertEq(hooks.length, 2); assertEq( diff --git a/test/account/ManifestValidity.t.sol b/test/account/ManifestValidity.t.sol deleted file mode 100644 index 08c2609d..00000000 --- a/test/account/ManifestValidity.t.sol +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; - -import {PluginManagerInternals} from "../../src/account/PluginManagerInternals.sol"; -import {FunctionReference} from "../../src/helpers/FunctionReferenceLib.sol"; - -import {BadHookMagicValue_ValidationFunction_Plugin} from "../mocks/plugins/ManifestValidityMocks.sol"; -import {AccountTestBase} from "../utils/AccountTestBase.sol"; - -contract ManifestValidityTest is AccountTestBase { - function setUp() public { - _transferOwnershipToTest(); - } - - // Tests that the plugin manager rejects a plugin with a validation function set to "hook always deny" - function test_ManifestValidity_invalid_HookAlwaysDeny_Validation() public { - BadHookMagicValue_ValidationFunction_Plugin plugin = new BadHookMagicValue_ValidationFunction_Plugin(); - - bytes32 manifestHash = keccak256(abi.encode(plugin.pluginManifest())); - - vm.prank(address(entryPoint)); - vm.expectRevert(abi.encodeWithSelector(PluginManagerInternals.InvalidPluginManifest.selector)); - account1.installPlugin({ - plugin: address(plugin), - manifestHash: manifestHash, - pluginInstallData: "", - dependencies: new FunctionReference[](0) - }); - } -} diff --git a/test/account/ValidationIntersection.t.sol b/test/account/ValidationIntersection.t.sol index de0e7ef1..877f8ff9 100644 --- a/test/account/ValidationIntersection.t.sol +++ b/test/account/ValidationIntersection.t.sol @@ -58,12 +58,37 @@ contract ValidationIntersectionTest is AccountTestBase { pluginInstallData: "", dependencies: new FunctionReference[](0) }); + // TODO: change with new install flow + // temporary fix to add the pre-validation hook + FunctionReference[] memory preValidationHooks = new FunctionReference[](1); + preValidationHooks[0] = FunctionReferenceLib.pack({ + addr: address(oneHookPlugin), + functionId: uint8(MockBaseUserOpValidationPlugin.FunctionId.PRE_VALIDATION_HOOK_1) + }); + bytes[] memory installDatas = new bytes[](1); + account1.installValidation( + oneHookValidation, true, new bytes4[](0), bytes(""), abi.encode(preValidationHooks, installDatas) + ); account1.installPlugin({ plugin: address(twoHookPlugin), manifestHash: keccak256(abi.encode(twoHookPlugin.pluginManifest())), pluginInstallData: "", dependencies: new FunctionReference[](0) }); + // temporary fix to add the pre-validation hook + preValidationHooks = new FunctionReference[](2); + preValidationHooks[0] = FunctionReferenceLib.pack({ + addr: address(twoHookPlugin), + functionId: uint8(MockBaseUserOpValidationPlugin.FunctionId.PRE_VALIDATION_HOOK_1) + }); + preValidationHooks[1] = FunctionReferenceLib.pack({ + addr: address(twoHookPlugin), + functionId: uint8(MockBaseUserOpValidationPlugin.FunctionId.PRE_VALIDATION_HOOK_2) + }); + installDatas = new bytes[](2); + account1.installValidation( + twoHookValidation, true, new bytes4[](0), bytes(""), abi.encode(preValidationHooks, installDatas) + ); vm.stopPrank(); } diff --git a/test/mocks/DefaultValidationFactoryFixture.sol b/test/mocks/DefaultValidationFactoryFixture.sol index 5fbefe7c..a4836ad8 100644 --- a/test/mocks/DefaultValidationFactoryFixture.sol +++ b/test/mocks/DefaultValidationFactoryFixture.sol @@ -6,6 +6,7 @@ import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.s import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; +import {FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; import {ISingleOwnerPlugin} from "../../src/plugins/owner/ISingleOwnerPlugin.sol"; import {SingleOwnerPlugin} from "../../src/plugins/owner/SingleOwnerPlugin.sol"; @@ -55,8 +56,9 @@ contract DefaultValidationFactoryFixture is OptimizedTest { // point proxy to actual implementation and init plugins UpgradeableModularAccount(payable(addr)).initializeDefaultValidation( - address(singleOwnerPlugin), - uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER), + FunctionReferenceLib.pack( + address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) + ), pluginInstallData ); } diff --git a/test/mocks/plugins/ComprehensivePlugin.sol b/test/mocks/plugins/ComprehensivePlugin.sol index 8257a2b8..6ef654c7 100644 --- a/test/mocks/plugins/ComprehensivePlugin.sol +++ b/test/mocks/plugins/ComprehensivePlugin.sol @@ -17,7 +17,6 @@ import {IValidation} from "../../../src/interfaces/IValidation.sol"; import {IValidationHook} from "../../../src/interfaces/IValidationHook.sol"; import {IExecutionHook} from "../../../src/interfaces/IExecutionHook.sol"; -import {IStandardExecutor} from "../../../src/interfaces/IStandardExecutor.sol"; import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; contract ComprehensivePlugin is IValidation, IValidationHook, IExecutionHook, BasePlugin { @@ -150,40 +149,6 @@ contract ComprehensivePlugin is IValidation, IValidationHook, IExecutionHook, Ba associatedFunction: fooValidationFunction }); - manifest.preValidationHooks = new ManifestAssociatedFunction[](4); - manifest.preValidationHooks[0] = ManifestAssociatedFunction({ - executionSelector: this.foo.selector, - associatedFunction: ManifestFunction({ - functionType: ManifestAssociatedFunctionType.SELF, - functionId: uint8(FunctionId.PRE_VALIDATION_HOOK_1), - dependencyIndex: 0 // Unused. - }) - }); - manifest.preValidationHooks[1] = ManifestAssociatedFunction({ - executionSelector: this.foo.selector, - associatedFunction: ManifestFunction({ - functionType: ManifestAssociatedFunctionType.SELF, - functionId: uint8(FunctionId.PRE_VALIDATION_HOOK_2), - dependencyIndex: 0 // Unused. - }) - }); - manifest.preValidationHooks[2] = ManifestAssociatedFunction({ - executionSelector: IStandardExecutor.execute.selector, - associatedFunction: ManifestFunction({ - functionType: ManifestAssociatedFunctionType.SELF, - functionId: uint8(FunctionId.PRE_VALIDATION_HOOK_1), - dependencyIndex: 0 // Unused. - }) - }); - manifest.preValidationHooks[3] = ManifestAssociatedFunction({ - executionSelector: IStandardExecutor.execute.selector, - associatedFunction: ManifestFunction({ - functionType: ManifestAssociatedFunctionType.SELF, - functionId: uint8(FunctionId.PRE_VALIDATION_HOOK_2), - dependencyIndex: 0 // Unused. - }) - }); - manifest.executionHooks = new ManifestExecutionHook[](3); manifest.executionHooks[0] = ManifestExecutionHook({ executionSelector: this.foo.selector, diff --git a/test/mocks/plugins/ManifestValidityMocks.sol b/test/mocks/plugins/ManifestValidityMocks.sol deleted file mode 100644 index d5c8ce1a..00000000 --- a/test/mocks/plugins/ManifestValidityMocks.sol +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; - -import { - ManifestFunction, - ManifestExecutionFunction, - ManifestAssociatedFunctionType, - ManifestAssociatedFunction, - PluginManifest, - PluginMetadata -} from "../../../src/interfaces/IPlugin.sol"; - -import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; - -// solhint-disable-next-line contract-name-camelcase -contract BadHookMagicValue_ValidationFunction_Plugin is BasePlugin { - function onInstall(bytes calldata) external override {} - - function onUninstall(bytes calldata) external override {} - - function foo() external pure returns (bytes32) { - return keccak256("bar"); - } - - function pluginManifest() external pure override returns (PluginManifest memory) { - PluginManifest memory manifest; - - manifest.executionFunctions = new ManifestExecutionFunction[](1); - manifest.executionFunctions[0] = ManifestExecutionFunction({ - executionSelector: this.foo.selector, - isPublic: false, - allowDefaultValidation: false - }); - - manifest.validationFunctions = new ManifestAssociatedFunction[](1); - manifest.validationFunctions[0] = ManifestAssociatedFunction({ - executionSelector: this.foo.selector, - associatedFunction: ManifestFunction({ - functionType: ManifestAssociatedFunctionType.PRE_HOOK_ALWAYS_DENY, - functionId: 0, - dependencyIndex: 0 - }) - }); - - return manifest; - } - - function pluginMetadata() external pure override returns (PluginMetadata memory) {} -} diff --git a/test/mocks/plugins/ValidationPluginMocks.sol b/test/mocks/plugins/ValidationPluginMocks.sol index 770c6791..d5f75e99 100644 --- a/test/mocks/plugins/ValidationPluginMocks.sol +++ b/test/mocks/plugins/ValidationPluginMocks.sol @@ -154,16 +154,6 @@ contract MockUserOpValidation1HookPlugin is MockBaseUserOpValidationPlugin { associatedFunction: userOpValidationFunctionRef }); - manifest.preValidationHooks = new ManifestAssociatedFunction[](1); - manifest.preValidationHooks[0] = ManifestAssociatedFunction({ - executionSelector: this.bar.selector, - associatedFunction: ManifestFunction({ - functionType: ManifestAssociatedFunctionType.SELF, - functionId: uint8(FunctionId.PRE_VALIDATION_HOOK_1), - dependencyIndex: 0 // Unused. - }) - }); - return manifest; } } @@ -210,24 +200,6 @@ contract MockUserOpValidation2HookPlugin is MockBaseUserOpValidationPlugin { associatedFunction: userOpValidationFunctionRef }); - manifest.preValidationHooks = new ManifestAssociatedFunction[](2); - manifest.preValidationHooks[0] = ManifestAssociatedFunction({ - executionSelector: this.baz.selector, - associatedFunction: ManifestFunction({ - functionType: ManifestAssociatedFunctionType.SELF, - functionId: uint8(FunctionId.PRE_VALIDATION_HOOK_1), - dependencyIndex: 0 // Unused. - }) - }); - manifest.preValidationHooks[1] = ManifestAssociatedFunction({ - executionSelector: this.baz.selector, - associatedFunction: ManifestFunction({ - functionType: ManifestAssociatedFunctionType.SELF, - functionId: uint8(FunctionId.PRE_VALIDATION_HOOK_2), - dependencyIndex: 0 // Unused. - }) - }); - return manifest; } } From 400e83392461cf93a6ee6762e188765925d5ed4e Mon Sep 17 00:00:00 2001 From: Adam Egyed <5456061+adamegyed@users.noreply.github.com> Date: Thu, 20 Jun 2024 16:49:06 -0400 Subject: [PATCH 020/111] feat: [v0.8-develop] remove execute from plugin (#65) --- src/account/AccountStorage.sol | 16 -- src/account/PluginManagerInternals.sol | 67 +----- src/account/UpgradeableModularAccount.sol | 85 +------ src/helpers/KnownSelectors.sol | 6 +- src/interfaces/IPlugin.sol | 12 - src/interfaces/IPluginExecutor.sol | 33 --- src/interfaces/IStandardExecutor.sol | 10 + test/account/AccountExecHooks.t.sol | 10 +- test/account/AccountReturnData.t.sol | 10 +- .../ExecuteFromPluginPermissions.t.sol | 210 ------------------ test/account/PermittedCallPermissions.t.sol | 61 +++++ test/account/UpgradeableModularAccount.t.sol | 8 +- .../plugins/BadTransferOwnershipPlugin.sol | 55 ----- .../ExecFromPluginPermissionsMocks.sol | 209 ----------------- test/mocks/plugins/PermittedCallMocks.sol | 43 ++++ test/mocks/plugins/ReturnDataPluginMocks.sol | 91 ++++---- 16 files changed, 178 insertions(+), 748 deletions(-) delete mode 100644 src/interfaces/IPluginExecutor.sol delete mode 100644 test/account/ExecuteFromPluginPermissions.t.sol create mode 100644 test/account/PermittedCallPermissions.t.sol delete mode 100644 test/mocks/plugins/BadTransferOwnershipPlugin.sol delete mode 100644 test/mocks/plugins/ExecFromPluginPermissionsMocks.sol create mode 100644 test/mocks/plugins/PermittedCallMocks.sol diff --git a/src/account/AccountStorage.sol b/src/account/AccountStorage.sol index 61ddabb3..0a992a33 100644 --- a/src/account/AccountStorage.sol +++ b/src/account/AccountStorage.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.25; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import {IPlugin} from "../interfaces/IPlugin.sol"; import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; import {FunctionReference} from "../interfaces/IPluginManager.sol"; @@ -11,25 +10,12 @@ import {FunctionReference} from "../interfaces/IPluginManager.sol"; bytes32 constant _ACCOUNT_STORAGE_SLOT = 0x9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e40; struct PluginData { - bool anyExternalExecPermitted; - // boolean to indicate if the plugin can spend native tokens from the account. - bool canSpendNativeToken; bytes32 manifestHash; FunctionReference[] dependencies; // Tracks the number of times this plugin has been used as a dependency function uint256 dependentCount; } -// Represents data associated with a plugin's permission to use `executeFromPluginExternal` -// to interact with contracts and addresses external to the account and its plugins. -struct PermittedExternalCallData { - // Is this address on the permitted addresses list? If it is, we either have a - // list of allowed selectors, or the flag that allows any selector. - bool addressPermitted; - bool anySelectorPermitted; - mapping(bytes4 => bool) permittedSelectors; -} - // Represents data associated with a specifc function selector. struct SelectorData { // The plugin that implements this execution function. @@ -67,8 +53,6 @@ struct AccountStorage { mapping(bytes4 => SelectorData) selectorData; mapping(FunctionReference validationFunction => ValidationData) validationData; mapping(address caller => mapping(bytes4 selector => bool)) callPermitted; - // key = address(calling plugin) || target address - mapping(IPlugin => mapping(address => PermittedExternalCallData)) permittedExternalCalls; // For ERC165 introspection mapping(bytes4 => uint256) supportedIfaces; } diff --git a/src/account/PluginManagerInternals.sol b/src/account/PluginManagerInternals.sol index 4db38b55..2ec06f81 100644 --- a/src/account/PluginManagerInternals.sol +++ b/src/account/PluginManagerInternals.sol @@ -11,19 +11,12 @@ import { ManifestFunction, ManifestAssociatedFunctionType, ManifestAssociatedFunction, - ManifestExternalCallPermission, PluginManifest } from "../interfaces/IPlugin.sol"; import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; import {FunctionReference, IPluginManager} from "../interfaces/IPluginManager.sol"; -import { - AccountStorage, - getAccountStorage, - SelectorData, - toSetValue, - PermittedExternalCallData -} from "./AccountStorage.sol"; import {KnownSelectors} from "../helpers/KnownSelectors.sol"; +import {AccountStorage, getAccountStorage, SelectorData, toSetValue} from "./AccountStorage.sol"; abstract contract PluginManagerInternals is IPluginManager { using EnumerableSet for EnumerableSet.Bytes32Set; @@ -211,11 +204,6 @@ abstract contract PluginManagerInternals is IPluginManager { // Update components according to the manifest. - // Mark whether or not this plugin may spend native token amounts - if (manifest.canSpendNativeToken) { - _storage.pluginData[plugin].canSpendNativeToken = true; - } - length = manifest.executionFunctions.length; for (uint256 i = 0; i < length; ++i) { bytes4 selector = manifest.executionFunctions[i].executionSelector; @@ -232,31 +220,6 @@ abstract contract PluginManagerInternals is IPluginManager { _storage.callPermitted[plugin][manifest.permittedExecutionSelectors[i]] = true; } - // Add the permitted external calls to the account. - if (manifest.permitAnyExternalAddress) { - _storage.pluginData[plugin].anyExternalExecPermitted = true; - } else { - // Only store the specific permitted external calls if "permit any" flag was not set. - length = manifest.permittedExternalCalls.length; - for (uint256 i = 0; i < length; ++i) { - ManifestExternalCallPermission memory externalCallPermission = manifest.permittedExternalCalls[i]; - - PermittedExternalCallData storage permittedExternalCallData = - _storage.permittedExternalCalls[IPlugin(plugin)][externalCallPermission.externalAddress]; - - permittedExternalCallData.addressPermitted = true; - - if (externalCallPermission.permitAnySelector) { - permittedExternalCallData.anySelectorPermitted = true; - } else { - uint256 externalContractSelectorsLength = externalCallPermission.selectors.length; - for (uint256 j = 0; j < externalContractSelectorsLength; ++j) { - permittedExternalCallData.permittedSelectors[externalCallPermission.selectors[j]] = true; - } - } - } - } - length = manifest.validationFunctions.length; for (uint256 i = 0; i < length; ++i) { ManifestAssociatedFunction memory mv = manifest.validationFunctions[i]; @@ -350,34 +313,6 @@ abstract contract PluginManagerInternals is IPluginManager { ); } - // remove external call permissions - - if (manifest.permitAnyExternalAddress) { - // Only clear if it was set during install time - _storage.pluginData[plugin].anyExternalExecPermitted = false; - } else { - // Only clear the specific permitted external calls if "permit any" flag was not set. - length = manifest.permittedExternalCalls.length; - for (uint256 i = 0; i < length; ++i) { - ManifestExternalCallPermission memory externalCallPermission = manifest.permittedExternalCalls[i]; - - PermittedExternalCallData storage permittedExternalCallData = - _storage.permittedExternalCalls[IPlugin(plugin)][externalCallPermission.externalAddress]; - - permittedExternalCallData.addressPermitted = false; - - // Only clear this flag if it was set in the constructor. - if (externalCallPermission.permitAnySelector) { - permittedExternalCallData.anySelectorPermitted = false; - } else { - uint256 externalContractSelectorsLength = externalCallPermission.selectors.length; - for (uint256 j = 0; j < externalContractSelectorsLength; ++j) { - permittedExternalCallData.permittedSelectors[externalCallPermission.selectors[j]] = false; - } - } - } - } - length = manifest.permittedExecutionSelectors.length; for (uint256 i = 0; i < length; ++i) { _storage.callPermitted[plugin][manifest.permittedExecutionSelectors[i]] = false; diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index c603b0ca..169b17a3 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -15,7 +15,6 @@ import {IPlugin, PluginManifest} from "../interfaces/IPlugin.sol"; import {IValidation} from "../interfaces/IValidation.sol"; import {IValidationHook} from "../interfaces/IValidationHook.sol"; import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; -import {IPluginExecutor} from "../interfaces/IPluginExecutor.sol"; import {FunctionReference, IPluginManager} from "../interfaces/IPluginManager.sol"; import {IStandardExecutor, Call} from "../interfaces/IStandardExecutor.sol"; import {AccountExecutor} from "./AccountExecutor.sol"; @@ -39,7 +38,6 @@ contract UpgradeableModularAccount is BaseAccount, IERC165, IERC1271, - IPluginExecutor, IStandardExecutor, PluginManagerInternals, PluginManager2, @@ -185,88 +183,7 @@ contract UpgradeableModularAccount is } } - /// @inheritdoc IPluginExecutor - function executeFromPlugin(bytes calldata data) external payable override returns (bytes memory) { - bytes4 selector = bytes4(data[:4]); - address callingPlugin = msg.sender; - - AccountStorage storage _storage = getAccountStorage(); - - if (!_storage.callPermitted[callingPlugin][selector]) { - revert ExecFromPluginNotPermitted(callingPlugin, selector); - } - - address execFunctionPlugin = _storage.selectorData[selector].plugin; - - if (execFunctionPlugin == address(0)) { - revert UnrecognizedFunction(selector); - } - - PostExecToRun[] memory postExecHooks = _doPreExecHooks(selector, data); - - (bool success, bytes memory returnData) = execFunctionPlugin.call(data); - - if (!success) { - assembly ("memory-safe") { - revert(add(returnData, 32), mload(returnData)) - } - } - - _doCachedPostExecHooks(postExecHooks); - - return returnData; - } - - /// @inheritdoc IPluginExecutor - function executeFromPluginExternal(address target, uint256 value, bytes calldata data) - external - payable - returns (bytes memory) - { - bytes4 selector = bytes4(data); - AccountStorage storage _storage = getAccountStorage(); - - // Make sure plugin is allowed to spend native token. - if (value > 0 && value > msg.value && !_storage.pluginData[msg.sender].canSpendNativeToken) { - revert NativeTokenSpendingNotPermitted(msg.sender); - } - - // Check the caller plugin's permission to make this call - - // Check the target contract permission. - // This first checks that the intended target is permitted at all. If it is, then it checks if any selector - // is permitted. If any selector is permitted, then it skips the selector-level permission check. - // If only a subset of selectors are permitted, then it also checks the selector-level permission. - // By checking in the order of [address specified with any selector allowed], [any address allowed], - // [address specified and selector specified], along with the extra bool `permittedCall`, we can - // reduce the number of `sload`s in the worst-case from 3 down to 2. - bool targetContractPermittedCall = _storage.permittedExternalCalls[IPlugin(msg.sender)][target] - .addressPermitted - && ( - _storage.permittedExternalCalls[IPlugin(msg.sender)][target].anySelectorPermitted - || _storage.permittedExternalCalls[IPlugin(msg.sender)][target].permittedSelectors[selector] - ); - - // If the target contract is not permitted, check if the caller plugin is permitted to make any external - // calls. - if (!(targetContractPermittedCall || _storage.pluginData[msg.sender].anyExternalExecPermitted)) { - revert ExecFromPluginExternalNotPermitted(msg.sender, target, value, data); - } - - // Run any pre exec hooks for this selector - PostExecToRun[] memory postExecHooks = - _doPreExecHooks(IPluginExecutor.executeFromPluginExternal.selector, msg.data); - - // Perform the external call - bytes memory returnData = _exec(target, value, data); - - // Run any post exec hooks for this selector - _doCachedPostExecHooks(postExecHooks); - - return returnData; - } - - /// @inheritdoc IPluginExecutor + /// @inheritdoc IStandardExecutor function executeWithAuthorization(bytes calldata data, bytes calldata authorization) external payable diff --git a/src/helpers/KnownSelectors.sol b/src/helpers/KnownSelectors.sol index 6d737172..e5244d2c 100644 --- a/src/helpers/KnownSelectors.sol +++ b/src/helpers/KnownSelectors.sol @@ -10,7 +10,6 @@ import {IPaymaster} from "@eth-infinitism/account-abstraction/interfaces/IPaymas import {IAccountLoupe} from "../interfaces/IAccountLoupe.sol"; import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; import {IPlugin} from "../interfaces/IPlugin.sol"; -import {IPluginExecutor} from "../interfaces/IPluginExecutor.sol"; import {IPluginManager} from "../interfaces/IPluginManager.sol"; import {IStandardExecutor} from "../interfaces/IStandardExecutor.sol"; import {IValidation} from "../interfaces/IValidation.sol"; @@ -32,10 +31,7 @@ library KnownSelectors { || selector == UUPSUpgradeable.upgradeToAndCall.selector // check against IStandardExecutor methods || selector == IStandardExecutor.execute.selector || selector == IStandardExecutor.executeBatch.selector - // check against IPluginExecutor methods - || selector == IPluginExecutor.executeFromPlugin.selector - || selector == IPluginExecutor.executeFromPluginExternal.selector - || selector == IPluginExecutor.executeWithAuthorization.selector + || selector == IStandardExecutor.executeWithAuthorization.selector // check against IAccountLoupe methods || selector == IAccountLoupe.getExecutionFunctionHandler.selector || selector == IAccountLoupe.getValidations.selector diff --git a/src/interfaces/IPlugin.sol b/src/interfaces/IPlugin.sol index b34e924f..3241e497 100644 --- a/src/interfaces/IPlugin.sol +++ b/src/interfaces/IPlugin.sol @@ -47,12 +47,6 @@ struct ManifestExecutionHook { bool isPostHook; } -struct ManifestExternalCallPermission { - address externalAddress; - bool permitAnySelector; - bytes4[] selectors; -} - struct SelectorPermission { bytes4 functionSelector; string permissionDescription; @@ -81,12 +75,6 @@ struct PluginManifest { uint8[] signatureValidationFunctions; // Plugin execution functions already installed on the MSCA that this plugin will be able to call. bytes4[] permittedExecutionSelectors; - // Boolean to indicate whether the plugin can call any external address. - bool permitAnyExternalAddress; - // Boolean to indicate whether the plugin needs access to spend native tokens of the account. If false, the - // plugin MUST still be able to spend up to the balance that it sends to the account in the same call. - bool canSpendNativeToken; - ManifestExternalCallPermission[] permittedExternalCalls; // List of ERC-165 interface IDs to add to account to support introspection checks. This MUST NOT include // IPlugin's interface ID. bytes4[] interfaceIds; diff --git a/src/interfaces/IPluginExecutor.sol b/src/interfaces/IPluginExecutor.sol deleted file mode 100644 index 2ff5ed11..00000000 --- a/src/interfaces/IPluginExecutor.sol +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.25; - -interface IPluginExecutor { - /// @notice Execute a call from a plugin to another plugin, via an execution function installed on the account. - /// @dev Plugins are not allowed to call native functions on the account. Permissions must be granted to the - /// calling plugin for the call to go through. - /// @param data The calldata to send to the plugin. - /// @return The return data from the call. - function executeFromPlugin(bytes calldata data) external payable returns (bytes memory); - - /// @notice Execute a call from a plugin to a non-plugin address. - /// @dev If the target is a plugin, the call SHOULD revert. Permissions must be granted to the calling plugin - /// for the call to go through. - /// @param target The address to be called. - /// @param value The value to send with the call. - /// @param data The calldata to send to the target. - /// @return The return data from the call. - function executeFromPluginExternal(address target, uint256 value, bytes calldata data) - external - payable - returns (bytes memory); - - /// @notice Execute a call using a specified runtime validation, as given in the first 21 bytes of - /// `authorization`. - /// @param data The calldata to send to the account. - /// @param authorization The authorization data to use for the call. The first 21 bytes specifies which runtime - /// validation to use, and the rest is sent as a parameter to runtime validation. - function executeWithAuthorization(bytes calldata data, bytes calldata authorization) - external - payable - returns (bytes memory); -} diff --git a/src/interfaces/IStandardExecutor.sol b/src/interfaces/IStandardExecutor.sol index 30b6deee..fbeb89c4 100644 --- a/src/interfaces/IStandardExecutor.sol +++ b/src/interfaces/IStandardExecutor.sol @@ -25,4 +25,14 @@ interface IStandardExecutor { /// @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 a specified runtime validation, as given in the first 21 bytes of + /// `authorization`. + /// @param data The calldata to send to the account. + /// @param authorization The authorization data to use for the call. The first 21 bytes specifies which runtime + /// validation to use, and the rest is sent as a parameter to runtime validation. + function executeWithAuthorization(bytes calldata data, bytes calldata authorization) + external + payable + returns (bytes memory); } diff --git a/test/account/AccountExecHooks.t.sol b/test/account/AccountExecHooks.t.sol index 842cf84c..6a3b7d8c 100644 --- a/test/account/AccountExecHooks.t.sol +++ b/test/account/AccountExecHooks.t.sol @@ -24,8 +24,8 @@ contract AccountExecHooksTest is AccountTestBase { uint8 internal constant _POST_HOOK_FUNCTION_ID_2 = 2; uint8 internal constant _BOTH_HOOKS_FUNCTION_ID_3 = 3; - PluginManifest public m1; - PluginManifest public m2; + PluginManifest internal _m1; + PluginManifest internal _m2; event PluginInstalled(address indexed plugin, bytes32 manifestHash, FunctionReference[] dependencies); event PluginUninstalled(address indexed plugin, bool indexed callbacksSucceeded); @@ -35,7 +35,7 @@ contract AccountExecHooksTest is AccountTestBase { function setUp() public { _transferOwnershipToTest(); - m1.executionFunctions.push( + _m1.executionFunctions.push( ManifestExecutionFunction({ executionSelector: _EXEC_SELECTOR, isPublic: true, @@ -163,8 +163,8 @@ contract AccountExecHooksTest is AccountTestBase { } function _installPlugin1WithHooks(ManifestExecutionHook memory execHooks) internal { - m1.executionHooks.push(execHooks); - mockPlugin1 = new MockPlugin(m1); + _m1.executionHooks.push(execHooks); + mockPlugin1 = new MockPlugin(_m1); manifestHash1 = keccak256(abi.encode(mockPlugin1.pluginManifest())); vm.expectEmit(true, true, true, true); diff --git a/test/account/AccountReturnData.t.sol b/test/account/AccountReturnData.t.sol index b3d2419a..8e8f3215 100644 --- a/test/account/AccountReturnData.t.sol +++ b/test/account/AccountReturnData.t.sol @@ -99,16 +99,16 @@ contract AccountReturnDataTest is AccountTestBase { assertEq(result2, keccak256("foo")); } - // Tests the ability to read data via executeFromPlugin routing to fallback functions + // Tests the ability to read data via routing to fallback functions function test_returnData_execFromPlugin_fallback() public { - bool result = ResultConsumerPlugin(address(account1)).checkResultEFPFallback(keccak256("bar")); + bool result = ResultConsumerPlugin(address(account1)).checkResultFallback(keccak256("bar")); assertTrue(result); } - // Tests the ability to read data via executeFromPluginExternal - function test_returnData_execFromPlugin_execute() public { - bool result = ResultConsumerPlugin(address(account1)).checkResultEFPExternal( + // Tests the ability to read data via executeWithAuthorization + function test_returnData_authorized_exec() public { + bool result = ResultConsumerPlugin(address(account1)).checkResultExecuteWithAuthorization( address(regularResultContract), keccak256("bar") ); diff --git a/test/account/ExecuteFromPluginPermissions.t.sol b/test/account/ExecuteFromPluginPermissions.t.sol deleted file mode 100644 index 6a95ba18..00000000 --- a/test/account/ExecuteFromPluginPermissions.t.sol +++ /dev/null @@ -1,210 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; - -import {console} from "forge-std/Test.sol"; - -import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {FunctionReference} from "../../src/helpers/FunctionReferenceLib.sol"; - -import {Counter} from "../mocks/Counter.sol"; -import {ResultCreatorPlugin} from "../mocks/plugins/ReturnDataPluginMocks.sol"; -import {EFPCallerPlugin, EFPCallerPluginAnyExternal} from "../mocks/plugins/ExecFromPluginPermissionsMocks.sol"; -import {AccountTestBase} from "../utils/AccountTestBase.sol"; - -contract ExecuteFromPluginPermissionsTest is AccountTestBase { - Counter public counter1; - Counter public counter2; - Counter public counter3; - ResultCreatorPlugin public resultCreatorPlugin; - - EFPCallerPlugin public efpCallerPlugin; - EFPCallerPluginAnyExternal public efpCallerPluginAnyExternal; - - function setUp() public { - _transferOwnershipToTest(); - - // Initialize the interaction targets - counter1 = new Counter(); - counter2 = new Counter(); - counter3 = new Counter(); - resultCreatorPlugin = new ResultCreatorPlugin(); - - // Initialize the EFP caller plugins, which will attempt to use the permissions system to authorize calls. - efpCallerPlugin = new EFPCallerPlugin(address(counter1), address(counter2), address(counter3)); - efpCallerPluginAnyExternal = new EFPCallerPluginAnyExternal(); - - // Add the result creator plugin to the account - bytes32 resultCreatorManifestHash = keccak256(abi.encode(resultCreatorPlugin.pluginManifest())); - vm.prank(address(entryPoint)); - account1.installPlugin({ - plugin: address(resultCreatorPlugin), - manifestHash: resultCreatorManifestHash, - pluginInstallData: "", - dependencies: new FunctionReference[](0) - }); - // Add the EFP caller plugin to the account - bytes32 efpCallerManifestHash = keccak256(abi.encode(efpCallerPlugin.pluginManifest())); - vm.prank(address(entryPoint)); - account1.installPlugin({ - plugin: address(efpCallerPlugin), - manifestHash: efpCallerManifestHash, - pluginInstallData: "", - dependencies: new FunctionReference[](0) - }); - - // Add the EFP caller plugin with any external permissions to the account - bytes32 efpCallerAnyExternalManifestHash = - keccak256(abi.encode(efpCallerPluginAnyExternal.pluginManifest())); - vm.prank(address(entryPoint)); - account1.installPlugin({ - plugin: address(efpCallerPluginAnyExternal), - manifestHash: efpCallerAnyExternalManifestHash, - pluginInstallData: "", - dependencies: new FunctionReference[](0) - }); - } - - // Report the addresses to be used in the address constants in ExecFromPluginPermissionsMocks.sol - function test_getPermissionsTestAddresses() public view { - // solhint-disable no-console - console.log("counter1 address: %s", address(counter1)); - console.log("counter2 address: %s", address(counter2)); - console.log("counter3 address: %s", address(counter3)); - console.log("resultCreatorPlugin address: %s", address(resultCreatorPlugin)); - // solhint-enable no-console - } - - function test_executeFromPluginAllowed() public { - bytes memory result = EFPCallerPlugin(address(account1)).useEFPPermissionAllowed(); - bytes32 actual = abi.decode(result, (bytes32)); - - assertEq(actual, keccak256("bar")); - } - - function test_executeFromPluginNotAllowed() public { - vm.expectRevert( - abi.encodeWithSelector( - UpgradeableModularAccount.ExecFromPluginNotPermitted.selector, - address(efpCallerPlugin), - ResultCreatorPlugin.bar.selector - ) - ); - EFPCallerPlugin(address(account1)).useEFPPermissionNotAllowed(); - } - - function test_executeFromPluginExternal_Allowed_IndividualSelectors() public { - EFPCallerPlugin(address(account1)).setNumberCounter1(17); - uint256 retrievedNumber = EFPCallerPlugin(address(account1)).getNumberCounter1(); - - assertEq(retrievedNumber, 17); - } - - function test_executeFromPluginExternal_NotAlowed_IndividualSelectors() public { - EFPCallerPlugin(address(account1)).setNumberCounter1(17); - - // Call to increment should fail - vm.expectRevert( - abi.encodeWithSelector( - UpgradeableModularAccount.ExecFromPluginExternalNotPermitted.selector, - address(efpCallerPlugin), - address(counter1), - 0, - abi.encodePacked(Counter.increment.selector) - ) - ); - EFPCallerPlugin(address(account1)).incrementCounter1(); - - uint256 retrievedNumber = EFPCallerPlugin(address(account1)).getNumberCounter1(); - - assertEq(retrievedNumber, 17); - } - - function test_executeFromPluginExternal_Allowed_AllSelectors() public { - EFPCallerPlugin(address(account1)).setNumberCounter2(17); - EFPCallerPlugin(address(account1)).incrementCounter2(); - uint256 retrievedNumber = EFPCallerPlugin(address(account1)).getNumberCounter2(); - - assertEq(retrievedNumber, 18); - } - - function test_executeFromPluginExternal_NotAllowed_AllSelectors() public { - vm.expectRevert( - abi.encodeWithSelector( - UpgradeableModularAccount.ExecFromPluginExternalNotPermitted.selector, - address(efpCallerPlugin), - address(counter3), - 0, - abi.encodeWithSelector(Counter.setNumber.selector, uint256(17)) - ) - ); - EFPCallerPlugin(address(account1)).setNumberCounter3(17); - - // Call to increment should fail - vm.expectRevert( - abi.encodeWithSelector( - UpgradeableModularAccount.ExecFromPluginExternalNotPermitted.selector, - address(efpCallerPlugin), - address(counter3), - 0, - abi.encodePacked(Counter.increment.selector) - ) - ); - EFPCallerPlugin(address(account1)).incrementCounter3(); - - vm.expectRevert( - abi.encodeWithSelector( - UpgradeableModularAccount.ExecFromPluginExternalNotPermitted.selector, - address(efpCallerPlugin), - address(counter3), - 0, - abi.encodePacked(bytes4(keccak256("number()"))) - ) - ); - EFPCallerPlugin(address(account1)).getNumberCounter3(); - - // Validate no state changes - assert(counter3.number() == 0); - } - - function test_executeFromPluginExternal_Allowed_AnyContract() public { - // Run full workflow for counter 1 - - EFPCallerPluginAnyExternal(address(account1)).passthroughExecute( - address(counter1), 0, abi.encodeCall(Counter.setNumber, (17)) - ); - uint256 retrievedNumber = counter1.number(); - assertEq(retrievedNumber, 17); - - EFPCallerPluginAnyExternal(address(account1)).passthroughExecute( - address(counter1), 0, abi.encodeCall(Counter.increment, ()) - ); - retrievedNumber = counter1.number(); - assertEq(retrievedNumber, 18); - - bytes memory result = EFPCallerPluginAnyExternal(address(account1)).passthroughExecute( - address(counter1), 0, abi.encodePacked(bytes4(keccak256("number()"))) - ); - retrievedNumber = abi.decode(result, (uint256)); - assertEq(retrievedNumber, 18); - - // Run full workflow for counter 2 - - EFPCallerPluginAnyExternal(address(account1)).passthroughExecute( - address(counter2), 0, abi.encodeCall(Counter.setNumber, (17)) - ); - retrievedNumber = counter2.number(); - assertEq(retrievedNumber, 17); - - EFPCallerPluginAnyExternal(address(account1)).passthroughExecute( - address(counter2), 0, abi.encodeCall(Counter.increment, ()) - ); - retrievedNumber = counter2.number(); - assertEq(retrievedNumber, 18); - - result = EFPCallerPluginAnyExternal(address(account1)).passthroughExecute( - address(counter2), 0, abi.encodePacked(bytes4(keccak256("number()"))) - ); - retrievedNumber = abi.decode(result, (uint256)); - assertEq(retrievedNumber, 18); - } -} diff --git a/test/account/PermittedCallPermissions.t.sol b/test/account/PermittedCallPermissions.t.sol new file mode 100644 index 00000000..517b2080 --- /dev/null +++ b/test/account/PermittedCallPermissions.t.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; +import {FunctionReference} from "../../src/helpers/FunctionReferenceLib.sol"; + +import {ResultCreatorPlugin} from "../mocks/plugins/ReturnDataPluginMocks.sol"; +import {PermittedCallerPlugin} from "../mocks/plugins/PermittedCallMocks.sol"; +import {AccountTestBase} from "../utils/AccountTestBase.sol"; + +contract PermittedCallPermissionsTest is AccountTestBase { + ResultCreatorPlugin public resultCreatorPlugin; + + PermittedCallerPlugin public permittedCallerPlugin; + + function setUp() public { + _transferOwnershipToTest(); + resultCreatorPlugin = new ResultCreatorPlugin(); + + // Initialize the permitted caller plugins, which will attempt to use the permissions system to authorize + // calls. + permittedCallerPlugin = new PermittedCallerPlugin(); + + // Add the result creator plugin to the account + bytes32 resultCreatorManifestHash = keccak256(abi.encode(resultCreatorPlugin.pluginManifest())); + vm.prank(address(entryPoint)); + account1.installPlugin({ + plugin: address(resultCreatorPlugin), + manifestHash: resultCreatorManifestHash, + pluginInstallData: "", + dependencies: new FunctionReference[](0) + }); + // Add the permitted caller plugin to the account + bytes32 permittedCallerManifestHash = keccak256(abi.encode(permittedCallerPlugin.pluginManifest())); + vm.prank(address(entryPoint)); + account1.installPlugin({ + plugin: address(permittedCallerPlugin), + manifestHash: permittedCallerManifestHash, + pluginInstallData: "", + dependencies: new FunctionReference[](0) + }); + } + + function test_permittedCall_Allowed() public { + bytes memory result = PermittedCallerPlugin(address(account1)).usePermittedCallAllowed(); + bytes32 actual = abi.decode(result, (bytes32)); + + assertEq(actual, keccak256("bar")); + } + + function test_permittedCall_NotAllowed() public { + vm.expectRevert( + abi.encodeWithSelector( + UpgradeableModularAccount.ExecFromPluginNotPermitted.selector, + address(permittedCallerPlugin), + ResultCreatorPlugin.bar.selector + ) + ); + PermittedCallerPlugin(address(account1)).usePermittedCallNotAllowed(); + } +} diff --git a/test/account/UpgradeableModularAccount.t.sol b/test/account/UpgradeableModularAccount.t.sol index 15ad262e..e3d09e7f 100644 --- a/test/account/UpgradeableModularAccount.t.sol +++ b/test/account/UpgradeableModularAccount.t.sol @@ -37,7 +37,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { address public ethRecipient; Counter public counter; - PluginManifest public manifest; + PluginManifest internal _manifest; FunctionReference public ownerValidation; @@ -258,7 +258,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { assertEq(plugins[1], address(tokenReceiverPlugin)); } - function test_installPlugin_ExecuteFromPlugin_PermittedExecSelectorNotInstalled() public { + function test_installPlugin_PermittedCallSelectorNotInstalled() public { vm.startPrank(address(entryPoint)); PluginManifest memory m; @@ -385,7 +385,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { dependencies: new FunctionReference[](0) }); - // Attempt to uninstall with a blank manifest + // Attempt to uninstall with a blank _manifest PluginManifest memory blankManifest; vm.expectRevert(abi.encodeWithSelector(PluginManagerInternals.InvalidPluginManifest.selector)); @@ -403,7 +403,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { function _installPluginWithExecHooks() internal returns (MockPlugin plugin) { vm.startPrank(address(entryPoint)); - plugin = new MockPlugin(manifest); + plugin = new MockPlugin(_manifest); bytes32 manifestHash = keccak256(abi.encode(plugin.pluginManifest())); IPluginManager(account1).installPlugin({ diff --git a/test/mocks/plugins/BadTransferOwnershipPlugin.sol b/test/mocks/plugins/BadTransferOwnershipPlugin.sol deleted file mode 100644 index dcb9a935..00000000 --- a/test/mocks/plugins/BadTransferOwnershipPlugin.sol +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; - -import {ManifestExecutionFunction, PluginManifest, PluginMetadata} from "../../../src/interfaces/IPlugin.sol"; -import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; -import {ISingleOwnerPlugin} from "../../../src/plugins/owner/ISingleOwnerPlugin.sol"; -import {IPluginExecutor} from "../../../src/interfaces/IPluginExecutor.sol"; - -contract BadTransferOwnershipPlugin is BasePlugin { - string public constant NAME = "Evil Transfer Ownership Plugin"; - string public constant VERSION = "1.0.0"; - string public constant AUTHOR = "ERC-6900 Authors"; - - // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Execution functions ┃ - // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - - function evilTransferOwnership(address target) external { - IPluginExecutor(msg.sender).executeFromPlugin( - abi.encodeCall(ISingleOwnerPlugin.transferOwnership, (target)) - ); - } - - // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Plugin interface functions ┃ - // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - - function onInstall(bytes calldata) external override {} - - function onUninstall(bytes calldata) external override {} - - function pluginManifest() external pure override returns (PluginManifest memory) { - PluginManifest memory manifest; - - manifest.executionFunctions = new ManifestExecutionFunction[](1); - manifest.executionFunctions[0] = ManifestExecutionFunction({ - executionSelector: this.evilTransferOwnership.selector, - isPublic: true, - allowDefaultValidation: false - }); - - manifest.permittedExecutionSelectors = new bytes4[](1); - manifest.permittedExecutionSelectors[0] = ISingleOwnerPlugin.transferOwnership.selector; - - return manifest; - } - - function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { - PluginMetadata memory metadata; - metadata.name = NAME; - metadata.version = VERSION; - metadata.author = AUTHOR; - return metadata; - } -} diff --git a/test/mocks/plugins/ExecFromPluginPermissionsMocks.sol b/test/mocks/plugins/ExecFromPluginPermissionsMocks.sol deleted file mode 100644 index 1d9f566f..00000000 --- a/test/mocks/plugins/ExecFromPluginPermissionsMocks.sol +++ /dev/null @@ -1,209 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; - -import { - ManifestExecutionFunction, - ManifestExternalCallPermission, - PluginManifest, - PluginMetadata -} from "../../../src/interfaces/IPlugin.sol"; -import {IPluginExecutor} from "../../../src/interfaces/IPluginExecutor.sol"; - -import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; -import {ResultCreatorPlugin} from "./ReturnDataPluginMocks.sol"; -import {Counter} from "../Counter.sol"; - -contract EFPCallerPlugin is BasePlugin { - // Store the counters as immutables, and use the view -> pure cast to get the manifest - // solhint-disable private-vars-leading-underscore, immutable-vars-naming - address private immutable counter1; - address private immutable counter2; - address private immutable counter3; - // solhint-enable private-vars-leading-underscore, immutable-vars-naming - - constructor(address _counter1, address _counter2, address _counter3) { - counter1 = _counter1; - counter2 = _counter2; - counter3 = _counter3; - } - - function onInstall(bytes calldata) external override {} - - function onUninstall(bytes calldata) external override {} - - function _getManifest() internal view returns (PluginManifest memory) { - PluginManifest memory manifest; - - manifest.executionFunctions = new ManifestExecutionFunction[](11); - manifest.executionFunctions[0].executionSelector = this.useEFPPermissionAllowed.selector; - manifest.executionFunctions[1].executionSelector = this.useEFPPermissionNotAllowed.selector; - manifest.executionFunctions[2].executionSelector = this.setNumberCounter1.selector; - manifest.executionFunctions[3].executionSelector = this.getNumberCounter1.selector; - manifest.executionFunctions[4].executionSelector = this.incrementCounter1.selector; - manifest.executionFunctions[5].executionSelector = this.setNumberCounter2.selector; - manifest.executionFunctions[6].executionSelector = this.getNumberCounter2.selector; - manifest.executionFunctions[7].executionSelector = this.incrementCounter2.selector; - manifest.executionFunctions[8].executionSelector = this.setNumberCounter3.selector; - manifest.executionFunctions[9].executionSelector = this.getNumberCounter3.selector; - manifest.executionFunctions[10].executionSelector = this.incrementCounter3.selector; - - for (uint256 i = 0; i < manifest.executionFunctions.length; i++) { - manifest.executionFunctions[i].isPublic = true; - } - - // Request permission only for "foo", but not "bar", from ResultCreatorPlugin - manifest.permittedExecutionSelectors = new bytes4[](1); - manifest.permittedExecutionSelectors[0] = ResultCreatorPlugin.foo.selector; - - // Request permission for: - // - `setNumber` and `number` on counter 1 - // - All selectors on counter 2 - // - None on counter 3 - manifest.permittedExternalCalls = new ManifestExternalCallPermission[](2); - - bytes4[] memory selectorsCounter1 = new bytes4[](2); - selectorsCounter1[0] = Counter.setNumber.selector; - selectorsCounter1[1] = bytes4(keccak256("number()")); // Public vars don't automatically get exported - // selectors - - manifest.permittedExternalCalls[0] = ManifestExternalCallPermission({ - externalAddress: counter1, - permitAnySelector: false, - selectors: selectorsCounter1 - }); - - manifest.permittedExternalCalls[1] = ManifestExternalCallPermission({ - externalAddress: counter2, - permitAnySelector: true, - selectors: new bytes4[](0) - }); - - return manifest; - } - - function _castToPure(function() internal view returns (PluginManifest memory) fnIn) - internal - pure - returns (function() internal pure returns (PluginManifest memory) fnOut) - { - assembly ("memory-safe") { - fnOut := fnIn - } - } - - function pluginManifest() external pure override returns (PluginManifest memory) { - return _castToPure(_getManifest)(); - } - - function pluginMetadata() external pure override returns (PluginMetadata memory) {} - - // The manifest requested access to use the plugin-defined method "foo" - function useEFPPermissionAllowed() external returns (bytes memory) { - return IPluginExecutor(msg.sender).executeFromPlugin(abi.encodeCall(ResultCreatorPlugin.foo, ())); - } - - // The manifest has not requested access to use the plugin-defined method "bar", so this should revert. - function useEFPPermissionNotAllowed() external returns (bytes memory) { - return IPluginExecutor(msg.sender).executeFromPlugin(abi.encodeCall(ResultCreatorPlugin.bar, ())); - } - - // Should be allowed - function setNumberCounter1(uint256 number) external { - IPluginExecutor(msg.sender).executeFromPluginExternal( - counter1, 0, abi.encodeWithSelector(Counter.setNumber.selector, number) - ); - } - - // Should be allowed - function getNumberCounter1() external returns (uint256) { - bytes memory returnData = IPluginExecutor(msg.sender).executeFromPluginExternal( - counter1, 0, abi.encodePacked(bytes4(keccak256("number()"))) - ); - - return abi.decode(returnData, (uint256)); - } - - // Should not be allowed - function incrementCounter1() external { - IPluginExecutor(msg.sender).executeFromPluginExternal( - counter1, 0, abi.encodeWithSelector(Counter.increment.selector) - ); - } - - // Should be allowed - function setNumberCounter2(uint256 number) external { - IPluginExecutor(msg.sender).executeFromPluginExternal( - counter2, 0, abi.encodeWithSelector(Counter.setNumber.selector, number) - ); - } - - // Should be allowed - function getNumberCounter2() external returns (uint256) { - bytes memory returnData = IPluginExecutor(msg.sender).executeFromPluginExternal( - counter2, 0, abi.encodePacked(bytes4(keccak256("number()"))) - ); - - return abi.decode(returnData, (uint256)); - } - - // Should be allowed - function incrementCounter2() external { - IPluginExecutor(msg.sender).executeFromPluginExternal( - counter2, 0, abi.encodeWithSelector(Counter.increment.selector) - ); - } - - // Should not be allowed - function setNumberCounter3(uint256 number) external { - IPluginExecutor(msg.sender).executeFromPluginExternal( - counter3, 0, abi.encodeWithSelector(Counter.setNumber.selector, number) - ); - } - - // Should not be allowed - function getNumberCounter3() external returns (uint256) { - bytes memory returnData = IPluginExecutor(msg.sender).executeFromPluginExternal( - counter3, 0, abi.encodePacked(bytes4(keccak256("number()"))) - ); - - return abi.decode(returnData, (uint256)); - } - - // Should not be allowed - function incrementCounter3() external { - IPluginExecutor(msg.sender).executeFromPluginExternal( - counter3, 0, abi.encodeWithSelector(Counter.increment.selector) - ); - } -} - -contract EFPCallerPluginAnyExternal is BasePlugin { - function onInstall(bytes calldata) external override {} - - function onUninstall(bytes calldata) external override {} - - function pluginManifest() external pure override returns (PluginManifest memory) { - PluginManifest memory manifest; - - manifest.executionFunctions = new ManifestExecutionFunction[](1); - manifest.executionFunctions[0] = ManifestExecutionFunction({ - executionSelector: this.passthroughExecute.selector, - isPublic: true, - allowDefaultValidation: false - }); - - manifest.permitAnyExternalAddress = true; - - return manifest; - } - - function pluginMetadata() external pure override returns (PluginMetadata memory) {} - - function passthroughExecute(address target, uint256 value, bytes calldata data) - external - payable - returns (bytes memory) - { - return IPluginExecutor(msg.sender).executeFromPluginExternal(target, value, data); - } -} diff --git a/test/mocks/plugins/PermittedCallMocks.sol b/test/mocks/plugins/PermittedCallMocks.sol new file mode 100644 index 00000000..450a70fb --- /dev/null +++ b/test/mocks/plugins/PermittedCallMocks.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {ManifestExecutionFunction, PluginManifest, PluginMetadata} from "../../../src/interfaces/IPlugin.sol"; + +import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; +import {ResultCreatorPlugin} from "./ReturnDataPluginMocks.sol"; + +contract PermittedCallerPlugin is BasePlugin { + function onInstall(bytes calldata) external override {} + + function onUninstall(bytes calldata) external override {} + + function pluginManifest() external pure override returns (PluginManifest memory) { + PluginManifest memory manifest; + + manifest.executionFunctions = new ManifestExecutionFunction[](2); + manifest.executionFunctions[0].executionSelector = this.usePermittedCallAllowed.selector; + manifest.executionFunctions[1].executionSelector = this.usePermittedCallNotAllowed.selector; + + for (uint256 i = 0; i < manifest.executionFunctions.length; i++) { + manifest.executionFunctions[i].isPublic = true; + } + + // Request permission only for "foo", but not "bar", from ResultCreatorPlugin + manifest.permittedExecutionSelectors = new bytes4[](1); + manifest.permittedExecutionSelectors[0] = ResultCreatorPlugin.foo.selector; + + return manifest; + } + + function pluginMetadata() external pure override returns (PluginMetadata memory) {} + + // The manifest requested access to use the plugin-defined method "foo" + function usePermittedCallAllowed() external view returns (bytes memory) { + return abi.encode(ResultCreatorPlugin(msg.sender).foo()); + } + + // The manifest has not requested access to use the plugin-defined method "bar", so this should revert. + function usePermittedCallNotAllowed() external view returns (bytes memory) { + return abi.encode(ResultCreatorPlugin(msg.sender).bar()); + } +} diff --git a/test/mocks/plugins/ReturnDataPluginMocks.sol b/test/mocks/plugins/ReturnDataPluginMocks.sol index 5ab3a811..dae2c8e4 100644 --- a/test/mocks/plugins/ReturnDataPluginMocks.sol +++ b/test/mocks/plugins/ReturnDataPluginMocks.sol @@ -1,13 +1,18 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; + import { ManifestExecutionFunction, - ManifestExternalCallPermission, + ManifestAssociatedFunctionType, + ManifestAssociatedFunction, + ManifestFunction, PluginManifest, PluginMetadata } from "../../../src/interfaces/IPlugin.sol"; -import {IPluginExecutor} from "../../../src/interfaces/IPluginExecutor.sol"; +import {IValidation} from "../../../src/interfaces/IValidation.sol"; +import {IStandardExecutor} from "../../../src/interfaces/IStandardExecutor.sol"; import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; @@ -55,33 +60,51 @@ contract ResultCreatorPlugin is BasePlugin { function pluginMetadata() external pure override returns (PluginMetadata memory) {} } -contract ResultConsumerPlugin is BasePlugin { +contract ResultConsumerPlugin is BasePlugin, IValidation { ResultCreatorPlugin public immutable RESULT_CREATOR; RegularResultContract public immutable REGULAR_RESULT_CONTRACT; + error NotAuthorized(); + constructor(ResultCreatorPlugin _resultCreator, RegularResultContract _regularResultContract) { RESULT_CREATOR = _resultCreator; REGULAR_RESULT_CONTRACT = _regularResultContract; } - // Check the return data through the executeFromPlugin fallback case - function checkResultEFPFallback(bytes32 expected) external returns (bool) { - // This result should be allowed based on the manifest permission request - IPluginExecutor(msg.sender).executeFromPlugin(abi.encodeCall(ResultCreatorPlugin.foo, ())); + // Validation function implementations. We only care about the runtime validation function, to authorize + // itself. + + function validateUserOp(uint8, PackedUserOperation calldata, bytes32) external pure returns (uint256) { + revert NotImplemented(); + } + + function validateRuntime(uint8, address sender, uint256, bytes calldata, bytes calldata) external view { + if (sender != address(this)) { + revert NotAuthorized(); + } + } + + function validateSignature(uint8, address, bytes32, bytes calldata) external pure returns (bytes4) { + revert NotImplemented(); + } + // Check the return data through the fallback + function checkResultFallback(bytes32 expected) external view returns (bool) { + // This result should be allowed based on the manifest permission request bytes32 actual = ResultCreatorPlugin(msg.sender).foo(); return actual == expected; } - // Check the rturn data through the executeFromPlugin std exec case - function checkResultEFPExternal(address target, bytes32 expected) external returns (bool) { + // Check the return data through the execute with authorization case + function checkResultExecuteWithAuthorization(address target, bytes32 expected) external returns (bool) { // This result should be allowed based on the manifest permission request - bytes memory returnData = IPluginExecutor(msg.sender).executeFromPluginExternal( - target, 0, abi.encodeCall(RegularResultContract.foo, ()) + bytes memory returnData = IStandardExecutor(msg.sender).executeWithAuthorization( + abi.encodeCall(IStandardExecutor.execute, (target, 0, abi.encodeCall(RegularResultContract.foo, ()))), + abi.encodePacked(this, uint8(0), uint8(0)) // Validation function of self, selector-associated ); - bytes32 actual = abi.decode(returnData, (bytes32)); + bytes32 actual = abi.decode(abi.decode(returnData, (bytes)), (bytes32)); return actual == expected; } @@ -91,36 +114,26 @@ contract ResultConsumerPlugin is BasePlugin { function onUninstall(bytes calldata) external override {} function pluginManifest() external pure override returns (PluginManifest memory) { - // We want to return the address of the immutable RegularResultContract in the permitted external calls - // area of the manifest. - // However, reading from immutable values is not permitted in pure functions. So we use this hack to get - // around that. - // In regular, non-mock plugins, external call targets in the plugin manifest should be constants, not just - // immutbales. - // But to make testing easier, we do this. - - function() internal pure returns (PluginManifest memory) pureManifestGetter; - - function() internal view returns (PluginManifest memory) viewManifestGetter = _innerPluginManifest; - - assembly ("memory-safe") { - pureManifestGetter := viewManifestGetter - } - - return pureManifestGetter(); - } - - function _innerPluginManifest() internal view returns (PluginManifest memory) { PluginManifest memory manifest; + manifest.validationFunctions = new ManifestAssociatedFunction[](1); + manifest.validationFunctions[0] = ManifestAssociatedFunction({ + executionSelector: IStandardExecutor.execute.selector, + associatedFunction: ManifestFunction({ + functionType: ManifestAssociatedFunctionType.SELF, + functionId: uint8(0), + dependencyIndex: 0 + }) + }); + manifest.executionFunctions = new ManifestExecutionFunction[](2); manifest.executionFunctions[0] = ManifestExecutionFunction({ - executionSelector: this.checkResultEFPFallback.selector, + executionSelector: this.checkResultFallback.selector, isPublic: true, allowDefaultValidation: false }); manifest.executionFunctions[1] = ManifestExecutionFunction({ - executionSelector: this.checkResultEFPExternal.selector, + executionSelector: this.checkResultExecuteWithAuthorization.selector, isPublic: true, allowDefaultValidation: false }); @@ -128,16 +141,6 @@ contract ResultConsumerPlugin is BasePlugin { manifest.permittedExecutionSelectors = new bytes4[](1); manifest.permittedExecutionSelectors[0] = ResultCreatorPlugin.foo.selector; - manifest.permittedExternalCalls = new ManifestExternalCallPermission[](1); - - bytes4[] memory allowedSelectors = new bytes4[](1); - allowedSelectors[0] = RegularResultContract.foo.selector; - manifest.permittedExternalCalls[0] = ManifestExternalCallPermission({ - externalAddress: address(REGULAR_RESULT_CONTRACT), - permitAnySelector: false, - selectors: allowedSelectors - }); - return manifest; } From 6e97d841611ab6178be73ce2a8c16c4a391d9c96 Mon Sep 17 00:00:00 2001 From: howy <132113803+howydev@users.noreply.github.com> Date: Thu, 27 Jun 2024 20:03:31 -0400 Subject: [PATCH 021/111] [1/n permissions] chore: remove existing permissions system (#67) --- src/account/AccountStorage.sol | 1 - src/account/PluginManagerInternals.sol | 13 ------------- src/account/UpgradeableModularAccount.sol | 8 +++----- src/interfaces/IPlugin.sol | 2 -- test/account/UpgradeableModularAccount.t.sol | 4 +--- test/mocks/plugins/PermittedCallMocks.sol | 4 ---- test/mocks/plugins/ReturnDataPluginMocks.sol | 3 --- 7 files changed, 4 insertions(+), 31 deletions(-) diff --git a/src/account/AccountStorage.sol b/src/account/AccountStorage.sol index 0a992a33..5d08ab6d 100644 --- a/src/account/AccountStorage.sol +++ b/src/account/AccountStorage.sol @@ -52,7 +52,6 @@ struct AccountStorage { // Execution functions and their associated functions mapping(bytes4 => SelectorData) selectorData; mapping(FunctionReference validationFunction => ValidationData) validationData; - mapping(address caller => mapping(bytes4 selector => bool)) callPermitted; // For ERC165 introspection mapping(bytes4 => uint256) supportedIfaces; } diff --git a/src/account/PluginManagerInternals.sol b/src/account/PluginManagerInternals.sol index 2ec06f81..05943e09 100644 --- a/src/account/PluginManagerInternals.sol +++ b/src/account/PluginManagerInternals.sol @@ -212,14 +212,6 @@ abstract contract PluginManagerInternals is IPluginManager { _setExecutionFunction(selector, isPublic, allowDefaultValidation, plugin); } - // Add installed plugin and selectors this plugin can call - length = manifest.permittedExecutionSelectors.length; - for (uint256 i = 0; i < length; ++i) { - // If there are duplicates, this will just enable the flag again. This is not a problem, since the - // boolean will be set to false twice during uninstall, which is fine. - _storage.callPermitted[plugin][manifest.permittedExecutionSelectors[i]] = true; - } - length = manifest.validationFunctions.length; for (uint256 i = 0; i < length; ++i) { ManifestAssociatedFunction memory mv = manifest.validationFunctions[i]; @@ -313,11 +305,6 @@ abstract contract PluginManagerInternals is IPluginManager { ); } - length = manifest.permittedExecutionSelectors.length; - for (uint256 i = 0; i < length; ++i) { - _storage.callPermitted[plugin][manifest.permittedExecutionSelectors[i]] = false; - } - length = manifest.executionFunctions.length; for (uint256 i = 0; i < length; ++i) { bytes4 selector = manifest.executionFunctions[i].executionSelector; diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 169b17a3..0c67ac81 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -555,11 +555,9 @@ contract UpgradeableModularAccount is AccountStorage storage _storage = getAccountStorage(); if ( - msg.sender == address(_ENTRY_POINT) || msg.sender == address(this) - || _storage.selectorData[msg.sig].isPublic - ) return; - - if (!_storage.callPermitted[msg.sender][msg.sig]) { + msg.sender != address(_ENTRY_POINT) && msg.sender != address(this) + && !_storage.selectorData[msg.sig].isPublic + ) { revert ExecFromPluginNotPermitted(msg.sender, msg.sig); } } diff --git a/src/interfaces/IPlugin.sol b/src/interfaces/IPlugin.sol index 3241e497..22d74546 100644 --- a/src/interfaces/IPlugin.sol +++ b/src/interfaces/IPlugin.sol @@ -73,8 +73,6 @@ struct PluginManifest { ManifestAssociatedFunction[] validationFunctions; ManifestExecutionHook[] executionHooks; uint8[] signatureValidationFunctions; - // Plugin execution functions already installed on the MSCA that this plugin will be able to call. - bytes4[] permittedExecutionSelectors; // List of ERC-165 interface IDs to add to account to support introspection checks. This MUST NOT include // IPlugin's interface ID. bytes4[] interfaceIds; diff --git a/test/account/UpgradeableModularAccount.t.sol b/test/account/UpgradeableModularAccount.t.sol index e3d09e7f..46ea1d5f 100644 --- a/test/account/UpgradeableModularAccount.t.sol +++ b/test/account/UpgradeableModularAccount.t.sol @@ -11,7 +11,7 @@ import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; import {PluginManagerInternals} from "../../src/account/PluginManagerInternals.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; -import {IPlugin, PluginManifest} from "../../src/interfaces/IPlugin.sol"; +import {PluginManifest} from "../../src/interfaces/IPlugin.sol"; import {IAccountLoupe} from "../../src/interfaces/IAccountLoupe.sol"; import {IPluginManager} from "../../src/interfaces/IPluginManager.sol"; import {Call} from "../../src/interfaces/IStandardExecutor.sol"; @@ -262,8 +262,6 @@ contract UpgradeableModularAccountTest is AccountTestBase { vm.startPrank(address(entryPoint)); PluginManifest memory m; - m.permittedExecutionSelectors = new bytes4[](1); - m.permittedExecutionSelectors[0] = IPlugin.onInstall.selector; MockPlugin mockPluginWithBadPermittedExec = new MockPlugin(m); bytes32 manifestHash = keccak256(abi.encode(mockPluginWithBadPermittedExec.pluginManifest())); diff --git a/test/mocks/plugins/PermittedCallMocks.sol b/test/mocks/plugins/PermittedCallMocks.sol index 450a70fb..77548225 100644 --- a/test/mocks/plugins/PermittedCallMocks.sol +++ b/test/mocks/plugins/PermittedCallMocks.sol @@ -22,10 +22,6 @@ contract PermittedCallerPlugin is BasePlugin { manifest.executionFunctions[i].isPublic = true; } - // Request permission only for "foo", but not "bar", from ResultCreatorPlugin - manifest.permittedExecutionSelectors = new bytes4[](1); - manifest.permittedExecutionSelectors[0] = ResultCreatorPlugin.foo.selector; - return manifest; } diff --git a/test/mocks/plugins/ReturnDataPluginMocks.sol b/test/mocks/plugins/ReturnDataPluginMocks.sol index dae2c8e4..485b8456 100644 --- a/test/mocks/plugins/ReturnDataPluginMocks.sol +++ b/test/mocks/plugins/ReturnDataPluginMocks.sol @@ -138,9 +138,6 @@ contract ResultConsumerPlugin is BasePlugin, IValidation { allowDefaultValidation: false }); - manifest.permittedExecutionSelectors = new bytes4[](1); - manifest.permittedExecutionSelectors[0] = ResultCreatorPlugin.foo.selector; - return manifest; } From f22a52131ac39bb07345a2a850fa883bbb330656 Mon Sep 17 00:00:00 2001 From: howy <132113803+howydev@users.noreply.github.com> Date: Mon, 8 Jul 2024 20:55:15 -0400 Subject: [PATCH 022/111] [2/n permissions] feat: add permission hooks (#76) --- src/account/AccountLoupe.sol | 26 +++++++++++++++++---- src/account/AccountStorage.sol | 4 ++++ src/account/PluginManagerInternals.sol | 15 ++++++------ src/account/UpgradeableModularAccount.sol | 28 +++++++++++++++-------- src/interfaces/IAccountLoupe.sol | 8 +++++++ src/interfaces/IPlugin.sol | 2 ++ test/account/AccountExecHooks.t.sol | 6 ++--- 7 files changed, 65 insertions(+), 24 deletions(-) diff --git a/src/account/AccountLoupe.sol b/src/account/AccountLoupe.sol index 9cde18b4..2afe6638 100644 --- a/src/account/AccountLoupe.sol +++ b/src/account/AccountLoupe.sol @@ -7,7 +7,7 @@ import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet import {IAccountLoupe, ExecutionHook} from "../interfaces/IAccountLoupe.sol"; import {FunctionReference, IPluginManager} from "../interfaces/IPluginManager.sol"; import {IStandardExecutor} from "../interfaces/IStandardExecutor.sol"; -import {getAccountStorage, SelectorData, toFunctionReferenceArray, toExecutionHook} from "./AccountStorage.sol"; +import {getAccountStorage, toFunctionReferenceArray, toExecutionHook} from "./AccountStorage.sol"; abstract contract AccountLoupe is IAccountLoupe { using EnumerableSet for EnumerableSet.Bytes32Set; @@ -39,18 +39,36 @@ abstract contract AccountLoupe is IAccountLoupe { override returns (ExecutionHook[] memory execHooks) { - SelectorData storage selectorData = getAccountStorage().selectorData[selector]; - uint256 executionHooksLength = selectorData.executionHooks.length(); + EnumerableSet.Bytes32Set storage hooks = getAccountStorage().selectorData[selector].executionHooks; + uint256 executionHooksLength = hooks.length(); execHooks = new ExecutionHook[](executionHooksLength); for (uint256 i = 0; i < executionHooksLength; ++i) { - bytes32 key = selectorData.executionHooks.at(i); + bytes32 key = hooks.at(i); ExecutionHook memory execHook = execHooks[i]; (execHook.hookFunction, execHook.isPreHook, execHook.isPostHook) = toExecutionHook(key); } } + /// @inheritdoc IAccountLoupe + function getPermissionHooks(FunctionReference validationFunction) + external + view + override + returns (ExecutionHook[] memory permissionHooks) + { + EnumerableSet.Bytes32Set storage hooks = + getAccountStorage().validationData[validationFunction].permissionHooks; + uint256 executionHooksLength = hooks.length(); + permissionHooks = new ExecutionHook[](executionHooksLength); + for (uint256 i = 0; i < executionHooksLength; ++i) { + bytes32 key = hooks.at(i); + ExecutionHook memory execHook = permissionHooks[i]; + (execHook.hookFunction, execHook.isPreHook, execHook.isPostHook) = toExecutionHook(key); + } + } + /// @inheritdoc IAccountLoupe function getPreValidationHooks(FunctionReference validationFunction) external diff --git a/src/account/AccountStorage.sol b/src/account/AccountStorage.sol index 5d08ab6d..352a3d5b 100644 --- a/src/account/AccountStorage.sol +++ b/src/account/AccountStorage.sol @@ -38,8 +38,12 @@ struct ValidationData { bool isDefault; // Whether or not this validation is a signature validator. bool isSignatureValidation; + // How many execution hooks require the UO context. + uint8 requireUOHookCount; // The pre validation hooks for this function selector. EnumerableSet.Bytes32Set preValidationHooks; + // Permission hooks for this validation function. + EnumerableSet.Bytes32Set permissionHooks; } struct AccountStorage { diff --git a/src/account/PluginManagerInternals.sol b/src/account/PluginManagerInternals.sol index 05943e09..a9b79802 100644 --- a/src/account/PluginManagerInternals.sol +++ b/src/account/PluginManagerInternals.sol @@ -125,12 +125,12 @@ abstract contract PluginManagerInternals is IPluginManager { } function _addExecHooks( - bytes4 selector, + EnumerableSet.Bytes32Set storage hooks, FunctionReference hookFunction, bool isPreExecHook, bool isPostExecHook ) internal { - getAccountStorage().selectorData[selector].executionHooks.add( + hooks.add( toSetValue( ExecutionHook({hookFunction: hookFunction, isPreHook: isPreExecHook, isPostHook: isPostExecHook}) ) @@ -138,12 +138,12 @@ abstract contract PluginManagerInternals is IPluginManager { } function _removeExecHooks( - bytes4 selector, + EnumerableSet.Bytes32Set storage hooks, FunctionReference hookFunction, bool isPreExecHook, bool isPostExecHook ) internal { - getAccountStorage().selectorData[selector].executionHooks.remove( + hooks.remove( toSetValue( ExecutionHook({hookFunction: hookFunction, isPreHook: isPreExecHook, isPostHook: isPostExecHook}) ) @@ -230,8 +230,9 @@ abstract contract PluginManagerInternals is IPluginManager { length = manifest.executionHooks.length; for (uint256 i = 0; i < length; ++i) { ManifestExecutionHook memory mh = manifest.executionHooks[i]; + EnumerableSet.Bytes32Set storage execHooks = _storage.selectorData[mh.executionSelector].executionHooks; FunctionReference hookFunction = FunctionReferenceLib.pack(plugin, mh.functionId); - _addExecHooks(mh.executionSelector, hookFunction, mh.isPreHook, mh.isPostHook); + _addExecHooks(execHooks, hookFunction, mh.isPreHook, mh.isPostHook); } length = manifest.interfaceIds.length; @@ -282,12 +283,12 @@ abstract contract PluginManagerInternals is IPluginManager { } // Remove components according to the manifest, in reverse order (by component type) of their installation. - length = manifest.executionHooks.length; for (uint256 i = 0; i < length; ++i) { ManifestExecutionHook memory mh = manifest.executionHooks[i]; FunctionReference hookFunction = FunctionReferenceLib.pack(plugin, mh.functionId); - _removeExecHooks(mh.executionSelector, hookFunction, mh.isPreHook, mh.isPostHook); + EnumerableSet.Bytes32Set storage execHooks = _storage.selectorData[mh.executionSelector].executionHooks; + _removeExecHooks(execHooks, hookFunction, mh.isPreHook, mh.isPostHook); } length = manifest.signatureValidationFunctions.length; diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 0c67ac81..1a52b5dd 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -22,7 +22,6 @@ import {AccountLoupe} from "./AccountLoupe.sol"; import { AccountStorage, getAccountStorage, - SelectorData, toSetValue, toFunctionReference, toExecutionHook @@ -70,6 +69,7 @@ contract UpgradeableModularAccount is error PostExecHookReverted(address plugin, uint8 functionId, bytes revertReason); error PreExecHookReverted(address plugin, uint8 functionId, bytes revertReason); error PreRuntimeValidationHookFailed(address plugin, uint8 functionId, bytes revertReason); + error RequireUserOperationContext(); error RuntimeValidationFunctionMissing(bytes4 selector); error RuntimeValidationFunctionReverted(address plugin, uint8 functionId, bytes revertReason); error SignatureValidationInvalid(address plugin, uint8 functionId); @@ -83,7 +83,8 @@ contract UpgradeableModularAccount is modifier wrapNativeFunction() { _checkPermittedCallerIfNotFromEP(); - PostExecToRun[] memory postExecHooks = _doPreExecHooks(msg.sig, msg.data); + PostExecToRun[] memory postExecHooks = + _doPreExecHooks(getAccountStorage().selectorData[msg.sig].executionHooks, msg.data); _; @@ -137,7 +138,7 @@ contract UpgradeableModularAccount is PostExecToRun[] memory postExecHooks; // Cache post-exec hooks in memory - postExecHooks = _doPreExecHooks(msg.sig, msg.data); + postExecHooks = _doPreExecHooks(getAccountStorage().selectorData[msg.sig].executionHooks, msg.data); // execute the function, bubbling up any reverts (bool execSuccess, bytes memory execReturnData) = execPlugin.call(msg.data); @@ -350,6 +351,17 @@ contract UpgradeableModularAccount is _checkIfValidationApplies(selector, userOpValidationFunction, isDefaultValidation); + // Check if there are exec hooks associated with the validator that require UO context, and revert if the + // call isn't to `executeUserOp` + // This check must be here because if context isn't passed, we wouldn't be able to get the exec hooks + // associated with the validator + if (getAccountStorage().validationData[userOpValidationFunction].requireUOHookCount > 0) { + /** + * && msg.sig != this.executeUserOp.selector + */ + revert RequireUserOperationContext(); + } + validationData = _doUserOpValidation(selector, userOpValidationFunction, userOp, userOp.signature[22:], userOpHash); } @@ -441,13 +453,11 @@ contract UpgradeableModularAccount is } } - function _doPreExecHooks(bytes4 selector, bytes calldata data) + function _doPreExecHooks(EnumerableSet.Bytes32Set storage executionHooks, bytes calldata data) internal returns (PostExecToRun[] memory postHooksToRun) { - SelectorData storage selectorData = getAccountStorage().selectorData[selector]; - - uint256 hooksLength = selectorData.executionHooks.length(); + uint256 hooksLength = executionHooks.length(); // Overallocate on length - not all of this may get filled up. We set the correct length later. postHooksToRun = new PostExecToRun[](hooksLength); @@ -455,7 +465,7 @@ contract UpgradeableModularAccount is // Copy all post hooks to the array. This happens before any pre hooks are run, so we can // be sure that the set of hooks to run will not be affected by state changes mid-execution. for (uint256 i = 0; i < hooksLength; ++i) { - bytes32 key = selectorData.executionHooks.at(i); + bytes32 key = executionHooks.at(i); (FunctionReference hookFunction,, bool isPostHook) = toExecutionHook(key); if (isPostHook) { postHooksToRun[i].postExecHook = hookFunction; @@ -465,7 +475,7 @@ contract UpgradeableModularAccount is // Run the pre hooks and copy their return data to the post hooks array, if an associated post-exec hook // exists. for (uint256 i = 0; i < hooksLength; ++i) { - bytes32 key = selectorData.executionHooks.at(i); + bytes32 key = executionHooks.at(i); (FunctionReference hookFunction, bool isPreHook, bool isPostHook) = toExecutionHook(key); if (isPreHook) { diff --git a/src/interfaces/IAccountLoupe.sol b/src/interfaces/IAccountLoupe.sol index 490b216c..b172464a 100644 --- a/src/interfaces/IAccountLoupe.sol +++ b/src/interfaces/IAccountLoupe.sol @@ -28,6 +28,14 @@ interface IAccountLoupe { /// @return The pre and post execution hooks for this selector. function getExecutionHooks(bytes4 selector) external view returns (ExecutionHook[] memory); + /// @notice Get the pre and post execution hooks for a validation function. + /// @param validationFunction The validation function to get the hooks for. + /// @return The pre and post execution hooks for this validation function. + function getPermissionHooks(FunctionReference validationFunction) + external + view + returns (ExecutionHook[] memory); + /// @notice Get the pre user op and runtime validation hooks associated with a selector. /// @param validationFunction The validation function to get the hooks for. /// @return preValidationHooks The pre validation hooks for this selector. diff --git a/src/interfaces/IPlugin.sol b/src/interfaces/IPlugin.sol index 22d74546..7259d748 100644 --- a/src/interfaces/IPlugin.sol +++ b/src/interfaces/IPlugin.sol @@ -64,6 +64,8 @@ struct PluginMetadata { // String desciptions of the relative sensitivity of specific functions. The selectors MUST be selectors for // functions implemented by this plugin. SelectorPermission[] permissionDescriptors; + // A list of all ERC-7715 permission strings that the plugin could possibly use + string[] permissionRequest; } /// @dev A struct describing how the plugin should be installed on a modular account. diff --git a/test/account/AccountExecHooks.t.sol b/test/account/AccountExecHooks.t.sol index 6a3b7d8c..f2e6391f 100644 --- a/test/account/AccountExecHooks.t.sol +++ b/test/account/AccountExecHooks.t.sol @@ -15,7 +15,6 @@ import {AccountTestBase} from "../utils/AccountTestBase.sol"; contract AccountExecHooksTest is AccountTestBase { MockPlugin public mockPlugin1; - MockPlugin public mockPlugin2; bytes32 public manifestHash1; bytes32 public manifestHash2; @@ -25,7 +24,6 @@ contract AccountExecHooksTest is AccountTestBase { uint8 internal constant _BOTH_HOOKS_FUNCTION_ID_3 = 3; PluginManifest internal _m1; - PluginManifest internal _m2; event PluginInstalled(address indexed plugin, bytes32 manifestHash, FunctionReference[] dependencies); event PluginUninstalled(address indexed plugin, bool indexed callbacksSucceeded); @@ -66,7 +64,7 @@ contract AccountExecHooksTest is AccountTestBase { IExecutionHook.preExecutionHook.selector, _PRE_HOOK_FUNCTION_ID_1, address(this), // caller - 0, // msg.value in call to account + uint256(0), // msg.value in call to account abi.encodeWithSelector(_EXEC_SELECTOR) ), 0 // msg value in call to plugin @@ -105,7 +103,7 @@ contract AccountExecHooksTest is AccountTestBase { IExecutionHook.preExecutionHook.selector, _BOTH_HOOKS_FUNCTION_ID_3, address(this), // caller - 0, // msg.value in call to account + uint256(0), // msg.value in call to account abi.encodeWithSelector(_EXEC_SELECTOR) ), 0 // msg value in call to plugin From f63137b04900fe3faef770f8819b1ec6555f9df2 Mon Sep 17 00:00:00 2001 From: howy <132113803+howydev@users.noreply.github.com> Date: Tue, 9 Jul 2024 14:55:05 -0400 Subject: [PATCH 023/111] [3/n permissions] feat: add execute user operation (#77) --- src/account/PluginManager2.sol | 66 +++++++++++++---- src/account/UpgradeableModularAccount.sol | 87 +++++++++++++++++------ src/interfaces/IPluginManager.sol | 11 ++- test/account/AccountLoupe.t.sol | 7 +- test/account/ValidationIntersection.t.sol | 14 +++- 5 files changed, 147 insertions(+), 38 deletions(-) diff --git a/src/account/PluginManager2.sol b/src/account/PluginManager2.sol index 9e73e306..f733eb60 100644 --- a/src/account/PluginManager2.sol +++ b/src/account/PluginManager2.sol @@ -7,6 +7,7 @@ import {IPlugin} from "../interfaces/IPlugin.sol"; import {FunctionReference} from "../interfaces/IPluginManager.sol"; import {FunctionReferenceLib} from "../helpers/FunctionReferenceLib.sol"; import {AccountStorage, getAccountStorage, toSetValue, toFunctionReference} from "./AccountStorage.sol"; +import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; // Temporary additional functions for a user-controlled install flow for validation functions. abstract contract PluginManager2 { @@ -16,13 +17,15 @@ abstract contract PluginManager2 { error PreValidationAlreadySet(FunctionReference validationFunction, FunctionReference preValidationFunction); error ValidationAlreadySet(bytes4 selector, FunctionReference validationFunction); error ValidationNotSet(bytes4 selector, FunctionReference validationFunction); + error PermissionAlreadySet(FunctionReference validationFunction, ExecutionHook hook); function _installValidation( FunctionReference validationFunction, bool isDefault, bytes4[] memory selectors, bytes calldata installData, - bytes memory preValidationHooks + bytes memory preValidationHooks, + bytes memory permissionHooks ) // TODO: flag for signature validation internal @@ -51,6 +54,26 @@ abstract contract PluginManager2 { } } + if (permissionHooks.length > 0) { + (ExecutionHook[] memory permissionFunctions, bytes[] memory initDatas) = + abi.decode(permissionHooks, (ExecutionHook[], bytes[])); + + for (uint256 i = 0; i < permissionFunctions.length; ++i) { + ExecutionHook memory permissionFunction = permissionFunctions[i]; + + if ( + !_storage.validationData[validationFunction].permissionHooks.add(toSetValue(permissionFunction)) + ) { + revert PermissionAlreadySet(validationFunction, permissionFunction); + } + + if (initDatas[i].length > 0) { + (address executionPlugin,) = FunctionReferenceLib.unpack(permissionFunction.hookFunction); + IPlugin(executionPlugin).onInstall(initDatas[i]); + } + } + } + if (isDefault) { if (_storage.validationData[validationFunction].isDefault) { revert DefaultValidationAlreadySet(validationFunction); @@ -75,24 +98,41 @@ abstract contract PluginManager2 { FunctionReference validationFunction, bytes4[] calldata selectors, bytes calldata uninstallData, - bytes calldata preValidationHookUninstallData + bytes calldata preValidationHookUninstallData, + bytes calldata permissionHookUninstallData ) internal { AccountStorage storage _storage = getAccountStorage(); _storage.validationData[validationFunction].isDefault = false; _storage.validationData[validationFunction].isSignatureValidation = false; - bytes[] memory preValidationHookUninstallDatas = abi.decode(preValidationHookUninstallData, (bytes[])); - - // Clear pre validation hooks - EnumerableSet.Bytes32Set storage preValidationHooks = - _storage.validationData[validationFunction].preValidationHooks; - while (preValidationHooks.length() > 0) { - FunctionReference preValidationFunction = toFunctionReference(preValidationHooks.at(0)); - preValidationHooks.remove(toSetValue(preValidationFunction)); - (address preValidationPlugin,) = FunctionReferenceLib.unpack(preValidationFunction); - if (preValidationHookUninstallDatas[0].length > 0) { - IPlugin(preValidationPlugin).onUninstall(preValidationHookUninstallDatas[0]); + { + bytes[] memory preValidationHookUninstallDatas = abi.decode(preValidationHookUninstallData, (bytes[])); + + // Clear pre validation hooks + EnumerableSet.Bytes32Set storage preValidationHooks = + _storage.validationData[validationFunction].preValidationHooks; + uint256 i = 0; + while (preValidationHooks.length() > 0) { + FunctionReference preValidationFunction = toFunctionReference(preValidationHooks.at(0)); + preValidationHooks.remove(toSetValue(preValidationFunction)); + (address preValidationPlugin,) = FunctionReferenceLib.unpack(preValidationFunction); + IPlugin(preValidationPlugin).onUninstall(preValidationHookUninstallDatas[i++]); + } + } + + { + bytes[] memory permissionHookUninstallDatas = abi.decode(permissionHookUninstallData, (bytes[])); + + // Clear permission hooks + EnumerableSet.Bytes32Set storage permissionHooks = + _storage.validationData[validationFunction].permissionHooks; + uint256 i = 0; + while (permissionHooks.length() > 0) { + FunctionReference permissionHook = toFunctionReference(permissionHooks.at(0)); + permissionHooks.remove(toSetValue(permissionHook)); + (address permissionHookPlugin,) = FunctionReferenceLib.unpack(permissionHook); + IPlugin(permissionHookPlugin).onUninstall(permissionHookUninstallDatas[i++]); } } diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 1a52b5dd..4175c5bc 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.25; import {BaseAccount} from "@eth-infinitism/account-abstraction/core/BaseAccount.sol"; import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; +import {IAccountExecute} from "@eth-infinitism/account-abstraction/interfaces/IAccountExecute.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; @@ -38,6 +39,7 @@ contract UpgradeableModularAccount is IERC165, IERC1271, IStandardExecutor, + IAccountExecute, PluginManagerInternals, PluginManager2, UUPSUpgradeable @@ -66,6 +68,7 @@ contract UpgradeableModularAccount is error ExecFromPluginNotPermitted(address plugin, bytes4 selector); error ExecFromPluginExternalNotPermitted(address plugin, address target, uint256 value, bytes data); error NativeTokenSpendingNotPermitted(address plugin); + error NotEntryPoint(); error PostExecHookReverted(address plugin, uint8 functionId, bytes revertReason); error PreExecHookReverted(address plugin, uint8 functionId, bytes revertReason); error PreRuntimeValidationHookFailed(address plugin, uint8 functionId, bytes revertReason); @@ -84,7 +87,7 @@ contract UpgradeableModularAccount is _checkPermittedCallerIfNotFromEP(); PostExecToRun[] memory postExecHooks = - _doPreExecHooks(getAccountStorage().selectorData[msg.sig].executionHooks, msg.data); + _doPreHooks(getAccountStorage().selectorData[msg.sig].executionHooks, msg.data); _; @@ -138,7 +141,7 @@ contract UpgradeableModularAccount is PostExecToRun[] memory postExecHooks; // Cache post-exec hooks in memory - postExecHooks = _doPreExecHooks(getAccountStorage().selectorData[msg.sig].executionHooks, msg.data); + postExecHooks = _doPreHooks(getAccountStorage().selectorData[msg.sig].executionHooks, msg.data); // execute the function, bubbling up any reverts (bool execSuccess, bytes memory execReturnData) = execPlugin.call(msg.data); @@ -155,6 +158,30 @@ contract UpgradeableModularAccount is return execReturnData; } + /// @notice Execution function that allows UO context to be passed to execution hooks + /// @dev This function is only callable by the EntryPoint + function executeUserOp(PackedUserOperation calldata userOp, bytes32) external { + if (msg.sender != address(_ENTRY_POINT)) { + revert NotEntryPoint(); + } + + FunctionReference userOpValidationFunction = FunctionReference.wrap(bytes21(userOp.signature[:21])); + + PostExecToRun[] memory postPermissionHooks = + _doPreHooks(getAccountStorage().validationData[userOpValidationFunction].permissionHooks, msg.data); + + (bool success, bytes memory result) = address(this).call(userOp.callData[4:]); + + if (!success) { + // Directly bubble up revert messages + assembly ("memory-safe") { + revert(add(result, 32), mload(result)) + } + } + + _doCachedPostExecHooks(postPermissionHooks); + } + /// @inheritdoc IStandardExecutor /// @notice May be validated by a default validation. function execute(address target, uint256 value, bytes calldata data) @@ -201,8 +228,11 @@ contract UpgradeableModularAccount is _doRuntimeValidation(runtimeValidationFunction, data, authorization[22:]); - // If runtime validation passes, execute the call + // If runtime validation passes, do runtime permission checks + PostExecToRun[] memory postPermissionHooks = + _doPreHooks(getAccountStorage().validationData[runtimeValidationFunction].permissionHooks, data); + // Execute the call (bool success, bytes memory returnData) = address(this).call(data); if (!success) { @@ -211,6 +241,8 @@ contract UpgradeableModularAccount is } } + _doCachedPostExecHooks(postPermissionHooks); + return returnData; } @@ -252,7 +284,7 @@ contract UpgradeableModularAccount is external initializer { - _installValidation(validationFunction, true, new bytes4[](0), installData, bytes("")); + _installValidation(validationFunction, true, new bytes4[](0), installData, bytes(""), bytes("")); emit ModularAccountInitialized(_ENTRY_POINT); } @@ -263,9 +295,12 @@ contract UpgradeableModularAccount is bool isDefault, bytes4[] memory selectors, bytes calldata installData, - bytes calldata preValidationHooks + bytes calldata preValidationHooks, + bytes calldata permissionHooks ) external wrapNativeFunction { - _installValidation(validationFunction, isDefault, selectors, installData, preValidationHooks); + _installValidation( + validationFunction, isDefault, selectors, installData, preValidationHooks, permissionHooks + ); } /// @inheritdoc IPluginManager @@ -274,9 +309,16 @@ contract UpgradeableModularAccount is FunctionReference validationFunction, bytes4[] calldata selectors, bytes calldata uninstallData, - bytes calldata preValidationHookUninstallData + bytes calldata preValidationHookUninstallData, + bytes calldata permissionHookUninstallData ) external wrapNativeFunction { - _uninstallValidation(validationFunction, selectors, uninstallData, preValidationHookUninstallData); + _uninstallValidation( + validationFunction, + selectors, + uninstallData, + preValidationHookUninstallData, + permissionHookUninstallData + ); } /// @notice ERC165 introspection @@ -344,6 +386,9 @@ contract UpgradeableModularAccount is revert UnrecognizedFunction(bytes4(userOp.callData)); } bytes4 selector = bytes4(userOp.callData); + if (selector == this.executeUserOp.selector) { + selector = bytes4(userOp.callData[4:8]); + } // Revert if the provided `authorization` less than 21 bytes long, rather than right-padding. FunctionReference userOpValidationFunction = FunctionReference.wrap(bytes21(userOp.signature[:21])); @@ -351,14 +396,14 @@ contract UpgradeableModularAccount is _checkIfValidationApplies(selector, userOpValidationFunction, isDefaultValidation); - // Check if there are exec hooks associated with the validator that require UO context, and revert if the - // call isn't to `executeUserOp` - // This check must be here because if context isn't passed, we wouldn't be able to get the exec hooks - // associated with the validator - if (getAccountStorage().validationData[userOpValidationFunction].requireUOHookCount > 0) { - /** - * && msg.sig != this.executeUserOp.selector - */ + // Check if there are permission hooks associated with the validator, and revert if the call isn't to + // `executeUserOp` + // This check must be here because if context isn't passed, we can't tell in execution which hooks should + // have ran + if ( + getAccountStorage().validationData[userOpValidationFunction].permissionHooks.length() > 0 + && bytes4(userOp.callData[:4]) != this.executeUserOp.selector + ) { revert RequireUserOperationContext(); } @@ -453,12 +498,11 @@ contract UpgradeableModularAccount is } } - function _doPreExecHooks(EnumerableSet.Bytes32Set storage executionHooks, bytes calldata data) + function _doPreHooks(EnumerableSet.Bytes32Set storage executionHooks, bytes memory data) internal returns (PostExecToRun[] memory postHooksToRun) { uint256 hooksLength = executionHooks.length(); - // Overallocate on length - not all of this may get filled up. We set the correct length later. postHooksToRun = new PostExecToRun[](hooksLength); @@ -479,7 +523,9 @@ contract UpgradeableModularAccount is (FunctionReference hookFunction, bool isPreHook, bool isPostHook) = toExecutionHook(key); if (isPreHook) { - bytes memory preExecHookReturnData = _runPreExecHook(hookFunction, data); + bytes memory preExecHookReturnData; + + preExecHookReturnData = _runPreExecHook(hookFunction, data); // If there is an associated post-exec hook, save the return data. if (isPostHook) { @@ -489,7 +535,7 @@ contract UpgradeableModularAccount is } } - function _runPreExecHook(FunctionReference preExecHook, bytes calldata data) + function _runPreExecHook(FunctionReference preExecHook, bytes memory data) internal returns (bytes memory preExecHookReturnData) { @@ -499,6 +545,7 @@ contract UpgradeableModularAccount is ) { preExecHookReturnData = returnData; } catch (bytes memory revertReason) { + // TODO: same issue with EP0.6 - we can't do bytes4 error codes in plugins revert PreExecHookReverted(plugin, functionId, revertReason); } } diff --git a/src/interfaces/IPluginManager.sol b/src/interfaces/IPluginManager.sol index 717e1fa0..32634e34 100644 --- a/src/interfaces/IPluginManager.sol +++ b/src/interfaces/IPluginManager.sol @@ -32,12 +32,15 @@ interface IPluginManager { /// @param isDefault Whether the validation function applies for all selectors in the default pool. /// @param selectors The selectors to install the validation function for. /// @param installData Optional data to be decoded and used by the plugin to setup initial plugin state. + /// @param preValidationHooks Optional pre-validation hooks to install for the validation function. + /// @param permissionHooks Optional permission hooks to install for the validation function. function installValidation( FunctionReference validationFunction, bool isDefault, bytes4[] memory selectors, bytes calldata installData, - bytes calldata preValidationHooks + bytes calldata preValidationHooks, + bytes calldata permissionHooks ) external; /// @notice Uninstall a validation function from a set of execution selectors. @@ -46,11 +49,15 @@ interface IPluginManager { /// @param selectors The selectors to uninstall the validation function for. /// @param uninstallData Optional data to be decoded and used by the plugin to clear plugin data for the /// account. + /// @param preValidationHookUninstallData Optional data to be decoded and used by the plugin to clear account + /// data + /// @param permissionHookUninstallData Optional data to be decoded and used by the plugin to clear account data function uninstallValidation( FunctionReference validationFunction, bytes4[] calldata selectors, bytes calldata uninstallData, - bytes calldata preValidationHookUninstallData + bytes calldata preValidationHookUninstallData, + bytes calldata permissionHookUninstallData ) external; /// @notice Uninstall a plugin from the modular account. diff --git a/test/account/AccountLoupe.t.sol b/test/account/AccountLoupe.t.sol index fa92ab00..412e548c 100644 --- a/test/account/AccountLoupe.t.sol +++ b/test/account/AccountLoupe.t.sol @@ -43,7 +43,12 @@ contract AccountLoupeTest is AccountTestBase { bytes[] memory installDatas = new bytes[](2); vm.prank(address(entryPoint)); account1.installValidation( - ownerValidation, true, new bytes4[](0), bytes(""), abi.encode(preValidationHooks, installDatas) + ownerValidation, + true, + new bytes4[](0), + bytes(""), + abi.encode(preValidationHooks, installDatas), + bytes("") ); } diff --git a/test/account/ValidationIntersection.t.sol b/test/account/ValidationIntersection.t.sol index 877f8ff9..6f451a16 100644 --- a/test/account/ValidationIntersection.t.sol +++ b/test/account/ValidationIntersection.t.sol @@ -67,7 +67,12 @@ contract ValidationIntersectionTest is AccountTestBase { }); bytes[] memory installDatas = new bytes[](1); account1.installValidation( - oneHookValidation, true, new bytes4[](0), bytes(""), abi.encode(preValidationHooks, installDatas) + oneHookValidation, + true, + new bytes4[](0), + bytes(""), + abi.encode(preValidationHooks, installDatas), + bytes("") ); account1.installPlugin({ plugin: address(twoHookPlugin), @@ -87,7 +92,12 @@ contract ValidationIntersectionTest is AccountTestBase { }); installDatas = new bytes[](2); account1.installValidation( - twoHookValidation, true, new bytes4[](0), bytes(""), abi.encode(preValidationHooks, installDatas) + twoHookValidation, + true, + new bytes4[](0), + bytes(""), + abi.encode(preValidationHooks, installDatas), + bytes("") ); vm.stopPrank(); } From de28c60b150be6a9b23ae2294f13b524e4b95792 Mon Sep 17 00:00:00 2001 From: Adam Egyed <5456061+adamegyed@users.noreply.github.com> Date: Tue, 9 Jul 2024 16:51:49 -0400 Subject: [PATCH 024/111] feat: [v0.8-develop] per validation hook data (#66) --- .solhint-test.json | 1 + src/account/AccountLoupe.sol | 3 +- src/account/AccountStorage.sol | 2 +- src/account/PluginManager2.sol | 36 +- src/account/UpgradeableModularAccount.sol | 113 ++++-- src/helpers/SparseCalldataSegmentLib.sol | 51 +++ src/interfaces/IValidation.sol | 1 + src/interfaces/IValidationHook.sol | 9 +- test/account/AccountReturnData.t.sol | 18 +- test/account/DefaultValidationTest.t.sol | 4 +- test/account/MultiValidation.t.sol | 36 +- test/account/PerHookData.t.sol | 361 ++++++++++++++++++ test/account/UpgradeableModularAccount.t.sol | 18 +- test/account/ValidationIntersection.t.sol | 20 +- test/libraries/SparseCalldataSegmentLib.t.sol | 111 ++++++ test/mocks/plugins/ComprehensivePlugin.sol | 6 +- .../plugins/MockAccessControlHookPlugin.sol | 78 ++++ test/mocks/plugins/ReturnDataPluginMocks.sol | 3 +- test/mocks/plugins/ValidationPluginMocks.sol | 6 +- test/utils/AccountTestBase.sol | 62 ++- 20 files changed, 839 insertions(+), 100 deletions(-) create mode 100644 src/helpers/SparseCalldataSegmentLib.sol create mode 100644 test/account/PerHookData.t.sol create mode 100644 test/libraries/SparseCalldataSegmentLib.t.sol create mode 100644 test/mocks/plugins/MockAccessControlHookPlugin.sol diff --git a/.solhint-test.json b/.solhint-test.json index cbd7bf02..fd2b1007 100644 --- a/.solhint-test.json +++ b/.solhint-test.json @@ -5,6 +5,7 @@ "immutable-vars-naming": ["error"], "no-unused-import": ["error"], "compiler-version": ["error", ">=0.8.19"], + "custom-errors": "off", "func-visibility": ["error", { "ignoreConstructors": true }], "max-line-length": ["error", 120], "max-states-count": ["warn", 30], diff --git a/src/account/AccountLoupe.sol b/src/account/AccountLoupe.sol index 2afe6638..3e62fe9a 100644 --- a/src/account/AccountLoupe.sol +++ b/src/account/AccountLoupe.sol @@ -76,8 +76,7 @@ abstract contract AccountLoupe is IAccountLoupe { override returns (FunctionReference[] memory preValidationHooks) { - preValidationHooks = - toFunctionReferenceArray(getAccountStorage().validationData[validationFunction].preValidationHooks); + preValidationHooks = getAccountStorage().validationData[validationFunction].preValidationHooks; } /// @inheritdoc IAccountLoupe diff --git a/src/account/AccountStorage.sol b/src/account/AccountStorage.sol index 352a3d5b..ddd8f900 100644 --- a/src/account/AccountStorage.sol +++ b/src/account/AccountStorage.sol @@ -41,7 +41,7 @@ struct ValidationData { // How many execution hooks require the UO context. uint8 requireUOHookCount; // The pre validation hooks for this function selector. - EnumerableSet.Bytes32Set preValidationHooks; + FunctionReference[] preValidationHooks; // Permission hooks for this validation function. EnumerableSet.Bytes32Set permissionHooks; } diff --git a/src/account/PluginManager2.sol b/src/account/PluginManager2.sol index f733eb60..effa1a15 100644 --- a/src/account/PluginManager2.sol +++ b/src/account/PluginManager2.sol @@ -13,11 +13,15 @@ import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; abstract contract PluginManager2 { using EnumerableSet for EnumerableSet.Bytes32Set; + // Index marking the start of the data for the validation function. + uint8 internal constant _RESERVED_VALIDATION_DATA_INDEX = 255; + error DefaultValidationAlreadySet(FunctionReference validationFunction); error PreValidationAlreadySet(FunctionReference validationFunction, FunctionReference preValidationFunction); error ValidationAlreadySet(bytes4 selector, FunctionReference validationFunction); error ValidationNotSet(bytes4 selector, FunctionReference validationFunction); error PermissionAlreadySet(FunctionReference validationFunction, ExecutionHook hook); + error PreValidationHookLimitExceeded(); function _installValidation( FunctionReference validationFunction, @@ -39,19 +43,21 @@ abstract contract PluginManager2 { for (uint256 i = 0; i < preValidationFunctions.length; ++i) { FunctionReference preValidationFunction = preValidationFunctions[i]; - if ( - !_storage.validationData[validationFunction].preValidationHooks.add( - toSetValue(preValidationFunction) - ) - ) { - revert PreValidationAlreadySet(validationFunction, preValidationFunction); - } + _storage.validationData[validationFunction].preValidationHooks.push(preValidationFunction); if (initDatas[i].length > 0) { (address preValidationPlugin,) = FunctionReferenceLib.unpack(preValidationFunction); IPlugin(preValidationPlugin).onInstall(initDatas[i]); } } + + // Avoid collision between reserved index and actual indices + if ( + _storage.validationData[validationFunction].preValidationHooks.length + > _RESERVED_VALIDATION_DATA_INDEX + ) { + revert PreValidationHookLimitExceeded(); + } } if (permissionHooks.length > 0) { @@ -110,15 +116,16 @@ abstract contract PluginManager2 { bytes[] memory preValidationHookUninstallDatas = abi.decode(preValidationHookUninstallData, (bytes[])); // Clear pre validation hooks - EnumerableSet.Bytes32Set storage preValidationHooks = + FunctionReference[] storage preValidationHooks = _storage.validationData[validationFunction].preValidationHooks; - uint256 i = 0; - while (preValidationHooks.length() > 0) { - FunctionReference preValidationFunction = toFunctionReference(preValidationHooks.at(0)); - preValidationHooks.remove(toSetValue(preValidationFunction)); - (address preValidationPlugin,) = FunctionReferenceLib.unpack(preValidationFunction); - IPlugin(preValidationPlugin).onUninstall(preValidationHookUninstallDatas[i++]); + for (uint256 i = 0; i < preValidationHooks.length; ++i) { + FunctionReference preValidationFunction = preValidationHooks[i]; + if (preValidationHookUninstallDatas[0].length > 0) { + (address preValidationPlugin,) = FunctionReferenceLib.unpack(preValidationFunction); + IPlugin(preValidationPlugin).onUninstall(preValidationHookUninstallDatas[0]); + } } + delete _storage.validationData[validationFunction].preValidationHooks; } { @@ -135,6 +142,7 @@ abstract contract PluginManager2 { IPlugin(permissionHookPlugin).onUninstall(permissionHookUninstallDatas[i++]); } } + delete _storage.validationData[validationFunction].preValidationHooks; // Because this function also calls `onUninstall`, and removes the default flag from validation, we must // assume these selectors passed in to be exhaustive. diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 4175c5bc..51137641 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -11,6 +11,7 @@ import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {FunctionReferenceLib} from "../helpers/FunctionReferenceLib.sol"; +import {SparseCalldataSegmentLib} from "../helpers/SparseCalldataSegmentLib.sol"; import {_coalescePreValidation, _coalesceValidation} from "../helpers/ValidationDataHelpers.sol"; import {IPlugin, PluginManifest} from "../interfaces/IPlugin.sol"; import {IValidation} from "../interfaces/IValidation.sol"; @@ -20,13 +21,7 @@ import {FunctionReference, IPluginManager} from "../interfaces/IPluginManager.so import {IStandardExecutor, Call} from "../interfaces/IStandardExecutor.sol"; import {AccountExecutor} from "./AccountExecutor.sol"; import {AccountLoupe} from "./AccountLoupe.sol"; -import { - AccountStorage, - getAccountStorage, - toSetValue, - toFunctionReference, - toExecutionHook -} from "./AccountStorage.sol"; +import {AccountStorage, getAccountStorage, toSetValue, toExecutionHook} from "./AccountStorage.sol"; import {AccountStorageInitializable} from "./AccountStorageInitializable.sol"; import {PluginManagerInternals} from "./PluginManagerInternals.sol"; import {PluginManager2} from "./PluginManager2.sol"; @@ -46,6 +41,7 @@ contract UpgradeableModularAccount is { using EnumerableSet for EnumerableSet.Bytes32Set; using FunctionReferenceLib for FunctionReference; + using SparseCalldataSegmentLib for bytes; struct PostExecToRun { bytes preExecHookReturnData; @@ -68,6 +64,7 @@ contract UpgradeableModularAccount is error ExecFromPluginNotPermitted(address plugin, bytes4 selector); error ExecFromPluginExternalNotPermitted(address plugin, address target, uint256 value, bytes data); error NativeTokenSpendingNotPermitted(address plugin); + error NonCanonicalEncoding(); error NotEntryPoint(); error PostExecHookReverted(address plugin, uint8 functionId, bytes revertReason); error PreExecHookReverted(address plugin, uint8 functionId, bytes revertReason); @@ -80,6 +77,8 @@ contract UpgradeableModularAccount is error UnrecognizedFunction(bytes4 selector); error UserOpValidationFunctionMissing(bytes4 selector); error ValidationDoesNotApply(bytes4 selector, address plugin, uint8 functionId, bool isDefault); + error ValidationSignatureSegmentMissing(); + error SignatureSegmentOutOfOrder(); // Wraps execution of a native function with runtime validation and hooks // Used for upgradeTo, upgradeToAndCall, execute, executeBatch, installPlugin, uninstallPlugin @@ -407,38 +406,50 @@ contract UpgradeableModularAccount is revert RequireUserOperationContext(); } - validationData = - _doUserOpValidation(selector, userOpValidationFunction, userOp, userOp.signature[22:], userOpHash); + validationData = _doUserOpValidation(userOpValidationFunction, userOp, userOp.signature[22:], userOpHash); } // To support gas estimation, we don't fail early when the failure is caused by a signature failure function _doUserOpValidation( - bytes4 selector, FunctionReference userOpValidationFunction, PackedUserOperation memory userOp, bytes calldata signature, bytes32 userOpHash - ) internal returns (uint256 validationData) { - userOp.signature = signature; + ) internal returns (uint256) { + // Set up the per-hook data tracking fields + bytes calldata signatureSegment; + (signatureSegment, signature) = signature.getNextSegment(); - if (userOpValidationFunction.isEmpty()) { - // If the validation function is empty, then the call cannot proceed. - revert UserOpValidationFunctionMissing(selector); - } - - uint256 currentValidationData; + uint256 validationData; // Do preUserOpValidation hooks - EnumerableSet.Bytes32Set storage preUserOpValidationHooks = + FunctionReference[] memory preUserOpValidationHooks = getAccountStorage().validationData[userOpValidationFunction].preValidationHooks; - uint256 preUserOpValidationHooksLength = preUserOpValidationHooks.length(); - for (uint256 i = 0; i < preUserOpValidationHooksLength; ++i) { - bytes32 key = preUserOpValidationHooks.at(i); - FunctionReference preUserOpValidationHook = toFunctionReference(key); + for (uint256 i = 0; i < preUserOpValidationHooks.length; ++i) { + // Load per-hook data, if any is present + // The segment index is the first byte of the signature + if (signatureSegment.getIndex() == i) { + // Use the current segment + userOp.signature = signatureSegment.getBody(); + + if (userOp.signature.length == 0) { + revert NonCanonicalEncoding(); + } + + // Load the next per-hook data segment + (signatureSegment, signature) = signature.getNextSegment(); + + if (signatureSegment.getIndex() <= i) { + revert SignatureSegmentOutOfOrder(); + } + } else { + userOp.signature = ""; + } - (address plugin, uint8 functionId) = preUserOpValidationHook.unpack(); - currentValidationData = IValidationHook(plugin).preUserOpValidationHook(functionId, userOp, userOpHash); + (address plugin, uint8 functionId) = preUserOpValidationHooks[i].unpack(); + uint256 currentValidationData = + IValidationHook(plugin).preUserOpValidationHook(functionId, userOp, userOpHash); if (uint160(currentValidationData) > 1) { // If the aggregator is not 0 or 1, it is an unexpected value @@ -449,16 +460,24 @@ contract UpgradeableModularAccount is // Run the user op validationFunction { + if (signatureSegment.getIndex() != _RESERVED_VALIDATION_DATA_INDEX) { + revert ValidationSignatureSegmentMissing(); + } + + userOp.signature = signatureSegment.getBody(); + (address plugin, uint8 functionId) = userOpValidationFunction.unpack(); - currentValidationData = IValidation(plugin).validateUserOp(functionId, userOp, userOpHash); + uint256 currentValidationData = IValidation(plugin).validateUserOp(functionId, userOp, userOpHash); - if (preUserOpValidationHooksLength != 0) { + if (preUserOpValidationHooks.length != 0) { // If we have other validation data we need to coalesce with validationData = _coalesceValidation(validationData, currentValidationData); } else { validationData = currentValidationData; } } + + return validationData; } function _doRuntimeValidation( @@ -466,18 +485,38 @@ contract UpgradeableModularAccount is bytes calldata callData, bytes calldata authorizationData ) internal { + // Set up the per-hook data tracking fields + bytes calldata authSegment; + (authSegment, authorizationData) = authorizationData.getNextSegment(); + // run all preRuntimeValidation hooks - EnumerableSet.Bytes32Set storage preRuntimeValidationHooks = + FunctionReference[] memory preRuntimeValidationHooks = getAccountStorage().validationData[runtimeValidationFunction].preValidationHooks; - uint256 preRuntimeValidationHooksLength = preRuntimeValidationHooks.length(); - for (uint256 i = 0; i < preRuntimeValidationHooksLength; ++i) { - bytes32 key = preRuntimeValidationHooks.at(i); - FunctionReference preRuntimeValidationHook = toFunctionReference(key); + for (uint256 i = 0; i < preRuntimeValidationHooks.length; ++i) { + bytes memory currentAuthData; + + if (authSegment.getIndex() == i) { + // Use the current segment + currentAuthData = authSegment.getBody(); + + if (currentAuthData.length == 0) { + revert NonCanonicalEncoding(); + } + + // Load the next per-hook data segment + (authSegment, authorizationData) = authorizationData.getNextSegment(); - (address hookPlugin, uint8 hookFunctionId) = preRuntimeValidationHook.unpack(); + if (authSegment.getIndex() <= i) { + revert SignatureSegmentOutOfOrder(); + } + } else { + currentAuthData = ""; + } + + (address hookPlugin, uint8 hookFunctionId) = preRuntimeValidationHooks[i].unpack(); try IValidationHook(hookPlugin).preRuntimeValidationHook( - hookFunctionId, msg.sender, msg.value, callData + hookFunctionId, msg.sender, msg.value, callData, currentAuthData ) // forgefmt: disable-start // solhint-disable-next-line no-empty-blocks @@ -487,9 +526,13 @@ contract UpgradeableModularAccount is } } + if (authSegment.getIndex() != _RESERVED_VALIDATION_DATA_INDEX) { + revert ValidationSignatureSegmentMissing(); + } + (address plugin, uint8 functionId) = runtimeValidationFunction.unpack(); - try IValidation(plugin).validateRuntime(functionId, msg.sender, msg.value, callData, authorizationData) + try IValidation(plugin).validateRuntime(functionId, msg.sender, msg.value, callData, authSegment.getBody()) // forgefmt: disable-start // solhint-disable-next-line no-empty-blocks {} catch (bytes memory revertReason) { diff --git a/src/helpers/SparseCalldataSegmentLib.sol b/src/helpers/SparseCalldataSegmentLib.sol new file mode 100644 index 00000000..0a6cc541 --- /dev/null +++ b/src/helpers/SparseCalldataSegmentLib.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.25; + +/// @title Sparse Calldata Segment Library +/// @notice Library 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 { + /// @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:]; + } +} diff --git a/src/interfaces/IValidation.sol b/src/interfaces/IValidation.sol index b3adcd3d..38c8a139 100644 --- a/src/interfaces/IValidation.sol +++ b/src/interfaces/IValidation.sol @@ -23,6 +23,7 @@ interface IValidation is IPlugin { /// @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( uint8 functionId, address sender, diff --git a/src/interfaces/IValidationHook.sol b/src/interfaces/IValidationHook.sol index 8eb7a61d..8300bbb8 100644 --- a/src/interfaces/IValidationHook.sol +++ b/src/interfaces/IValidationHook.sol @@ -24,8 +24,13 @@ interface IValidationHook is IPlugin { /// @param sender The caller address. /// @param value The call value. /// @param data The calldata sent. - function preRuntimeValidationHook(uint8 functionId, address sender, uint256 value, bytes calldata data) - external; + function preRuntimeValidationHook( + uint8 functionId, + address sender, + uint256 value, + bytes calldata data, + bytes calldata authorization + ) external; // TODO: support this hook type within the account & in the manifest diff --git a/test/account/AccountReturnData.t.sol b/test/account/AccountReturnData.t.sol index 8e8f3215..fc9fd615 100644 --- a/test/account/AccountReturnData.t.sol +++ b/test/account/AccountReturnData.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {FunctionReference} from "../../src/helpers/FunctionReferenceLib.sol"; +import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; import {Call} from "../../src/interfaces/IStandardExecutor.sol"; import {ISingleOwnerPlugin} from "../../src/plugins/owner/ISingleOwnerPlugin.sol"; @@ -59,8 +59,12 @@ contract AccountReturnDataTest is AccountTestBase { account1.execute, (address(regularResultContract), 0, abi.encodeCall(RegularResultContract.foo, ())) ), - abi.encodePacked( - singleOwnerPlugin, ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER, SELECTOR_ASSOCIATED_VALIDATION + _encodeSignature( + FunctionReferenceLib.pack( + address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) + ), + SELECTOR_ASSOCIATED_VALIDATION, + "" ) ); @@ -85,8 +89,12 @@ contract AccountReturnDataTest is AccountTestBase { bytes memory retData = account1.executeWithAuthorization( abi.encodeCall(account1.executeBatch, (calls)), - abi.encodePacked( - singleOwnerPlugin, ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER, SELECTOR_ASSOCIATED_VALIDATION + _encodeSignature( + FunctionReferenceLib.pack( + address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) + ), + SELECTOR_ASSOCIATED_VALIDATION, + "" ) ); diff --git a/test/account/DefaultValidationTest.t.sol b/test/account/DefaultValidationTest.t.sol index fc93060d..c2f118de 100644 --- a/test/account/DefaultValidationTest.t.sol +++ b/test/account/DefaultValidationTest.t.sol @@ -57,7 +57,7 @@ contract DefaultValidationTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = abi.encodePacked(ownerValidation, DEFAULT_VALIDATION, r, s, v); + userOp.signature = _encodeSignature(ownerValidation, DEFAULT_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -74,7 +74,7 @@ contract DefaultValidationTest is AccountTestBase { vm.prank(owner1); account1.executeWithAuthorization( abi.encodeCall(UpgradeableModularAccount.execute, (ethRecipient, 1 wei, "")), - abi.encodePacked(ownerValidation, DEFAULT_VALIDATION) + _encodeSignature(ownerValidation, DEFAULT_VALIDATION, "") ); assertEq(ethRecipient.balance, 2 wei); diff --git a/test/account/MultiValidation.t.sol b/test/account/MultiValidation.t.sol index 9b22f5a0..e80d022c 100644 --- a/test/account/MultiValidation.t.sol +++ b/test/account/MultiValidation.t.sol @@ -67,20 +67,24 @@ contract MultiValidationTest is AccountTestBase { ); account1.executeWithAuthorization( abi.encodeCall(IStandardExecutor.execute, (address(0), 0, "")), - abi.encodePacked( - address(validator2), - uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER), - SELECTOR_ASSOCIATED_VALIDATION + _encodeSignature( + FunctionReferenceLib.pack( + address(validator2), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) + ), + SELECTOR_ASSOCIATED_VALIDATION, + "" ) ); vm.prank(owner2); account1.executeWithAuthorization( abi.encodeCall(IStandardExecutor.execute, (address(0), 0, "")), - abi.encodePacked( - address(validator2), - uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER), - SELECTOR_ASSOCIATED_VALIDATION + _encodeSignature( + FunctionReferenceLib.pack( + address(validator2), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) + ), + SELECTOR_ASSOCIATED_VALIDATION, + "" ) ); } @@ -105,13 +109,10 @@ contract MultiValidationTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = abi.encodePacked( - address(validator2), + userOp.signature = _encodeSignature( + FunctionReferenceLib.pack(address(validator2), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER)), SELECTOR_ASSOCIATED_VALIDATION, - uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER), - r, - s, - v + abi.encodePacked(r, s, v) ); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); @@ -123,8 +124,11 @@ contract MultiValidationTest is AccountTestBase { userOp.nonce = 1; (v, r, s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = - abi.encodePacked(address(validator2), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER), r, s, v); + userOp.signature = _encodeSignature( + FunctionReferenceLib.pack(address(validator2), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER)), + SELECTOR_ASSOCIATED_VALIDATION, + abi.encodePacked(r, s, v) + ); userOps[0] = userOp; vm.expectRevert(abi.encodeWithSelector(IEntryPoint.FailedOp.selector, 0, "AA24 signature error")); diff --git a/test/account/PerHookData.t.sol b/test/account/PerHookData.t.sol new file mode 100644 index 00000000..b677951f --- /dev/null +++ b/test/account/PerHookData.t.sol @@ -0,0 +1,361 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.25; + +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; +import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; + +import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; +import {ISingleOwnerPlugin} from "../../src/plugins/owner/ISingleOwnerPlugin.sol"; +import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; + +import {MockAccessControlHookPlugin} from "../mocks/plugins/MockAccessControlHookPlugin.sol"; +import {Counter} from "../mocks/Counter.sol"; +import {AccountTestBase} from "../utils/AccountTestBase.sol"; + +contract PerHookDataTest is AccountTestBase { + using MessageHashUtils for bytes32; + + MockAccessControlHookPlugin internal _accessControlHookPlugin; + + Counter internal _counter; + + FunctionReference internal _ownerValidation; + + uint256 public constant CALL_GAS_LIMIT = 50000; + uint256 public constant VERIFICATION_GAS_LIMIT = 1200000; + + function setUp() public { + _counter = new Counter(); + + _accessControlHookPlugin = new MockAccessControlHookPlugin(); + + // Write over `account1` with a new account proxy, with different initialization. + + address accountImplementation = address(factory.accountImplementation()); + + account1 = UpgradeableModularAccount(payable(new ERC1967Proxy(accountImplementation, ""))); + + _ownerValidation = FunctionReferenceLib.pack( + address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) + ); + + FunctionReference accessControlHook = FunctionReferenceLib.pack( + address(_accessControlHookPlugin), uint8(MockAccessControlHookPlugin.FunctionId.PRE_VALIDATION_HOOK) + ); + + FunctionReference[] memory preValidationHooks = new FunctionReference[](1); + preValidationHooks[0] = accessControlHook; + + bytes[] memory preValidationHookData = new bytes[](1); + // Access control is restricted to only the _counter + preValidationHookData[0] = abi.encode(_counter); + + bytes memory packedPreValidationHooks = abi.encode(preValidationHooks, preValidationHookData); + + vm.prank(address(entryPoint)); + account1.installValidation( + _ownerValidation, true, new bytes4[](0), abi.encode(owner1), packedPreValidationHooks, "" + ); + + vm.deal(address(account1), 100 ether); + } + + function test_passAccessControl_userOp() public { + assertEq(_counter.number(), 0); + + (PackedUserOperation memory userOp, bytes32 userOpHash) = _getCounterUserOP(); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); + + PreValidationHookData[] memory preValidationHookData = new PreValidationHookData[](1); + preValidationHookData[0] = PreValidationHookData({index: 0, validationData: abi.encodePacked(_counter)}); + + userOp.signature = _encodeSignature( + _ownerValidation, DEFAULT_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v) + ); + + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); + userOps[0] = userOp; + + entryPoint.handleOps(userOps, beneficiary); + + assertEq(_counter.number(), 1); + } + + function test_failAccessControl_badSigData_userOp() public { + (PackedUserOperation memory userOp, bytes32 userOpHash) = _getCounterUserOP(); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); + + PreValidationHookData[] memory preValidationHookData = new PreValidationHookData[](1); + preValidationHookData[0] = PreValidationHookData({ + index: 0, + validationData: abi.encodePacked(address(0x1234123412341234123412341234123412341234)) + }); + + userOp.signature = _encodeSignature( + _ownerValidation, DEFAULT_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v) + ); + + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); + userOps[0] = userOp; + + vm.expectRevert( + abi.encodeWithSelector( + IEntryPoint.FailedOpWithRevert.selector, + 0, + "AA23 reverted", + abi.encodeWithSignature("Error(string)", "Proof doesn't match target") + ) + ); + entryPoint.handleOps(userOps, beneficiary); + } + + function test_failAccessControl_noSigData_userOp() public { + (PackedUserOperation memory userOp, bytes32 userOpHash) = _getCounterUserOP(); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); + + userOp.signature = _encodeSignature(_ownerValidation, DEFAULT_VALIDATION, abi.encodePacked(r, s, v)); + + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); + userOps[0] = userOp; + + vm.expectRevert( + abi.encodeWithSelector( + IEntryPoint.FailedOpWithRevert.selector, + 0, + "AA23 reverted", + abi.encodeWithSignature("Error(string)", "Proof doesn't match target") + ) + ); + entryPoint.handleOps(userOps, beneficiary); + } + + function test_failAccessControl_badIndexProvided_userOp() public { + (PackedUserOperation memory userOp, bytes32 userOpHash) = _getCounterUserOP(); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); + + PreValidationHookData[] memory preValidationHookData = new PreValidationHookData[](2); + preValidationHookData[0] = PreValidationHookData({index: 0, validationData: abi.encodePacked(_counter)}); + preValidationHookData[1] = PreValidationHookData({index: 1, validationData: abi.encodePacked(_counter)}); + + userOp.signature = _encodeSignature( + _ownerValidation, DEFAULT_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v) + ); + + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); + userOps[0] = userOp; + + vm.expectRevert( + abi.encodeWithSelector( + IEntryPoint.FailedOpWithRevert.selector, + 0, + "AA23 reverted", + abi.encodeWithSelector(UpgradeableModularAccount.ValidationSignatureSegmentMissing.selector) + ) + ); + entryPoint.handleOps(userOps, beneficiary); + } + + // todo: index out of order failure case with 2 pre hooks + + function test_failAccessControl_badTarget_userOp() public { + PackedUserOperation memory userOp = PackedUserOperation({ + sender: address(account1), + nonce: 0, + initCode: "", + callData: abi.encodeCall(UpgradeableModularAccount.execute, (beneficiary, 1 wei, "")), + accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), + preVerificationGas: 0, + gasFees: _encodeGas(1, 1), + paymasterAndData: "", + signature: "" + }); + + bytes32 userOpHash = entryPoint.getUserOpHash(userOp); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); + + PreValidationHookData[] memory preValidationHookData = new PreValidationHookData[](1); + preValidationHookData[0] = PreValidationHookData({index: 0, validationData: abi.encodePacked(beneficiary)}); + + userOp.signature = _encodeSignature( + _ownerValidation, DEFAULT_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v) + ); + + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); + userOps[0] = userOp; + + vm.expectRevert( + abi.encodeWithSelector( + IEntryPoint.FailedOpWithRevert.selector, + 0, + "AA23 reverted", + abi.encodeWithSignature("Error(string)", "Target not allowed") + ) + ); + entryPoint.handleOps(userOps, beneficiary); + } + + function test_failPerHookData_nonCanonicalEncoding_userOp() public { + (PackedUserOperation memory userOp, bytes32 userOpHash) = _getCounterUserOP(); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); + + PreValidationHookData[] memory preValidationHookData = new PreValidationHookData[](1); + preValidationHookData[0] = PreValidationHookData({index: 0, validationData: ""}); + + userOp.signature = _encodeSignature( + _ownerValidation, DEFAULT_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v) + ); + + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); + userOps[0] = userOp; + + vm.expectRevert( + abi.encodeWithSelector( + IEntryPoint.FailedOpWithRevert.selector, + 0, + "AA23 reverted", + abi.encodeWithSelector(UpgradeableModularAccount.NonCanonicalEncoding.selector) + ) + ); + entryPoint.handleOps(userOps, beneficiary); + } + + function test_passAccessControl_runtime() public { + assertEq(_counter.number(), 0); + + PreValidationHookData[] memory preValidationHookData = new PreValidationHookData[](1); + preValidationHookData[0] = PreValidationHookData({index: 0, validationData: abi.encodePacked(_counter)}); + + vm.prank(owner1); + account1.executeWithAuthorization( + abi.encodeCall( + UpgradeableModularAccount.execute, + (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) + ), + _encodeSignature(_ownerValidation, DEFAULT_VALIDATION, preValidationHookData, "") + ); + + assertEq(_counter.number(), 1); + } + + function test_failAccessControl_badSigData_runtime() public { + PreValidationHookData[] memory preValidationHookData = new PreValidationHookData[](1); + preValidationHookData[0] = PreValidationHookData({ + index: 0, + validationData: abi.encodePacked(address(0x1234123412341234123412341234123412341234)) + }); + + vm.prank(owner1); + vm.expectRevert( + abi.encodeWithSelector( + UpgradeableModularAccount.PreRuntimeValidationHookFailed.selector, + _accessControlHookPlugin, + uint8(MockAccessControlHookPlugin.FunctionId.PRE_VALIDATION_HOOK), + abi.encodeWithSignature("Error(string)", "Proof doesn't match target") + ) + ); + account1.executeWithAuthorization( + abi.encodeCall( + UpgradeableModularAccount.execute, + (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) + ), + _encodeSignature(_ownerValidation, DEFAULT_VALIDATION, preValidationHookData, "") + ); + } + + function test_failAccessControl_noSigData_runtime() public { + vm.prank(owner1); + vm.expectRevert( + abi.encodeWithSelector( + UpgradeableModularAccount.PreRuntimeValidationHookFailed.selector, + _accessControlHookPlugin, + uint8(MockAccessControlHookPlugin.FunctionId.PRE_VALIDATION_HOOK), + abi.encodeWithSignature("Error(string)", "Proof doesn't match target") + ) + ); + account1.executeWithAuthorization( + abi.encodeCall( + UpgradeableModularAccount.execute, + (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) + ), + _encodeSignature(_ownerValidation, DEFAULT_VALIDATION, "") + ); + } + + function test_failAccessControl_badIndexProvided_runtime() public { + PreValidationHookData[] memory preValidationHookData = new PreValidationHookData[](2); + preValidationHookData[0] = PreValidationHookData({index: 0, validationData: abi.encodePacked(_counter)}); + preValidationHookData[1] = PreValidationHookData({index: 1, validationData: abi.encodePacked(_counter)}); + + vm.prank(owner1); + vm.expectRevert( + abi.encodeWithSelector(UpgradeableModularAccount.ValidationSignatureSegmentMissing.selector) + ); + account1.executeWithAuthorization( + abi.encodeCall( + UpgradeableModularAccount.execute, + (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) + ), + _encodeSignature(_ownerValidation, DEFAULT_VALIDATION, preValidationHookData, "") + ); + } + + //todo: index out of order failure case with 2 pre hooks + + function test_failAccessControl_badTarget_runtime() public { + PreValidationHookData[] memory preValidationHookData = new PreValidationHookData[](1); + preValidationHookData[0] = PreValidationHookData({index: 0, validationData: abi.encodePacked(beneficiary)}); + + vm.prank(owner1); + vm.expectRevert( + abi.encodeWithSelector( + UpgradeableModularAccount.PreRuntimeValidationHookFailed.selector, + _accessControlHookPlugin, + uint8(MockAccessControlHookPlugin.FunctionId.PRE_VALIDATION_HOOK), + abi.encodeWithSignature("Error(string)", "Target not allowed") + ) + ); + account1.executeWithAuthorization( + abi.encodeCall(UpgradeableModularAccount.execute, (beneficiary, 1 wei, "")), + _encodeSignature(_ownerValidation, DEFAULT_VALIDATION, preValidationHookData, "") + ); + } + + function test_failPerHookData_nonCanonicalEncoding_runtime() public { + PreValidationHookData[] memory preValidationHookData = new PreValidationHookData[](1); + preValidationHookData[0] = PreValidationHookData({index: 0, validationData: ""}); + + vm.prank(owner1); + vm.expectRevert(abi.encodeWithSelector(UpgradeableModularAccount.NonCanonicalEncoding.selector)); + account1.executeWithAuthorization( + abi.encodeCall( + UpgradeableModularAccount.execute, + (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) + ), + _encodeSignature(_ownerValidation, DEFAULT_VALIDATION, preValidationHookData, "") + ); + } + + function _getCounterUserOP() internal view returns (PackedUserOperation memory, bytes32) { + PackedUserOperation memory userOp = PackedUserOperation({ + sender: address(account1), + nonce: 0, + initCode: "", + callData: abi.encodeCall( + UpgradeableModularAccount.execute, (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) + ), + accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), + preVerificationGas: 0, + gasFees: _encodeGas(1, 1), + paymasterAndData: "", + signature: "" + }); + + bytes32 userOpHash = entryPoint.getUserOpHash(userOp); + + return (userOp, userOpHash); + } +} diff --git a/test/account/UpgradeableModularAccount.t.sol b/test/account/UpgradeableModularAccount.t.sol index 46ea1d5f..3f386a60 100644 --- a/test/account/UpgradeableModularAccount.t.sol +++ b/test/account/UpgradeableModularAccount.t.sol @@ -87,7 +87,8 @@ contract UpgradeableModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = abi.encodePacked(ownerValidation, SELECTOR_ASSOCIATED_VALIDATION, r, s, v); + userOp.signature = + _encodeSignature(ownerValidation, SELECTOR_ASSOCIATED_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -116,7 +117,8 @@ contract UpgradeableModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = abi.encodePacked(ownerValidation, SELECTOR_ASSOCIATED_VALIDATION, r, s, v); + userOp.signature = + _encodeSignature(ownerValidation, SELECTOR_ASSOCIATED_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -142,7 +144,8 @@ contract UpgradeableModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = abi.encodePacked(ownerValidation, SELECTOR_ASSOCIATED_VALIDATION, r, s, v); + userOp.signature = + _encodeSignature(ownerValidation, SELECTOR_ASSOCIATED_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -168,7 +171,8 @@ contract UpgradeableModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = abi.encodePacked(ownerValidation, SELECTOR_ASSOCIATED_VALIDATION, r, s, v); + userOp.signature = + _encodeSignature(ownerValidation, SELECTOR_ASSOCIATED_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -196,7 +200,8 @@ contract UpgradeableModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = abi.encodePacked(ownerValidation, SELECTOR_ASSOCIATED_VALIDATION, r, s, v); + userOp.signature = + _encodeSignature(ownerValidation, SELECTOR_ASSOCIATED_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -227,7 +232,8 @@ contract UpgradeableModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = abi.encodePacked(ownerValidation, SELECTOR_ASSOCIATED_VALIDATION, r, s, v); + userOp.signature = + _encodeSignature(ownerValidation, SELECTOR_ASSOCIATED_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; diff --git a/test/account/ValidationIntersection.t.sol b/test/account/ValidationIntersection.t.sol index 6f451a16..faa24074 100644 --- a/test/account/ValidationIntersection.t.sol +++ b/test/account/ValidationIntersection.t.sol @@ -107,7 +107,7 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(noHookPlugin.foo.selector); - userOp.signature = abi.encodePacked(noHookValidation, SELECTOR_ASSOCIATED_VALIDATION); + userOp.signature = _encodeSignature(noHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); @@ -124,7 +124,7 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(oneHookPlugin.bar.selector); - userOp.signature = abi.encodePacked(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION); + userOp.signature = _encodeSignature(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); @@ -142,7 +142,7 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(oneHookPlugin.bar.selector); - userOp.signature = abi.encodePacked(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION); + userOp.signature = _encodeSignature(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); @@ -165,7 +165,7 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(oneHookPlugin.bar.selector); - userOp.signature = abi.encodePacked(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION); + userOp.signature = _encodeSignature(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); @@ -187,7 +187,7 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(oneHookPlugin.bar.selector); - userOp.signature = abi.encodePacked(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION); + userOp.signature = _encodeSignature(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); @@ -207,7 +207,7 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(oneHookPlugin.bar.selector); - userOp.signature = abi.encodePacked(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION); + userOp.signature = _encodeSignature(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); @@ -232,7 +232,7 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(oneHookPlugin.bar.selector); - userOp.signature = abi.encodePacked(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION); + userOp.signature = _encodeSignature(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); @@ -256,7 +256,7 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(oneHookPlugin.bar.selector); - userOp.signature = abi.encodePacked(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION); + userOp.signature = _encodeSignature(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); @@ -280,7 +280,7 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(twoHookPlugin.baz.selector); - userOp.signature = abi.encodePacked(twoHookValidation, SELECTOR_ASSOCIATED_VALIDATION); + userOp.signature = _encodeSignature(twoHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); @@ -299,7 +299,7 @@ contract ValidationIntersectionTest is AccountTestBase { PackedUserOperation memory userOp; userOp.callData = bytes.concat(twoHookPlugin.baz.selector); - userOp.signature = abi.encodePacked(twoHookValidation, SELECTOR_ASSOCIATED_VALIDATION); + userOp.signature = _encodeSignature(twoHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); vm.prank(address(entryPoint)); diff --git a/test/libraries/SparseCalldataSegmentLib.t.sol b/test/libraries/SparseCalldataSegmentLib.t.sol new file mode 100644 index 00000000..7edea4e4 --- /dev/null +++ b/test/libraries/SparseCalldataSegmentLib.t.sol @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.25; + +import {Test} from "forge-std/Test.sol"; + +import {SparseCalldataSegmentLib} from "../../src/helpers/SparseCalldataSegmentLib.sol"; + +contract SparseCalldataSegmentLibTest is Test { + 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) + { + require(segments.length == indices.length, "segments len != indices len"); + + 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/mocks/plugins/ComprehensivePlugin.sol b/test/mocks/plugins/ComprehensivePlugin.sol index 6ef654c7..4062218b 100644 --- a/test/mocks/plugins/ComprehensivePlugin.sol +++ b/test/mocks/plugins/ComprehensivePlugin.sol @@ -74,7 +74,11 @@ contract ComprehensivePlugin is IValidation, IValidationHook, IExecutionHook, Ba revert NotImplemented(); } - function preRuntimeValidationHook(uint8 functionId, address, uint256, bytes calldata) external pure override { + function preRuntimeValidationHook(uint8 functionId, address, uint256, bytes calldata, bytes calldata) + external + pure + override + { if (functionId == uint8(FunctionId.PRE_VALIDATION_HOOK_1)) { return; } else if (functionId == uint8(FunctionId.PRE_VALIDATION_HOOK_2)) { diff --git a/test/mocks/plugins/MockAccessControlHookPlugin.sol b/test/mocks/plugins/MockAccessControlHookPlugin.sol new file mode 100644 index 00000000..c17868a8 --- /dev/null +++ b/test/mocks/plugins/MockAccessControlHookPlugin.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.25; + +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; + +import {PluginMetadata, PluginManifest} from "../../../src/interfaces/IPlugin.sol"; +import {IValidationHook} from "../../../src/interfaces/IValidationHook.sol"; +import {IStandardExecutor} from "../../../src/interfaces/IStandardExecutor.sol"; +import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; + +// A pre validaiton hook plugin that uses per-hook data. +// This example enforces that the target of an `execute` call must only be the previously specified address. +// This is just a mock - it does not enforce this over `executeBatch` and other methods of making calls, and should +// not be used in production.. +contract MockAccessControlHookPlugin is IValidationHook, BasePlugin { + enum FunctionId { + PRE_VALIDATION_HOOK + } + + mapping(address account => address allowedTarget) public allowedTargets; + + function onInstall(bytes calldata data) external override { + address allowedTarget = abi.decode(data, (address)); + allowedTargets[msg.sender] = allowedTarget; + } + + function onUninstall(bytes calldata) external override { + delete allowedTargets[msg.sender]; + } + + function preUserOpValidationHook(uint8 functionId, PackedUserOperation calldata userOp, bytes32) + external + view + override + returns (uint256) + { + if (functionId == uint8(FunctionId.PRE_VALIDATION_HOOK)) { + if (bytes4(userOp.callData[:4]) == IStandardExecutor.execute.selector) { + address target = abi.decode(userOp.callData[4:36], (address)); + + // Simulate a merkle proof - require that the target address is also provided in the signature + address proof = address(bytes20(userOp.signature)); + require(proof == target, "Proof doesn't match target"); + require(target == allowedTargets[msg.sender], "Target not allowed"); + return 0; + } + } + revert NotImplemented(); + } + + function preRuntimeValidationHook( + uint8 functionId, + address, + uint256, + bytes calldata data, + bytes calldata authorization + ) external view override { + if (functionId == uint8(FunctionId.PRE_VALIDATION_HOOK)) { + if (bytes4(data[:4]) == IStandardExecutor.execute.selector) { + address target = abi.decode(data[4:36], (address)); + + // Simulate a merkle proof - require that the target address is also provided in the authorization + // data + address proof = address(bytes20(authorization)); + require(proof == target, "Proof doesn't match target"); + require(target == allowedTargets[msg.sender], "Target not allowed"); + + return; + } + } + + revert NotImplemented(); + } + + function pluginMetadata() external pure override returns (PluginMetadata memory) {} + + function pluginManifest() external pure override returns (PluginManifest memory) {} +} diff --git a/test/mocks/plugins/ReturnDataPluginMocks.sol b/test/mocks/plugins/ReturnDataPluginMocks.sol index 485b8456..c0fc0cfe 100644 --- a/test/mocks/plugins/ReturnDataPluginMocks.sol +++ b/test/mocks/plugins/ReturnDataPluginMocks.sol @@ -101,7 +101,8 @@ contract ResultConsumerPlugin is BasePlugin, IValidation { // This result should be allowed based on the manifest permission request bytes memory returnData = IStandardExecutor(msg.sender).executeWithAuthorization( abi.encodeCall(IStandardExecutor.execute, (target, 0, abi.encodeCall(RegularResultContract.foo, ()))), - abi.encodePacked(this, uint8(0), uint8(0)) // Validation function of self, selector-associated + abi.encodePacked(this, uint8(0), uint8(0), uint32(1), uint8(255)) // Validation function of self, + // selector-associated, with no auth data ); bytes32 actual = abi.decode(abi.decode(returnData, (bytes)), (bytes32)); diff --git a/test/mocks/plugins/ValidationPluginMocks.sol b/test/mocks/plugins/ValidationPluginMocks.sol index d5f75e99..f6ed4a5f 100644 --- a/test/mocks/plugins/ValidationPluginMocks.sol +++ b/test/mocks/plugins/ValidationPluginMocks.sol @@ -67,7 +67,11 @@ abstract contract MockBaseUserOpValidationPlugin is IValidation, IValidationHook // Empty stubs function pluginMetadata() external pure override returns (PluginMetadata memory) {} - function preRuntimeValidationHook(uint8, address, uint256, bytes calldata) external pure override { + function preRuntimeValidationHook(uint8, address, uint256, bytes calldata, bytes calldata) + external + pure + override + { revert NotImplemented(); } diff --git a/test/utils/AccountTestBase.sol b/test/utils/AccountTestBase.sol index 059e9cac..736b6041 100644 --- a/test/utils/AccountTestBase.sol +++ b/test/utils/AccountTestBase.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.19; import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.sol"; +import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {ISingleOwnerPlugin} from "../../src/plugins/owner/ISingleOwnerPlugin.sol"; import {SingleOwnerPlugin} from "../../src/plugins/owner/SingleOwnerPlugin.sol"; @@ -14,6 +15,8 @@ import {MSCAFactoryFixture} from "../mocks/MSCAFactoryFixture.sol"; /// @dev This contract handles common boilerplate setup for tests using UpgradeableModularAccount with /// SingleOwnerPlugin. abstract contract AccountTestBase is OptimizedTest { + using FunctionReferenceLib for FunctionReference; + EntryPoint public entryPoint; address payable public beneficiary; SingleOwnerPlugin public singleOwnerPlugin; @@ -26,6 +29,11 @@ abstract contract AccountTestBase is OptimizedTest { uint8 public constant SELECTOR_ASSOCIATED_VALIDATION = 0; uint8 public constant DEFAULT_VALIDATION = 1; + struct PreValidationHookData { + uint8 index; + bytes validationData; + } + constructor() { entryPoint = new EntryPoint(); (owner1, owner1Key) = makeAddrAndKey("owner1"); @@ -50,10 +58,12 @@ abstract contract AccountTestBase is OptimizedTest { abi.encodeCall(SingleOwnerPlugin.transferOwnership, (address(this))) ) ), - abi.encodePacked( - address(singleOwnerPlugin), - ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER, - SELECTOR_ASSOCIATED_VALIDATION + _encodeSignature( + FunctionReferenceLib.pack( + address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) + ), + SELECTOR_ASSOCIATED_VALIDATION, + "" ) ); } @@ -62,4 +72,48 @@ abstract contract AccountTestBase is OptimizedTest { function _encodeGas(uint256 g1, uint256 g2) internal pure returns (bytes32) { return bytes32(uint256((g1 << 128) + uint128(g2))); } + + // helper function to encode a signature, according to the per-hook and per-validation data format. + function _encodeSignature( + FunctionReference validationFunction, + uint8 defaultOrNot, + PreValidationHookData[] memory preValidationHookData, + bytes memory validationData + ) internal pure returns (bytes memory) { + bytes memory sig = abi.encodePacked(validationFunction, defaultOrNot); + + for (uint256 i = 0; i < preValidationHookData.length; ++i) { + sig = abi.encodePacked( + sig, + _packValidationDataWithIndex( + preValidationHookData[i].index, preValidationHookData[i].validationData + ) + ); + } + + // Index of the actual validation data is the length of the preValidationHooksRetrieved - aka + // one-past-the-end + sig = abi.encodePacked(sig, _packValidationDataWithIndex(255, validationData)); + + return sig; + } + + // overload for the case where there are no pre-validation hooks + function _encodeSignature( + FunctionReference validationFunction, + uint8 defaultOrNot, + bytes memory validationData + ) internal pure returns (bytes memory) { + PreValidationHookData[] memory emptyPreValidationHookData = new PreValidationHookData[](0); + return _encodeSignature(validationFunction, defaultOrNot, emptyPreValidationHookData, validationData); + } + + // helper function to pack validation data with an index, according to the sparse calldata segment spec. + function _packValidationDataWithIndex(uint8 index, bytes memory validationData) + internal + pure + returns (bytes memory) + { + return abi.encodePacked(uint32(validationData.length + 1), index, validationData); + } } From d8c726c9f45644269654c4ff6377d179e7285f8a Mon Sep 17 00:00:00 2001 From: Adam Egyed <5456061+adamegyed@users.noreply.github.com> Date: Wed, 10 Jul 2024 10:49:08 -0400 Subject: [PATCH 025/111] feat: [v0.8-develop] Add allowlist sample hook, refactor test base (#70) --- src/account/UpgradeableModularAccount.sol | 14 +- .../permissionhooks/AllowlistPlugin.sol | 142 ++++++++ test/account/AccountLoupe.t.sol | 13 +- test/account/DefaultValidationTest.t.sol | 15 +- test/account/MultiValidation.t.sol | 3 - test/account/PerHookData.t.sol | 65 ++-- test/account/UpgradeableModularAccount.t.sol | 23 +- .../mocks/DefaultValidationFactoryFixture.sol | 8 +- test/samples/AllowlistPlugin.t.sol | 318 ++++++++++++++++++ test/utils/AccountTestBase.sol | 134 ++++++++ test/utils/CustomValidationTestBase.sol | 48 +++ 11 files changed, 696 insertions(+), 87 deletions(-) create mode 100644 src/samples/permissionhooks/AllowlistPlugin.sol create mode 100644 test/samples/AllowlistPlugin.t.sol create mode 100644 test/utils/CustomValidationTestBase.sol diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 51137641..99f92c80 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -279,11 +279,15 @@ contract UpgradeableModularAccount is /// with user install configs. /// @dev This function is only callable once, and only by the EntryPoint. - function initializeDefaultValidation(FunctionReference validationFunction, bytes calldata installData) - external - initializer - { - _installValidation(validationFunction, true, new bytes4[](0), installData, bytes(""), bytes("")); + function initializeWithValidation( + FunctionReference validationFunction, + bool shared, + bytes4[] memory selectors, + bytes calldata installData, + bytes calldata preValidationHooks, + bytes calldata permissionHooks + ) external initializer { + _installValidation(validationFunction, shared, selectors, installData, preValidationHooks, permissionHooks); emit ModularAccountInitialized(_ENTRY_POINT); } diff --git a/src/samples/permissionhooks/AllowlistPlugin.sol b/src/samples/permissionhooks/AllowlistPlugin.sol new file mode 100644 index 00000000..209d8370 --- /dev/null +++ b/src/samples/permissionhooks/AllowlistPlugin.sol @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.25; + +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; + +import {PluginMetadata, PluginManifest} from "../../interfaces/IPlugin.sol"; +import {IValidationHook} from "../../interfaces/IValidationHook.sol"; +import {IStandardExecutor, Call} from "../../interfaces/IStandardExecutor.sol"; +import {BasePlugin} from "../../plugins/BasePlugin.sol"; + +contract AllowlistPlugin is IValidationHook, BasePlugin { + enum FunctionId { + PRE_VALIDATION_HOOK + } + + struct AllowlistInit { + address target; + bool hasSelectorAllowlist; + bytes4[] selectors; + } + + struct AllowlistEntry { + bool allowed; + bool hasSelectorAllowlist; + } + + mapping(address target => mapping(address account => AllowlistEntry)) public targetAllowlist; + mapping(address target => mapping(bytes4 selector => mapping(address account => bool))) public + selectorAllowlist; + + error TargetNotAllowed(); + error SelectorNotAllowed(); + error NoSelectorSpecified(); + + function onInstall(bytes calldata data) external override { + AllowlistInit[] memory init = abi.decode(data, (AllowlistInit[])); + + for (uint256 i = 0; i < init.length; i++) { + targetAllowlist[init[i].target][msg.sender] = AllowlistEntry(true, init[i].hasSelectorAllowlist); + + if (init[i].hasSelectorAllowlist) { + for (uint256 j = 0; j < init[i].selectors.length; j++) { + selectorAllowlist[init[i].target][init[i].selectors[j]][msg.sender] = true; + } + } + } + } + + function onUninstall(bytes calldata data) external override { + AllowlistInit[] memory init = abi.decode(data, (AllowlistInit[])); + + for (uint256 i = 0; i < init.length; i++) { + delete targetAllowlist[init[i].target][msg.sender]; + + if (init[i].hasSelectorAllowlist) { + for (uint256 j = 0; j < init[i].selectors.length; j++) { + delete selectorAllowlist[init[i].target][init[i].selectors[j]][msg.sender]; + } + } + } + } + + function setAllowlistTarget(address target, bool allowed, bool hasSelectorAllowlist) external { + targetAllowlist[target][msg.sender] = AllowlistEntry(allowed, hasSelectorAllowlist); + } + + function setAllowlistSelector(address target, bytes4 selector, bool allowed) external { + selectorAllowlist[target][selector][msg.sender] = allowed; + } + + function preUserOpValidationHook(uint8 functionId, PackedUserOperation calldata userOp, bytes32) + external + view + override + returns (uint256) + { + if (functionId == uint8(FunctionId.PRE_VALIDATION_HOOK)) { + _checkAllowlistCalldata(userOp.callData); + return 0; + } + revert NotImplemented(); + } + + function preRuntimeValidationHook(uint8 functionId, address, uint256, bytes calldata data, bytes calldata) + external + view + override + { + if (functionId == uint8(FunctionId.PRE_VALIDATION_HOOK)) { + _checkAllowlistCalldata(data); + return; + } + + revert NotImplemented(); + } + + function pluginMetadata() external pure override returns (PluginMetadata memory) { + PluginMetadata memory metadata; + metadata.name = "Allowlist Plugin"; + metadata.version = "v0.0.1"; + metadata.author = "ERC-6900 Working Group"; + + return metadata; + } + + // solhint-disable-next-line no-empty-blocks + function pluginManifest() external pure override returns (PluginManifest memory) {} + + function _checkAllowlistCalldata(bytes calldata callData) internal view { + if (bytes4(callData[:4]) == IStandardExecutor.execute.selector) { + (address target,, bytes memory data) = abi.decode(callData[4:], (address, uint256, bytes)); + _checkCallPermission(msg.sender, target, data); + } else if (bytes4(callData[:4]) == IStandardExecutor.executeBatch.selector) { + Call[] memory calls = abi.decode(callData[4:], (Call[])); + + for (uint256 i = 0; i < calls.length; i++) { + _checkCallPermission(msg.sender, calls[i].target, calls[i].data); + } + } + } + + function _checkCallPermission(address account, address target, bytes memory data) internal view { + AllowlistEntry storage entry = targetAllowlist[target][account]; + (bool allowed, bool hasSelectorAllowlist) = (entry.allowed, entry.hasSelectorAllowlist); + + if (!allowed) { + revert TargetNotAllowed(); + } + + if (hasSelectorAllowlist) { + if (data.length < 4) { + revert NoSelectorSpecified(); + } + + bytes4 selector = bytes4(data); + + if (!selectorAllowlist[target][selector][account]) { + revert SelectorNotAllowed(); + } + } + } +} diff --git a/test/account/AccountLoupe.t.sol b/test/account/AccountLoupe.t.sol index 412e548c..a89d04cb 100644 --- a/test/account/AccountLoupe.t.sol +++ b/test/account/AccountLoupe.t.sol @@ -7,7 +7,6 @@ import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/Functio import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; import {IPluginManager} from "../../src/interfaces/IPluginManager.sol"; import {IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; -import {ISingleOwnerPlugin} from "../../src/plugins/owner/ISingleOwnerPlugin.sol"; import {ComprehensivePlugin} from "../mocks/plugins/ComprehensivePlugin.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; @@ -15,8 +14,6 @@ import {AccountTestBase} from "../utils/AccountTestBase.sol"; contract AccountLoupeTest is AccountTestBase { ComprehensivePlugin public comprehensivePlugin; - FunctionReference public ownerValidation; - event ReceivedCall(bytes msgData, uint256 msgValue); function setUp() public { @@ -28,10 +25,6 @@ contract AccountLoupeTest is AccountTestBase { vm.prank(address(entryPoint)); account1.installPlugin(address(comprehensivePlugin), manifestHash, "", new FunctionReference[](0)); - ownerValidation = FunctionReferenceLib.pack( - address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) - ); - FunctionReference[] memory preValidationHooks = new FunctionReference[](2); preValidationHooks[0] = FunctionReferenceLib.pack( address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.PRE_VALIDATION_HOOK_1) @@ -43,7 +36,7 @@ contract AccountLoupeTest is AccountTestBase { bytes[] memory installDatas = new bytes[](2); vm.prank(address(entryPoint)); account1.installValidation( - ownerValidation, + _ownerValidation, true, new bytes4[](0), bytes(""), @@ -111,7 +104,7 @@ contract AccountLoupeTest is AccountTestBase { validations = account1.getValidations(account1.execute.selector); assertEq(validations.length, 1); - assertEq(FunctionReference.unwrap(validations[0]), FunctionReference.unwrap(ownerValidation)); + assertEq(FunctionReference.unwrap(validations[0]), FunctionReference.unwrap(_ownerValidation)); } function test_pluginLoupe_getExecutionHooks() public { @@ -152,7 +145,7 @@ contract AccountLoupeTest is AccountTestBase { } function test_pluginLoupe_getValidationHooks() public { - FunctionReference[] memory hooks = account1.getPreValidationHooks(ownerValidation); + FunctionReference[] memory hooks = account1.getPreValidationHooks(_ownerValidation); assertEq(hooks.length, 2); assertEq( diff --git a/test/account/DefaultValidationTest.t.sol b/test/account/DefaultValidationTest.t.sol index c2f118de..7324e176 100644 --- a/test/account/DefaultValidationTest.t.sol +++ b/test/account/DefaultValidationTest.t.sol @@ -5,8 +5,6 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; -import {ISingleOwnerPlugin} from "../../src/plugins/owner/ISingleOwnerPlugin.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; import {DefaultValidationFactoryFixture} from "../mocks/DefaultValidationFactoryFixture.sol"; @@ -16,11 +14,6 @@ contract DefaultValidationTest is AccountTestBase { DefaultValidationFactoryFixture public defaultValidationFactoryFixture; - uint256 public constant CALL_GAS_LIMIT = 50000; - uint256 public constant VERIFICATION_GAS_LIMIT = 1200000; - - FunctionReference public ownerValidation; - address public ethRecipient; function setUp() public { @@ -32,10 +25,6 @@ contract DefaultValidationTest is AccountTestBase { ethRecipient = makeAddr("ethRecipient"); vm.deal(ethRecipient, 1 wei); - - ownerValidation = FunctionReferenceLib.pack( - address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) - ); } function test_defaultValidation_userOp_simple() public { @@ -57,7 +46,7 @@ contract DefaultValidationTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = _encodeSignature(ownerValidation, DEFAULT_VALIDATION, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature(_ownerValidation, DEFAULT_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -74,7 +63,7 @@ contract DefaultValidationTest is AccountTestBase { vm.prank(owner1); account1.executeWithAuthorization( abi.encodeCall(UpgradeableModularAccount.execute, (ethRecipient, 1 wei, "")), - _encodeSignature(ownerValidation, DEFAULT_VALIDATION, "") + _encodeSignature(_ownerValidation, DEFAULT_VALIDATION, "") ); assertEq(ethRecipient.balance, 2 wei); diff --git a/test/account/MultiValidation.t.sol b/test/account/MultiValidation.t.sol index e80d022c..78867f55 100644 --- a/test/account/MultiValidation.t.sol +++ b/test/account/MultiValidation.t.sol @@ -25,9 +25,6 @@ contract MultiValidationTest is AccountTestBase { address public owner2; uint256 public owner2Key; - uint256 public constant CALL_GAS_LIMIT = 50000; - uint256 public constant VERIFICATION_GAS_LIMIT = 1200000; - function setUp() public { validator2 = new SingleOwnerPlugin(); diff --git a/test/account/PerHookData.t.sol b/test/account/PerHookData.t.sol index b677951f..73729251 100644 --- a/test/account/PerHookData.t.sol +++ b/test/account/PerHookData.t.sol @@ -3,63 +3,28 @@ pragma solidity ^0.8.25; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; -import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {ISingleOwnerPlugin} from "../../src/plugins/owner/ISingleOwnerPlugin.sol"; import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; import {MockAccessControlHookPlugin} from "../mocks/plugins/MockAccessControlHookPlugin.sol"; import {Counter} from "../mocks/Counter.sol"; -import {AccountTestBase} from "../utils/AccountTestBase.sol"; +import {CustomValidationTestBase} from "../utils/CustomValidationTestBase.sol"; -contract PerHookDataTest is AccountTestBase { +contract PerHookDataTest is CustomValidationTestBase { using MessageHashUtils for bytes32; MockAccessControlHookPlugin internal _accessControlHookPlugin; Counter internal _counter; - FunctionReference internal _ownerValidation; - - uint256 public constant CALL_GAS_LIMIT = 50000; - uint256 public constant VERIFICATION_GAS_LIMIT = 1200000; - function setUp() public { _counter = new Counter(); _accessControlHookPlugin = new MockAccessControlHookPlugin(); - // Write over `account1` with a new account proxy, with different initialization. - - address accountImplementation = address(factory.accountImplementation()); - - account1 = UpgradeableModularAccount(payable(new ERC1967Proxy(accountImplementation, ""))); - - _ownerValidation = FunctionReferenceLib.pack( - address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) - ); - - FunctionReference accessControlHook = FunctionReferenceLib.pack( - address(_accessControlHookPlugin), uint8(MockAccessControlHookPlugin.FunctionId.PRE_VALIDATION_HOOK) - ); - - FunctionReference[] memory preValidationHooks = new FunctionReference[](1); - preValidationHooks[0] = accessControlHook; - - bytes[] memory preValidationHookData = new bytes[](1); - // Access control is restricted to only the _counter - preValidationHookData[0] = abi.encode(_counter); - - bytes memory packedPreValidationHooks = abi.encode(preValidationHooks, preValidationHookData); - - vm.prank(address(entryPoint)); - account1.installValidation( - _ownerValidation, true, new bytes4[](0), abi.encode(owner1), packedPreValidationHooks, "" - ); - - vm.deal(address(account1), 100 ether); + _customValidationSetup(); } function test_passAccessControl_userOp() public { @@ -358,4 +323,28 @@ contract PerHookDataTest is AccountTestBase { return (userOp, userOpHash); } + + // Test config + + function _initialValidationConfig() + internal + virtual + override + returns (FunctionReference, bool, bytes4[] memory, bytes memory, bytes memory, bytes memory) + { + FunctionReference accessControlHook = FunctionReferenceLib.pack( + address(_accessControlHookPlugin), uint8(MockAccessControlHookPlugin.FunctionId.PRE_VALIDATION_HOOK) + ); + + FunctionReference[] memory preValidationHooks = new FunctionReference[](1); + preValidationHooks[0] = accessControlHook; + + bytes[] memory preValidationHookData = new bytes[](1); + // Access control is restricted to only the counter + preValidationHookData[0] = abi.encode(_counter); + + bytes memory packedPreValidationHooks = abi.encode(preValidationHooks, preValidationHookData); + + return (_ownerValidation, true, new bytes4[](0), abi.encode(owner1), packedPreValidationHooks, ""); + } } diff --git a/test/account/UpgradeableModularAccount.t.sol b/test/account/UpgradeableModularAccount.t.sol index 3f386a60..668852b8 100644 --- a/test/account/UpgradeableModularAccount.t.sol +++ b/test/account/UpgradeableModularAccount.t.sol @@ -10,7 +10,7 @@ import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; import {PluginManagerInternals} from "../../src/account/PluginManagerInternals.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; +import {FunctionReference} from "../../src/helpers/FunctionReferenceLib.sol"; import {PluginManifest} from "../../src/interfaces/IPlugin.sol"; import {IAccountLoupe} from "../../src/interfaces/IAccountLoupe.sol"; import {IPluginManager} from "../../src/interfaces/IPluginManager.sol"; @@ -39,11 +39,6 @@ contract UpgradeableModularAccountTest is AccountTestBase { Counter public counter; PluginManifest internal _manifest; - FunctionReference public ownerValidation; - - uint256 public constant CALL_GAS_LIMIT = 50000; - uint256 public constant VERIFICATION_GAS_LIMIT = 1200000; - event PluginInstalled(address indexed plugin, bytes32 manifestHash, FunctionReference[] dependencies); event PluginUninstalled(address indexed plugin, bool indexed callbacksSucceeded); event ReceivedCall(bytes msgData, uint256 msgValue); @@ -61,10 +56,6 @@ contract UpgradeableModularAccountTest is AccountTestBase { vm.deal(ethRecipient, 1 wei); counter = new Counter(); counter.increment(); // amoritze away gas cost of zero->nonzero transition - - ownerValidation = FunctionReferenceLib.pack( - address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) - ); } function test_deployAccount() public { @@ -88,7 +79,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); userOp.signature = - _encodeSignature(ownerValidation, SELECTOR_ASSOCIATED_VALIDATION, abi.encodePacked(r, s, v)); + _encodeSignature(_ownerValidation, SELECTOR_ASSOCIATED_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -118,7 +109,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); userOp.signature = - _encodeSignature(ownerValidation, SELECTOR_ASSOCIATED_VALIDATION, abi.encodePacked(r, s, v)); + _encodeSignature(_ownerValidation, SELECTOR_ASSOCIATED_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -145,7 +136,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); userOp.signature = - _encodeSignature(ownerValidation, SELECTOR_ASSOCIATED_VALIDATION, abi.encodePacked(r, s, v)); + _encodeSignature(_ownerValidation, SELECTOR_ASSOCIATED_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -172,7 +163,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); userOp.signature = - _encodeSignature(ownerValidation, SELECTOR_ASSOCIATED_VALIDATION, abi.encodePacked(r, s, v)); + _encodeSignature(_ownerValidation, SELECTOR_ASSOCIATED_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -201,7 +192,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); userOp.signature = - _encodeSignature(ownerValidation, SELECTOR_ASSOCIATED_VALIDATION, abi.encodePacked(r, s, v)); + _encodeSignature(_ownerValidation, SELECTOR_ASSOCIATED_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -233,7 +224,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); userOp.signature = - _encodeSignature(ownerValidation, SELECTOR_ASSOCIATED_VALIDATION, abi.encodePacked(r, s, v)); + _encodeSignature(_ownerValidation, SELECTOR_ASSOCIATED_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; diff --git a/test/mocks/DefaultValidationFactoryFixture.sol b/test/mocks/DefaultValidationFactoryFixture.sol index a4836ad8..0145ab8a 100644 --- a/test/mocks/DefaultValidationFactoryFixture.sol +++ b/test/mocks/DefaultValidationFactoryFixture.sol @@ -55,11 +55,15 @@ contract DefaultValidationFactoryFixture is OptimizedTest { new ERC1967Proxy{salt: getSalt(owner, salt)}(address(accountImplementation), ""); // point proxy to actual implementation and init plugins - UpgradeableModularAccount(payable(addr)).initializeDefaultValidation( + UpgradeableModularAccount(payable(addr)).initializeWithValidation( FunctionReferenceLib.pack( address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) ), - pluginInstallData + true, + new bytes4[](0), + pluginInstallData, + "", + "" ); } diff --git a/test/samples/AllowlistPlugin.t.sol b/test/samples/AllowlistPlugin.t.sol new file mode 100644 index 00000000..da70c8c3 --- /dev/null +++ b/test/samples/AllowlistPlugin.t.sol @@ -0,0 +1,318 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.25; + +import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; + +import {Call} from "../../src/interfaces/IStandardExecutor.sol"; +import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; +import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; +import {AllowlistPlugin} from "../../src/samples/permissionhooks/AllowlistPlugin.sol"; + +import {CustomValidationTestBase} from "../utils/CustomValidationTestBase.sol"; +import {Counter} from "../mocks/Counter.sol"; + +contract AllowlistPluginTest is CustomValidationTestBase { + AllowlistPlugin public allowlistPlugin; + + AllowlistPlugin.AllowlistInit[] public allowlistInit; + + Counter[] public counters; + + function setUp() public { + allowlistPlugin = new AllowlistPlugin(); + + counters = new Counter[](10); + + for (uint256 i = 0; i < counters.length; i++) { + counters[i] = new Counter(); + } + + // Don't call `_customValidationSetup` here, as we want to test various configurations of install data. + } + + function testFuzz_allowlistHook_userOp_single(uint256 seed) public { + AllowlistPlugin.AllowlistInit[] memory inits; + (inits, seed) = _generateRandomizedAllowlistInit(seed); + + _copyInitToStorage(inits); + _customValidationSetup(); + + Call[] memory calls = new Call[](1); + (calls[0], seed) = _generateRandomCall(seed); + bytes memory expectedError = _getExpectedUserOpError(calls); + + _runExecUserOp(calls[0].target, calls[0].data, expectedError); + } + + function testFuzz_allowlistHook_userOp_batch(uint256 seed) public { + AllowlistPlugin.AllowlistInit[] memory inits; + (inits, seed) = _generateRandomizedAllowlistInit(seed); + + _copyInitToStorage(inits); + _customValidationSetup(); + + Call[] memory calls; + (calls, seed) = _generateRandomCalls(seed); + bytes memory expectedError = _getExpectedUserOpError(calls); + + _runExecBatchUserOp(calls, expectedError); + } + + function testFuzz_allowlistHook_runtime_single(uint256 seed) public { + AllowlistPlugin.AllowlistInit[] memory inits; + (inits, seed) = _generateRandomizedAllowlistInit(seed); + + _copyInitToStorage(inits); + _customValidationSetup(); + + Call[] memory calls = new Call[](1); + (calls[0], seed) = _generateRandomCall(seed); + bytes memory expectedError = _getExpectedRuntimeError(calls); + + if (keccak256(expectedError) == keccak256("emptyrevert")) { + _runtimeExecExpFail(calls[0].target, calls[0].data, ""); + } else { + _runtimeExec(calls[0].target, calls[0].data, expectedError); + } + } + + function testFuzz_allowlistHook_runtime_batch(uint256 seed) public { + AllowlistPlugin.AllowlistInit[] memory inits; + (inits, seed) = _generateRandomizedAllowlistInit(seed); + + _copyInitToStorage(inits); + _customValidationSetup(); + + Call[] memory calls; + (calls, seed) = _generateRandomCalls(seed); + bytes memory expectedError = _getExpectedRuntimeError(calls); + + if (keccak256(expectedError) == keccak256("emptyrevert")) { + _runtimeExecBatchExpFail(calls, ""); + } else { + _runtimeExecBatch(calls, expectedError); + } + } + + function _generateRandomCalls(uint256 seed) internal view returns (Call[] memory, uint256) { + uint256 length = seed % 10; + seed = _next(seed); + + Call[] memory calls = new Call[](length); + + for (uint256 i = 0; i < length; i++) { + (calls[i], seed) = _generateRandomCall(seed); + } + + return (calls, seed); + } + + function _generateRandomCall(uint256 seed) internal view returns (Call memory call, uint256 newSeed) { + // Half of the time, the target is a random counter, the other half, it's a random address. + bool isCounter = seed % 2 == 0; + seed = _next(seed); + + call.target = isCounter ? address(counters[seed % counters.length]) : address(uint160(uint256(seed))); + seed = _next(seed); + + bool validSelector = seed % 2 == 0; + seed = _next(seed); + + if (validSelector) { + uint256 selectorIndex = seed % 3; + seed = _next(seed); + + if (selectorIndex == 0) { + call.data = abi.encodeCall(Counter.setNumber, (seed % 100)); + } else if (selectorIndex == 1) { + call.data = abi.encodeCall(Counter.increment, ()); + } else { + call.data = abi.encodeWithSignature("number()"); + } + + seed = _next(seed); + } else { + call.data = abi.encodePacked(bytes4(uint32(uint256(seed)))); + seed = _next(seed); + } + + return (call, seed); + } + + function _getExpectedUserOpError(Call[] memory calls) internal view returns (bytes memory) { + for (uint256 i = 0; i < calls.length; i++) { + Call memory call = calls[i]; + + (bool allowed, bool hasSelectorAllowlist) = + allowlistPlugin.targetAllowlist(call.target, address(account1)); + if (allowed) { + if ( + hasSelectorAllowlist + && !allowlistPlugin.selectorAllowlist(call.target, bytes4(call.data), address(account1)) + ) { + return abi.encodeWithSelector( + IEntryPoint.FailedOpWithRevert.selector, + 0, + "AA23 reverted", + abi.encodeWithSelector(AllowlistPlugin.SelectorNotAllowed.selector) + ); + } + } else { + return abi.encodeWithSelector( + IEntryPoint.FailedOpWithRevert.selector, + 0, + "AA23 reverted", + abi.encodeWithSelector(AllowlistPlugin.TargetNotAllowed.selector) + ); + } + } + + return ""; + } + + function _getExpectedRuntimeError(Call[] memory calls) internal view returns (bytes memory) { + for (uint256 i = 0; i < calls.length; i++) { + Call memory call = calls[i]; + + (bool allowed, bool hasSelectorAllowlist) = + allowlistPlugin.targetAllowlist(call.target, address(account1)); + if (allowed) { + if ( + hasSelectorAllowlist + && !allowlistPlugin.selectorAllowlist(call.target, bytes4(call.data), address(account1)) + ) { + return abi.encodeWithSelector( + UpgradeableModularAccount.PreRuntimeValidationHookFailed.selector, + address(allowlistPlugin), + uint8(AllowlistPlugin.FunctionId.PRE_VALIDATION_HOOK), + abi.encodeWithSelector(AllowlistPlugin.SelectorNotAllowed.selector) + ); + } + } else { + return abi.encodeWithSelector( + UpgradeableModularAccount.PreRuntimeValidationHookFailed.selector, + address(allowlistPlugin), + uint8(AllowlistPlugin.FunctionId.PRE_VALIDATION_HOOK), + abi.encodeWithSelector(AllowlistPlugin.TargetNotAllowed.selector) + ); + } + } + + // At this point, we have returned any error that would come from the AllowlistPlugin. + // But, because this is in the runtime path, the Counter itself may throw if it is not a valid selector. + + for (uint256 i = 0; i < calls.length; i++) { + Call memory call = calls[i]; + bytes4 selector = bytes4(call.data); + + if ( + selector != Counter.setNumber.selector && selector != Counter.increment.selector + && selector != bytes4(abi.encodeWithSignature("number()")) + ) { + //todo: better define a way to handle empty reverts. + return "emptyrevert"; + } + } + + return ""; + } + + function _generateRandomizedAllowlistInit(uint256 seed) + internal + view + returns (AllowlistPlugin.AllowlistInit[] memory, uint256) + { + uint256 length = seed % 10; + seed = _next(seed); + + AllowlistPlugin.AllowlistInit[] memory init = new AllowlistPlugin.AllowlistInit[](length); + + for (uint256 i = 0; i < length; i++) { + // Half the time, the target is a random counter, the other half, it's a random address. + bool isCounter = seed % 2 == 0; + seed = _next(seed); + + address target = + isCounter ? address(counters[seed % counters.length]) : address(uint160(uint256(seed))); + + bool hasSelectorAllowlist = seed % 2 == 0; + seed = _next(seed); + + uint256 selectorLength = seed % 10; + seed = _next(seed); + + bytes4[] memory selectors = new bytes4[](selectorLength); + + for (uint256 j = 0; j < selectorLength; j++) { + // half of the time, the selector is a valid selector on counter, the other half it's a random + // selector + + bool isCounterSelector = seed % 2 == 0; + seed = _next(seed); + + if (isCounterSelector) { + uint256 selectorIndex = seed % 3; + seed = _next(seed); + + if (selectorIndex == 0) { + selectors[j] = Counter.setNumber.selector; + } else if (selectorIndex == 1) { + selectors[j] = Counter.increment.selector; + } else { + selectors[j] = bytes4(abi.encodeWithSignature("number()")); + } + } else { + selectors[j] = bytes4(uint32(uint256(seed))); + seed = _next(seed); + } + + selectors[j] = bytes4(uint32(uint256(keccak256(abi.encodePacked(seed, j))))); + seed = _next(seed); + } + + init[i] = AllowlistPlugin.AllowlistInit(target, hasSelectorAllowlist, selectors); + } + + return (init, seed); + } + + // todo: runtime paths + + // fuzz targets, fuzz target selectors. + + // Maybe pull out the helper function for running user ops and possibly expect a failure? + + function _next(uint256 seed) internal pure returns (uint256) { + return uint256(keccak256(abi.encodePacked(seed))); + } + + function _initialValidationConfig() + internal + virtual + override + returns (FunctionReference, bool, bytes4[] memory, bytes memory, bytes memory, bytes memory) + { + FunctionReference accessControlHook = FunctionReferenceLib.pack( + address(allowlistPlugin), uint8(AllowlistPlugin.FunctionId.PRE_VALIDATION_HOOK) + ); + + FunctionReference[] memory preValidationHooks = new FunctionReference[](1); + preValidationHooks[0] = accessControlHook; + + bytes[] memory preValidationHookData = new bytes[](1); + // Access control is restricted to only the counter + preValidationHookData[0] = abi.encode(allowlistInit); + + bytes memory packedPreValidationHooks = abi.encode(preValidationHooks, preValidationHookData); + + return (_ownerValidation, true, new bytes4[](0), abi.encode(owner1), packedPreValidationHooks, ""); + } + + // Unfortunately, this is a feature that solidity has only implemented in via-ir, so we need to do it manually + // to be able to run the tests in lite mode. + function _copyInitToStorage(AllowlistPlugin.AllowlistInit[] memory init) internal { + for (uint256 i = 0; i < init.length; i++) { + allowlistInit.push(init[i]); + } + } +} diff --git a/test/utils/AccountTestBase.sol b/test/utils/AccountTestBase.sol index 736b6041..f5fe033b 100644 --- a/test/utils/AccountTestBase.sol +++ b/test/utils/AccountTestBase.sol @@ -2,8 +2,11 @@ pragma solidity ^0.8.19; import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.sol"; +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; +import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; +import {IStandardExecutor, Call} from "../../src/interfaces/IStandardExecutor.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {ISingleOwnerPlugin} from "../../src/plugins/owner/ISingleOwnerPlugin.sol"; import {SingleOwnerPlugin} from "../../src/plugins/owner/SingleOwnerPlugin.sol"; @@ -16,6 +19,7 @@ import {MSCAFactoryFixture} from "../mocks/MSCAFactoryFixture.sol"; /// SingleOwnerPlugin. abstract contract AccountTestBase is OptimizedTest { using FunctionReferenceLib for FunctionReference; + using MessageHashUtils for bytes32; EntryPoint public entryPoint; address payable public beneficiary; @@ -26,9 +30,14 @@ abstract contract AccountTestBase is OptimizedTest { uint256 public owner1Key; UpgradeableModularAccount public account1; + FunctionReference internal _ownerValidation; + uint8 public constant SELECTOR_ASSOCIATED_VALIDATION = 0; uint8 public constant DEFAULT_VALIDATION = 1; + uint256 public constant CALL_GAS_LIMIT = 50000; + uint256 public constant VERIFICATION_GAS_LIMIT = 1200000; + struct PreValidationHookData { uint8 index; bytes validationData; @@ -44,6 +53,131 @@ abstract contract AccountTestBase is OptimizedTest { account1 = factory.createAccount(owner1, 0); vm.deal(address(account1), 100 ether); + + _ownerValidation = FunctionReferenceLib.pack( + address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) + ); + } + + function _runExecUserOp(address target, bytes memory callData) internal { + _runUserOp(abi.encodeCall(IStandardExecutor.execute, (target, 0, callData))); + } + + function _runExecUserOp(address target, bytes memory callData, bytes memory revertReason) internal { + _runUserOp(abi.encodeCall(IStandardExecutor.execute, (target, 0, callData)), revertReason); + } + + function _runExecBatchUserOp(Call[] memory calls) internal { + _runUserOp(abi.encodeCall(IStandardExecutor.executeBatch, (calls))); + } + + function _runExecBatchUserOp(Call[] memory calls, bytes memory revertReason) internal { + _runUserOp(abi.encodeCall(IStandardExecutor.executeBatch, (calls)), revertReason); + } + + function _runUserOp(bytes memory callData) internal { + // Run user op without expecting a revert + _runUserOp(callData, hex""); + } + + function _runUserOp(bytes memory callData, bytes memory expectedRevertData) internal { + uint256 nonce = entryPoint.getNonce(address(account1), 0); + + PackedUserOperation memory userOp = PackedUserOperation({ + sender: address(account1), + nonce: nonce, + initCode: hex"", + callData: callData, + accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), + preVerificationGas: 0, + gasFees: _encodeGas(1, 1), + paymasterAndData: hex"", + signature: hex"" + }); + + bytes32 userOpHash = entryPoint.getUserOpHash(userOp); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); + + userOp.signature = _encodeSignature( + FunctionReferenceLib.pack( + address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) + ), + DEFAULT_VALIDATION, + abi.encodePacked(r, s, v) + ); + + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); + userOps[0] = userOp; + + if (expectedRevertData.length > 0) { + vm.expectRevert(expectedRevertData); + } + entryPoint.handleOps(userOps, beneficiary); + } + + function _runtimeExec(address target, bytes memory callData) internal { + _runtimeCall(abi.encodeCall(IStandardExecutor.execute, (target, 0, callData))); + } + + function _runtimeExec(address target, bytes memory callData, bytes memory expectedRevertData) internal { + _runtimeCall(abi.encodeCall(IStandardExecutor.execute, (target, 0, callData)), expectedRevertData); + } + + function _runtimeExecExpFail(address target, bytes memory callData, bytes memory expectedRevertData) + internal + { + _runtimeCallExpFail(abi.encodeCall(IStandardExecutor.execute, (target, 0, callData)), expectedRevertData); + } + + function _runtimeExecBatch(Call[] memory calls) internal { + _runtimeCall(abi.encodeCall(IStandardExecutor.executeBatch, (calls))); + } + + function _runtimeExecBatch(Call[] memory calls, bytes memory expectedRevertData) internal { + _runtimeCall(abi.encodeCall(IStandardExecutor.executeBatch, (calls)), expectedRevertData); + } + + function _runtimeExecBatchExpFail(Call[] memory calls, bytes memory expectedRevertData) internal { + _runtimeCallExpFail(abi.encodeCall(IStandardExecutor.executeBatch, (calls)), expectedRevertData); + } + + function _runtimeCall(bytes memory callData) internal { + _runtimeCall(callData, ""); + } + + function _runtimeCall(bytes memory callData, bytes memory expectedRevertData) internal { + if (expectedRevertData.length > 0) { + vm.expectRevert(expectedRevertData); + } + + vm.prank(owner1); + account1.executeWithAuthorization( + callData, + _encodeSignature( + FunctionReferenceLib.pack( + address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) + ), + DEFAULT_VALIDATION, + "" + ) + ); + } + + // Always expects a revert, even if the revert data is zero-length. + function _runtimeCallExpFail(bytes memory callData, bytes memory expectedRevertData) internal { + vm.expectRevert(expectedRevertData); + + vm.prank(owner1); + account1.executeWithAuthorization( + callData, + _encodeSignature( + FunctionReferenceLib.pack( + address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) + ), + DEFAULT_VALIDATION, + "" + ) + ); } function _transferOwnershipToTest() internal { diff --git a/test/utils/CustomValidationTestBase.sol b/test/utils/CustomValidationTestBase.sol new file mode 100644 index 00000000..1bf76c3a --- /dev/null +++ b/test/utils/CustomValidationTestBase.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.25; + +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; + +import {FunctionReference} from "../../src/helpers/FunctionReferenceLib.sol"; +import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; + +import {AccountTestBase} from "./AccountTestBase.sol"; + +/// @dev This test contract base is used to test custom validation logic. +/// To use this, override the _initialValidationConfig function to return the desired validation configuration. +/// Then, call _customValidationSetup in the test setup. +/// Make sure to do so after any state variables that `_initialValidationConfig` relies on are set. +abstract contract CustomValidationTestBase is AccountTestBase { + function _customValidationSetup() internal { + ( + FunctionReference validationFunction, + bool shared, + bytes4[] memory selectors, + bytes memory installData, + bytes memory preValidationHooks, + bytes memory permissionHooks + ) = _initialValidationConfig(); + + address accountImplementation = address(factory.accountImplementation()); + + account1 = UpgradeableModularAccount(payable(new ERC1967Proxy{salt: 0}(accountImplementation, ""))); + + account1.initializeWithValidation( + validationFunction, shared, selectors, installData, preValidationHooks, permissionHooks + ); + + vm.deal(address(account1), 100 ether); + } + + function _initialValidationConfig() + internal + virtual + returns ( + FunctionReference validationFunction, + bool shared, + bytes4[] memory selectors, + bytes memory installData, + bytes memory preValidationHooks, + bytes memory permissionHooks + ); +} From f376bf017d17b5d4b4782aa94fb3439ca8332d79 Mon Sep 17 00:00:00 2001 From: Adam Egyed <5456061+adamegyed@users.noreply.github.com> Date: Wed, 10 Jul 2024 11:41:51 -0400 Subject: [PATCH 026/111] feat: [v0.8-develop] account self call restrictions (#78) --- src/account/UpgradeableModularAccount.sol | 73 ++++- test/account/SelfCallAuthorization.t.sol | 343 ++++++++++++++++++++++ test/utils/AccountTestBase.sol | 2 +- 3 files changed, 405 insertions(+), 13 deletions(-) create mode 100644 test/account/SelfCallAuthorization.t.sol diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 99f92c80..d57fcb0f 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -72,6 +72,7 @@ contract UpgradeableModularAccount is error RequireUserOperationContext(); error RuntimeValidationFunctionMissing(bytes4 selector); error RuntimeValidationFunctionReverted(address plugin, uint8 functionId, bytes revertReason); + error SelfCallRecursionDepthExceeded(); error SignatureValidationInvalid(address plugin, uint8 functionId); error UnexpectedAggregator(address plugin, uint8 functionId, address aggregator); error UnrecognizedFunction(bytes4 selector); @@ -216,14 +217,12 @@ contract UpgradeableModularAccount is payable returns (bytes memory) { - bytes4 execSelector = bytes4(data[:4]); - // Revert if the provided `authorization` less than 21 bytes long, rather than right-padding. FunctionReference runtimeValidationFunction = FunctionReference.wrap(bytes21(authorization[:21])); // Check if the runtime validation function is allowed to be called bool isDefaultValidation = uint8(authorization[21]) == 1; - _checkIfValidationApplies(execSelector, runtimeValidationFunction, isDefaultValidation); + _checkIfValidationAppliesCallData(data, runtimeValidationFunction, isDefaultValidation); _doRuntimeValidation(runtimeValidationFunction, data, authorization[22:]); @@ -388,16 +387,12 @@ contract UpgradeableModularAccount is if (userOp.callData.length < 4) { revert UnrecognizedFunction(bytes4(userOp.callData)); } - bytes4 selector = bytes4(userOp.callData); - if (selector == this.executeUserOp.selector) { - selector = bytes4(userOp.callData[4:8]); - } // Revert if the provided `authorization` less than 21 bytes long, rather than right-padding. FunctionReference userOpValidationFunction = FunctionReference.wrap(bytes21(userOp.signature[:21])); bool isDefaultValidation = uint8(userOp.signature[21]) == 1; - _checkIfValidationApplies(selector, userOpValidationFunction, isDefaultValidation); + _checkIfValidationAppliesCallData(userOp.callData, userOpValidationFunction, isDefaultValidation); // Check if there are permission hooks associated with the validator, and revert if the call isn't to // `executeUserOp` @@ -623,10 +618,64 @@ contract UpgradeableModularAccount is // solhint-disable-next-line no-empty-blocks function _authorizeUpgrade(address newImplementation) internal override {} - function _checkIfValidationApplies(bytes4 selector, FunctionReference validationFunction, bool isDefault) - internal - view - { + function _checkIfValidationAppliesCallData( + bytes calldata callData, + FunctionReference validationFunction, + bool isDefault + ) internal view { + bytes4 outerSelector = bytes4(callData[:4]); + if (outerSelector == this.executeUserOp.selector) { + // If the selector is executeUserOp, pull the actual selector from the following data, + // and trim the calldata to ensure the self-call decoding is still accurate. + callData = callData[4:]; + outerSelector = bytes4(callData[:4]); + } + + _checkIfValidationAppliesSelector(outerSelector, validationFunction, isDefault); + + if (outerSelector == IStandardExecutor.execute.selector) { + (address target,,) = abi.decode(callData[4:], (address, uint256, bytes)); + + if (target == address(this)) { + // There is no point to call `execute` to recurse exactly once - this is equivalent to just having + // the calldata as a top-level call. + revert SelfCallRecursionDepthExceeded(); + } + } else if (outerSelector == IStandardExecutor.executeBatch.selector) { + // executeBatch may be used to batch account actions together, by targetting the account itself. + // If this is done, we must ensure all of the inner calls are allowed by the provided validation + // function. + + (Call[] memory calls) = abi.decode(callData[4:], (Call[])); + + for (uint256 i = 0; i < calls.length; ++i) { + if (calls[i].target == address(this)) { + bytes4 nestedSelector = bytes4(calls[i].data); + + if ( + nestedSelector == IStandardExecutor.execute.selector + || nestedSelector == IStandardExecutor.executeBatch.selector + ) { + // To prevent arbitrarily-deep recursive checking, we limit the depth of self-calls to one + // for the purposes of batching. + // This means that all self-calls must occur at the top level of the batch. + // Note that plugins of other contracts using `executeWithAuthorization` may still + // independently call into this account with a different validation function, allowing + // composition of multiple batches. + revert SelfCallRecursionDepthExceeded(); + } + + _checkIfValidationAppliesSelector(nestedSelector, validationFunction, isDefault); + } + } + } + } + + function _checkIfValidationAppliesSelector( + bytes4 selector, + FunctionReference validationFunction, + bool isDefault + ) internal view { AccountStorage storage _storage = getAccountStorage(); // Check that the provided validation function is applicable to the selector diff --git a/test/account/SelfCallAuthorization.t.sol b/test/account/SelfCallAuthorization.t.sol new file mode 100644 index 00000000..840f268a --- /dev/null +++ b/test/account/SelfCallAuthorization.t.sol @@ -0,0 +1,343 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.25; + +import {IAccountExecute} from "@eth-infinitism/account-abstraction/interfaces/IAccountExecute.sol"; +import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; + +import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; +import {IStandardExecutor, Call} from "../../src/interfaces/IStandardExecutor.sol"; +import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; + +import {AccountTestBase} from "../utils/AccountTestBase.sol"; +import {DefaultValidationFactoryFixture} from "../mocks/DefaultValidationFactoryFixture.sol"; +import {ComprehensivePlugin} from "../mocks/plugins/ComprehensivePlugin.sol"; + +contract SelfCallAuthorizationTest is AccountTestBase { + DefaultValidationFactoryFixture public defaultValidationFactoryFixture; + + ComprehensivePlugin public comprehensivePlugin; + + FunctionReference public comprehensivePluginValidation; + + function setUp() public { + defaultValidationFactoryFixture = new DefaultValidationFactoryFixture(entryPoint, singleOwnerPlugin); + + account1 = UpgradeableModularAccount(payable(defaultValidationFactoryFixture.createAccount(owner1, 0))); + + vm.deal(address(account1), 100 ether); + + // install the comprehensive plugin to get new exec functions with different validations configured. + + comprehensivePlugin = new ComprehensivePlugin(); + + bytes32 manifestHash = keccak256(abi.encode(comprehensivePlugin.pluginManifest())); + vm.prank(address(entryPoint)); + account1.installPlugin(address(comprehensivePlugin), manifestHash, "", new FunctionReference[](0)); + + comprehensivePluginValidation = FunctionReferenceLib.pack( + address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.VALIDATION) + ); + } + + function test_selfCallFails_userOp() public { + // Uses default validation + _runUserOp( + abi.encodeCall(ComprehensivePlugin.foo, ()), + abi.encodeWithSelector( + IEntryPoint.FailedOpWithRevert.selector, + 0, + "AA23 reverted", + abi.encodeWithSelector( + UpgradeableModularAccount.UserOpValidationFunctionMissing.selector, + ComprehensivePlugin.foo.selector + ) + ) + ); + } + + function test_selfCallFails_execUserOp() public { + // Uses default validation + _runUserOp( + abi.encodePacked(IAccountExecute.executeUserOp.selector, abi.encodeCall(ComprehensivePlugin.foo, ())), + abi.encodeWithSelector( + IEntryPoint.FailedOpWithRevert.selector, + 0, + "AA23 reverted", + abi.encodeWithSelector( + UpgradeableModularAccount.UserOpValidationFunctionMissing.selector, + ComprehensivePlugin.foo.selector + ) + ) + ); + } + + function test_selfCallFails_runtime() public { + // Uses default validation + _runtimeCall( + abi.encodeCall(ComprehensivePlugin.foo, ()), + abi.encodeWithSelector( + UpgradeableModularAccount.UserOpValidationFunctionMissing.selector, + ComprehensivePlugin.foo.selector + ) + ); + } + + function test_selfCallPrivilegeEscalation_prevented_userOp() public { + // Using default validation, self-call bypasses custom validation needed for ComprehensivePlugin.foo + _runUserOp( + abi.encodeCall( + UpgradeableModularAccount.execute, + (address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())) + ), + abi.encodeWithSelector( + IEntryPoint.FailedOpWithRevert.selector, + 0, + "AA23 reverted", + abi.encodeWithSelector(UpgradeableModularAccount.SelfCallRecursionDepthExceeded.selector) + ) + ); + + Call[] memory calls = new Call[](1); + calls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())); + + _runUserOp( + abi.encodeCall(IStandardExecutor.executeBatch, (calls)), + abi.encodeWithSelector( + IEntryPoint.FailedOpWithRevert.selector, + 0, + "AA23 reverted", + abi.encodeWithSelector( + UpgradeableModularAccount.UserOpValidationFunctionMissing.selector, + ComprehensivePlugin.foo.selector + ) + ) + ); + } + + function test_selfCallPrivilegeEscalation_prevented_execUserOp() public { + // Using default validation, self-call bypasses custom validation needed for ComprehensivePlugin.foo + _runUserOp( + abi.encodePacked( + IAccountExecute.executeUserOp.selector, + abi.encodeCall( + UpgradeableModularAccount.execute, + (address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())) + ) + ), + abi.encodeWithSelector( + IEntryPoint.FailedOpWithRevert.selector, + 0, + "AA23 reverted", + abi.encodeWithSelector(UpgradeableModularAccount.SelfCallRecursionDepthExceeded.selector) + ) + ); + + Call[] memory calls = new Call[](1); + calls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())); + + _runUserOp( + abi.encodePacked( + IAccountExecute.executeUserOp.selector, abi.encodeCall(IStandardExecutor.executeBatch, (calls)) + ), + abi.encodeWithSelector( + IEntryPoint.FailedOpWithRevert.selector, + 0, + "AA23 reverted", + abi.encodeWithSelector( + UpgradeableModularAccount.UserOpValidationFunctionMissing.selector, + ComprehensivePlugin.foo.selector + ) + ) + ); + } + + function test_selfCallPrivilegeEscalation_prevented_runtime() public { + // Using default validation, self-call bypasses custom validation needed for ComprehensivePlugin.foo + _runtimeCall( + abi.encodeCall( + UpgradeableModularAccount.execute, + (address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())) + ), + abi.encodeWithSelector(UpgradeableModularAccount.SelfCallRecursionDepthExceeded.selector) + ); + + Call[] memory calls = new Call[](1); + calls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())); + + _runtimeExecBatchExpFail( + calls, + abi.encodeWithSelector( + UpgradeableModularAccount.UserOpValidationFunctionMissing.selector, + ComprehensivePlugin.foo.selector + ) + ); + } + + function test_batchAction_allowed_userOp() public { + _enableBatchValidation(); + + Call[] memory calls = new Call[](2); + calls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())); + calls[1] = Call(address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())); + + PackedUserOperation memory userOp = _generateUserOpWithComprehensivePluginValidation( + abi.encodeCall(IStandardExecutor.executeBatch, (calls)) + ); + + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); + userOps[0] = userOp; + + vm.expectCall(address(comprehensivePlugin), abi.encodeCall(ComprehensivePlugin.foo, ()), 2); + entryPoint.handleOps(userOps, beneficiary); + } + + function test_batchAction_allowed_execUserOp() public { + _enableBatchValidation(); + + Call[] memory calls = new Call[](2); + calls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())); + calls[1] = Call(address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())); + + PackedUserOperation memory userOp = _generateUserOpWithComprehensivePluginValidation( + abi.encodePacked( + IAccountExecute.executeUserOp.selector, abi.encodeCall(IStandardExecutor.executeBatch, (calls)) + ) + ); + + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); + userOps[0] = userOp; + + vm.expectCall(address(comprehensivePlugin), abi.encodeCall(ComprehensivePlugin.foo, ()), 2); + entryPoint.handleOps(userOps, beneficiary); + } + + function test_batchAction_allowed_runtime() public { + _enableBatchValidation(); + + Call[] memory calls = new Call[](2); + calls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())); + calls[1] = Call(address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())); + + vm.expectCall(address(comprehensivePlugin), abi.encodeCall(ComprehensivePlugin.foo, ()), 2); + account1.executeWithAuthorization( + abi.encodeCall(IStandardExecutor.executeBatch, (calls)), + _encodeSignature(comprehensivePluginValidation, SELECTOR_ASSOCIATED_VALIDATION, "") + ); + } + + function test_recursiveDepthCapped_userOp() public { + _enableBatchValidation(); + + Call[] memory innerCalls = new Call[](1); + innerCalls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())); + + Call[] memory outerCalls = new Call[](1); + outerCalls[0] = Call(address(account1), 0, abi.encodeCall(IStandardExecutor.executeBatch, (innerCalls))); + + PackedUserOperation memory userOp = _generateUserOpWithComprehensivePluginValidation( + abi.encodeCall(IStandardExecutor.executeBatch, (outerCalls)) + ); + + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); + userOps[0] = userOp; + + vm.expectRevert( + abi.encodeWithSelector( + IEntryPoint.FailedOpWithRevert.selector, + 0, + "AA23 reverted", + abi.encodeWithSelector(UpgradeableModularAccount.SelfCallRecursionDepthExceeded.selector) + ) + ); + entryPoint.handleOps(userOps, beneficiary); + } + + function test_recursiveDepthCapped_execUserOp() public { + _enableBatchValidation(); + + Call[] memory innerCalls = new Call[](1); + innerCalls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())); + + Call[] memory outerCalls = new Call[](1); + outerCalls[0] = Call(address(account1), 0, abi.encodeCall(IStandardExecutor.executeBatch, (innerCalls))); + + PackedUserOperation memory userOp = _generateUserOpWithComprehensivePluginValidation( + abi.encodePacked( + IAccountExecute.executeUserOp.selector, + abi.encodeCall(IStandardExecutor.executeBatch, (outerCalls)) + ) + ); + + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); + userOps[0] = userOp; + + vm.expectRevert( + abi.encodeWithSelector( + IEntryPoint.FailedOpWithRevert.selector, + 0, + "AA23 reverted", + abi.encodeWithSelector(UpgradeableModularAccount.SelfCallRecursionDepthExceeded.selector) + ) + ); + entryPoint.handleOps(userOps, beneficiary); + } + + function test_recursiveDepthCapped_runtime() public { + _enableBatchValidation(); + + Call[] memory innerCalls = new Call[](1); + innerCalls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())); + + Call[] memory outerCalls = new Call[](1); + outerCalls[0] = Call(address(account1), 0, abi.encodeCall(IStandardExecutor.executeBatch, (innerCalls))); + + vm.expectRevert(abi.encodeWithSelector(UpgradeableModularAccount.SelfCallRecursionDepthExceeded.selector)); + account1.executeWithAuthorization( + abi.encodeCall(IStandardExecutor.executeBatch, (outerCalls)), + _encodeSignature(comprehensivePluginValidation, SELECTOR_ASSOCIATED_VALIDATION, "") + ); + } + + function _enableBatchValidation() internal { + // Extend ComprehensivePlugin's validation function to also validate `executeBatch`, to allow the + // self-call. + + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = IStandardExecutor.executeBatch.selector; + + vm.prank(owner1); + account1.executeWithAuthorization( + abi.encodeCall( + UpgradeableModularAccount.installValidation, + (comprehensivePluginValidation, false, selectors, "", "", "") + ), + _encodeSignature(_ownerValidation, DEFAULT_VALIDATION, "") + ); + } + + function _generateUserOpWithComprehensivePluginValidation(bytes memory callData) + internal + view + returns (PackedUserOperation memory) + { + uint256 nonce = entryPoint.getNonce(address(account1), 0); + return PackedUserOperation({ + sender: address(account1), + nonce: nonce, + initCode: hex"", + callData: callData, + accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), + preVerificationGas: 0, + gasFees: _encodeGas(1, 1), + paymasterAndData: hex"", + signature: _encodeSignature( + comprehensivePluginValidation, + SELECTOR_ASSOCIATED_VALIDATION, + // Comprehensive plugin's validation function doesn't actually check anything, so we don't need to + // sign anything. + "" + ) + }); + } +} diff --git a/test/utils/AccountTestBase.sol b/test/utils/AccountTestBase.sol index f5fe033b..cc11334d 100644 --- a/test/utils/AccountTestBase.sol +++ b/test/utils/AccountTestBase.sol @@ -35,7 +35,7 @@ abstract contract AccountTestBase is OptimizedTest { uint8 public constant SELECTOR_ASSOCIATED_VALIDATION = 0; uint8 public constant DEFAULT_VALIDATION = 1; - uint256 public constant CALL_GAS_LIMIT = 50000; + uint256 public constant CALL_GAS_LIMIT = 100000; uint256 public constant VERIFICATION_GAS_LIMIT = 1200000; struct PreValidationHookData { From 072fa158e246eb708a23530cc9b306ce2de7fc85 Mon Sep 17 00:00:00 2001 From: Adam Egyed <5456061+adamegyed@users.noreply.github.com> Date: Wed, 10 Jul 2024 11:55:45 -0400 Subject: [PATCH 027/111] refactor: [v0.8-develop] invert validation mapping (#85) --- src/account/AccountLoupe.sol | 14 +++++++++++--- src/account/AccountStorage.sol | 12 ++++++++++-- src/account/PluginManager2.sol | 15 +++++---------- src/account/PluginManagerInternals.sol | 8 ++------ src/account/UpgradeableModularAccount.sol | 9 ++------- src/helpers/KnownSelectors.sol | 3 +-- src/interfaces/IAccountLoupe.sol | 8 ++++---- src/interfaces/IPluginManager.sol | 2 -- test/account/AccountLoupe.t.sol | 20 ++++++-------------- test/account/MultiValidation.t.sol | 11 +++++++---- 10 files changed, 48 insertions(+), 54 deletions(-) diff --git a/src/account/AccountLoupe.sol b/src/account/AccountLoupe.sol index 3e62fe9a..32721550 100644 --- a/src/account/AccountLoupe.sol +++ b/src/account/AccountLoupe.sol @@ -7,7 +7,7 @@ import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet import {IAccountLoupe, ExecutionHook} from "../interfaces/IAccountLoupe.sol"; import {FunctionReference, IPluginManager} from "../interfaces/IPluginManager.sol"; import {IStandardExecutor} from "../interfaces/IStandardExecutor.sol"; -import {getAccountStorage, toFunctionReferenceArray, toExecutionHook} from "./AccountStorage.sol"; +import {getAccountStorage, toExecutionHook, toSelector} from "./AccountStorage.sol"; abstract contract AccountLoupe is IAccountLoupe { using EnumerableSet for EnumerableSet.Bytes32Set; @@ -28,8 +28,16 @@ abstract contract AccountLoupe is IAccountLoupe { } /// @inheritdoc IAccountLoupe - function getValidations(bytes4 selector) external view override returns (FunctionReference[] memory) { - return toFunctionReferenceArray(getAccountStorage().selectorData[selector].validations); + function getSelectors(FunctionReference validationFunction) external view returns (bytes4[] memory) { + uint256 length = getAccountStorage().validationData[validationFunction].selectors.length(); + + bytes4[] memory selectors = new bytes4[](length); + + for (uint256 i = 0; i < length; ++i) { + selectors[i] = toSelector(getAccountStorage().validationData[validationFunction].selectors.at(i)); + } + + return selectors; } /// @inheritdoc IAccountLoupe diff --git a/src/account/AccountStorage.sol b/src/account/AccountStorage.sol index ddd8f900..4f90169f 100644 --- a/src/account/AccountStorage.sol +++ b/src/account/AccountStorage.sol @@ -29,8 +29,6 @@ struct SelectorData { bool allowDefaultValidation; // The execution hooks for this function selector. EnumerableSet.Bytes32Set executionHooks; - // Which validation functions are associated with this function selector. - EnumerableSet.Bytes32Set validations; } struct ValidationData { @@ -44,6 +42,8 @@ struct ValidationData { FunctionReference[] preValidationHooks; // Permission hooks for this validation function. EnumerableSet.Bytes32Set permissionHooks; + // The set of selectors that may be validated by this validation function. + EnumerableSet.Bytes32Set selectors; } struct AccountStorage { @@ -96,6 +96,14 @@ function toExecutionHook(bytes32 setValue) isPostHook = (uint256(setValue) >> 72) & 0xFF == 1; } +function toSetValue(bytes4 selector) pure returns (bytes32) { + return bytes32(selector); +} + +function toSelector(bytes32 setValue) pure returns (bytes4) { + return bytes4(setValue); +} + /// @dev Helper function to get all elements of a set into memory. function toFunctionReferenceArray(EnumerableSet.Bytes32Set storage set) view diff --git a/src/account/PluginManager2.sol b/src/account/PluginManager2.sol index effa1a15..dbac17c1 100644 --- a/src/account/PluginManager2.sol +++ b/src/account/PluginManager2.sol @@ -89,7 +89,7 @@ abstract contract PluginManager2 { for (uint256 i = 0; i < selectors.length; ++i) { bytes4 selector = selectors[i]; - if (!_storage.selectorData[selector].validations.add(toSetValue(validationFunction))) { + if (!_storage.validationData[validationFunction].selectors.add(toSetValue(selector))) { revert ValidationAlreadySet(selector, validationFunction); } } @@ -102,7 +102,6 @@ abstract contract PluginManager2 { function _uninstallValidation( FunctionReference validationFunction, - bytes4[] calldata selectors, bytes calldata uninstallData, bytes calldata preValidationHookUninstallData, bytes calldata permissionHookUninstallData @@ -144,14 +143,10 @@ abstract contract PluginManager2 { } delete _storage.validationData[validationFunction].preValidationHooks; - // Because this function also calls `onUninstall`, and removes the default flag from validation, we must - // assume these selectors passed in to be exhaustive. - // TODO: consider enforcing this from user-supplied install config. - for (uint256 i = 0; i < selectors.length; ++i) { - bytes4 selector = selectors[i]; - if (!_storage.selectorData[selector].validations.remove(toSetValue(validationFunction))) { - revert ValidationNotSet(selector, validationFunction); - } + // Clear selectors + while (_storage.validationData[validationFunction].selectors.length() > 0) { + bytes32 selector = _storage.validationData[validationFunction].selectors.at(0); + _storage.validationData[validationFunction].selectors.remove(selector); } if (uninstallData.length > 0) { diff --git a/src/account/PluginManagerInternals.sol b/src/account/PluginManagerInternals.sol index a9b79802..42868585 100644 --- a/src/account/PluginManagerInternals.sol +++ b/src/account/PluginManagerInternals.sol @@ -103,12 +103,10 @@ abstract contract PluginManagerInternals is IPluginManager { internal notNullFunction(validationFunction) { - SelectorData storage _selectorData = getAccountStorage().selectorData[selector]; - // Fail on duplicate validation functions. Otherwise, dependency validation functions could shadow // non-depdency validation functions. Then, if a either plugin is uninstalled, it would cause a partial // uninstall of the other. - if (!_selectorData.validations.add(toSetValue(validationFunction))) { + if (!getAccountStorage().validationData[validationFunction].selectors.add(toSetValue(selector))) { revert ValidationFunctionAlreadySet(selector, validationFunction); } } @@ -117,11 +115,9 @@ abstract contract PluginManagerInternals is IPluginManager { internal notNullFunction(validationFunction) { - SelectorData storage _selectorData = getAccountStorage().selectorData[selector]; - // May ignore return value, as the manifest hash is validated to ensure that the validation function // exists. - _selectorData.validations.remove(toSetValue(validationFunction)); + getAccountStorage().validationData[validationFunction].selectors.remove(toSetValue(selector)); } function _addExecHooks( diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index d57fcb0f..aa6dfe19 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -309,17 +309,12 @@ contract UpgradeableModularAccount is /// @notice May be validated by a default validation. function uninstallValidation( FunctionReference validationFunction, - bytes4[] calldata selectors, bytes calldata uninstallData, bytes calldata preValidationHookUninstallData, bytes calldata permissionHookUninstallData ) external wrapNativeFunction { _uninstallValidation( - validationFunction, - selectors, - uninstallData, - preValidationHookUninstallData, - permissionHookUninstallData + validationFunction, uninstallData, preValidationHookUninstallData, permissionHookUninstallData ); } @@ -685,7 +680,7 @@ contract UpgradeableModularAccount is } } else { // Not default validation, but per-selector - if (!getAccountStorage().selectorData[selector].validations.contains(toSetValue(validationFunction))) { + if (!getAccountStorage().validationData[validationFunction].selectors.contains(toSetValue(selector))) { revert UserOpValidationFunctionMissing(selector); } } diff --git a/src/helpers/KnownSelectors.sol b/src/helpers/KnownSelectors.sol index e5244d2c..1d02d2a3 100644 --- a/src/helpers/KnownSelectors.sol +++ b/src/helpers/KnownSelectors.sol @@ -34,8 +34,7 @@ library KnownSelectors { || selector == IStandardExecutor.executeWithAuthorization.selector // check against IAccountLoupe methods || selector == IAccountLoupe.getExecutionFunctionHandler.selector - || selector == IAccountLoupe.getValidations.selector - || selector == IAccountLoupe.getExecutionHooks.selector + || selector == IAccountLoupe.getSelectors.selector || selector == IAccountLoupe.getExecutionHooks.selector || selector == IAccountLoupe.getPreValidationHooks.selector || selector == IAccountLoupe.getInstalledPlugins.selector; } diff --git a/src/interfaces/IAccountLoupe.sol b/src/interfaces/IAccountLoupe.sol index b172464a..d74c5940 100644 --- a/src/interfaces/IAccountLoupe.sol +++ b/src/interfaces/IAccountLoupe.sol @@ -18,10 +18,10 @@ interface IAccountLoupe { /// @return plugin The plugin address for this selector. function getExecutionFunctionHandler(bytes4 selector) external view returns (address plugin); - /// @notice Get the validation functions for a selector. - /// @param selector The selector to get the validation functions for. - /// @return The validation functions for this selector. - function getValidations(bytes4 selector) external view returns (FunctionReference[] memory); + /// @notice Get the selectors for a validation function. + /// @param validationFunction The validation function to get the selectors for. + /// @return The allowed selectors for this validation function. + function getSelectors(FunctionReference validationFunction) external view returns (bytes4[] memory); /// @notice Get the pre and post execution hooks for a selector. /// @param selector The selector to get the hooks for. diff --git a/src/interfaces/IPluginManager.sol b/src/interfaces/IPluginManager.sol index 32634e34..d98badbf 100644 --- a/src/interfaces/IPluginManager.sol +++ b/src/interfaces/IPluginManager.sol @@ -46,7 +46,6 @@ interface IPluginManager { /// @notice Uninstall a validation function from a set of execution selectors. /// TODO: remove or update. /// @param validationFunction The validation function to uninstall. - /// @param selectors The selectors to uninstall the validation function for. /// @param uninstallData Optional data to be decoded and used by the plugin to clear plugin data for the /// account. /// @param preValidationHookUninstallData Optional data to be decoded and used by the plugin to clear account @@ -54,7 +53,6 @@ interface IPluginManager { /// @param permissionHookUninstallData Optional data to be decoded and used by the plugin to clear account data function uninstallValidation( FunctionReference validationFunction, - bytes4[] calldata selectors, bytes calldata uninstallData, bytes calldata preValidationHookUninstallData, bytes calldata permissionHookUninstallData diff --git a/test/account/AccountLoupe.t.sol b/test/account/AccountLoupe.t.sol index a89d04cb..4c0ddb88 100644 --- a/test/account/AccountLoupe.t.sol +++ b/test/account/AccountLoupe.t.sol @@ -88,23 +88,15 @@ contract AccountLoupeTest is AccountTestBase { } } - function test_pluginLoupe_getValidationFunctions() public { - FunctionReference[] memory validations = account1.getValidations(comprehensivePlugin.foo.selector); - - assertEq(validations.length, 1); - assertEq( - FunctionReference.unwrap(validations[0]), - FunctionReference.unwrap( - FunctionReferenceLib.pack( - address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.VALIDATION) - ) - ) + function test_pluginLoupe_getSelectors() public { + FunctionReference comprehensivePluginValidation = FunctionReferenceLib.pack( + address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.VALIDATION) ); - validations = account1.getValidations(account1.execute.selector); + bytes4[] memory selectors = account1.getSelectors(comprehensivePluginValidation); - assertEq(validations.length, 1); - assertEq(FunctionReference.unwrap(validations[0]), FunctionReference.unwrap(_ownerValidation)); + assertEq(selectors.length, 1); + assertEq(selectors[0], comprehensivePlugin.foo.selector); } function test_pluginLoupe_getExecutionHooks() public { diff --git a/test/account/MultiValidation.t.sol b/test/account/MultiValidation.t.sol index 78867f55..9c79be9d 100644 --- a/test/account/MultiValidation.t.sol +++ b/test/account/MultiValidation.t.sol @@ -42,10 +42,13 @@ contract MultiValidationTest is AccountTestBase { ); validations[1] = FunctionReferenceLib.pack(address(validator2), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER)); - FunctionReference[] memory validations2 = account1.getValidations(IStandardExecutor.execute.selector); - assertEq(validations2.length, 2); - assertEq(FunctionReference.unwrap(validations2[0]), FunctionReference.unwrap(validations[0])); - assertEq(FunctionReference.unwrap(validations2[1]), FunctionReference.unwrap(validations[1])); + + bytes4[] memory selectors0 = account1.getSelectors(validations[0]); + bytes4[] memory selectors1 = account1.getSelectors(validations[1]); + assertEq(selectors0.length, selectors1.length); + for (uint256 i = 0; i < selectors0.length; i++) { + assertEq(selectors0[i], selectors1[i]); + } } function test_runtimeValidation_specify() public { From cfa31b1a42790947abbdabce478cb0d3ee03c311 Mon Sep 17 00:00:00 2001 From: Adam Egyed <5456061+adamegyed@users.noreply.github.com> Date: Wed, 10 Jul 2024 13:17:57 -0400 Subject: [PATCH 028/111] feat: [v0.8-develop] remove plugin dependencies (#86) --- src/account/AccountLoupe.sol | 5 +- src/account/AccountStorage.sol | 11 +- src/account/PluginManagerInternals.sol | 189 +++++-------------- src/account/UpgradeableModularAccount.sol | 18 +- src/interfaces/IPlugin.sol | 35 +--- src/interfaces/IPluginManager.sol | 11 +- src/plugins/owner/ISingleOwnerPlugin.sol | 3 +- src/plugins/owner/SingleOwnerPlugin.sol | 49 ++--- test/account/AccountExecHooks.t.sol | 8 +- test/account/AccountLoupe.t.sol | 2 +- test/account/AccountReturnData.t.sol | 8 +- test/account/MultiValidation.t.sol | 2 +- test/account/PermittedCallPermissions.t.sol | 7 +- test/account/SelfCallAuthorization.t.sol | 2 +- test/account/UpgradeableModularAccount.t.sol | 37 ++-- test/account/ValidationIntersection.t.sol | 9 +- test/mocks/plugins/ComprehensivePlugin.sol | 20 +- test/mocks/plugins/ReturnDataPluginMocks.sol | 22 +-- test/mocks/plugins/ValidationPluginMocks.sol | 53 +++--- test/plugin/SingleOwnerPlugin.t.sol | 6 +- test/plugin/TokenReceiverPlugin.t.sol | 3 +- 21 files changed, 166 insertions(+), 334 deletions(-) diff --git a/src/account/AccountLoupe.sol b/src/account/AccountLoupe.sol index 32721550..d652f45c 100644 --- a/src/account/AccountLoupe.sol +++ b/src/account/AccountLoupe.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.25; import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import {IAccountLoupe, ExecutionHook} from "../interfaces/IAccountLoupe.sol"; import {FunctionReference, IPluginManager} from "../interfaces/IPluginManager.sol"; @@ -11,7 +12,7 @@ import {getAccountStorage, toExecutionHook, toSelector} from "./AccountStorage.s abstract contract AccountLoupe is IAccountLoupe { using EnumerableSet for EnumerableSet.Bytes32Set; - using EnumerableSet for EnumerableSet.AddressSet; + using EnumerableMap for EnumerableMap.AddressToUintMap; /// @inheritdoc IAccountLoupe function getExecutionFunctionHandler(bytes4 selector) external view override returns (address plugin) { @@ -89,6 +90,6 @@ abstract contract AccountLoupe is IAccountLoupe { /// @inheritdoc IAccountLoupe function getInstalledPlugins() external view override returns (address[] memory pluginAddresses) { - pluginAddresses = getAccountStorage().plugins.values(); + pluginAddresses = getAccountStorage().pluginManifestHashes.keys(); } } diff --git a/src/account/AccountStorage.sol b/src/account/AccountStorage.sol index 4f90169f..a7645a42 100644 --- a/src/account/AccountStorage.sol +++ b/src/account/AccountStorage.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.25; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; import {FunctionReference} from "../interfaces/IPluginManager.sol"; @@ -9,13 +10,6 @@ import {FunctionReference} from "../interfaces/IPluginManager.sol"; // bytes = keccak256("ERC6900.UpgradeableModularAccount.Storage") bytes32 constant _ACCOUNT_STORAGE_SLOT = 0x9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e40; -struct PluginData { - bytes32 manifestHash; - FunctionReference[] dependencies; - // Tracks the number of times this plugin has been used as a dependency function - uint256 dependentCount; -} - // Represents data associated with a specifc function selector. struct SelectorData { // The plugin that implements this execution function. @@ -51,8 +45,7 @@ struct AccountStorage { uint8 initialized; bool initializing; // Plugin metadata storage - EnumerableSet.AddressSet plugins; - mapping(address => PluginData) pluginData; + EnumerableMap.AddressToUintMap pluginManifestHashes; // Execution functions and their associated functions mapping(bytes4 => SelectorData) selectorData; mapping(FunctionReference validationFunction => ValidationData) validationData; diff --git a/src/account/PluginManagerInternals.sol b/src/account/PluginManagerInternals.sol index 42868585..40bb4971 100644 --- a/src/account/PluginManagerInternals.sol +++ b/src/account/PluginManagerInternals.sol @@ -3,16 +3,10 @@ pragma solidity ^0.8.25; import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import {FunctionReferenceLib} from "../helpers/FunctionReferenceLib.sol"; -import { - IPlugin, - ManifestExecutionHook, - ManifestFunction, - ManifestAssociatedFunctionType, - ManifestAssociatedFunction, - PluginManifest -} from "../interfaces/IPlugin.sol"; +import {IPlugin, ManifestExecutionHook, ManifestValidation, PluginManifest} from "../interfaces/IPlugin.sol"; import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; import {FunctionReference, IPluginManager} from "../interfaces/IPluginManager.sol"; import {KnownSelectors} from "../helpers/KnownSelectors.sol"; @@ -20,45 +14,27 @@ import {AccountStorage, getAccountStorage, SelectorData, toSetValue} from "./Acc abstract contract PluginManagerInternals is IPluginManager { using EnumerableSet for EnumerableSet.Bytes32Set; - using EnumerableSet for EnumerableSet.AddressSet; + using EnumerableMap for EnumerableMap.AddressToUintMap; using FunctionReferenceLib for FunctionReference; error ArrayLengthMismatch(); error Erc4337FunctionNotAllowed(bytes4 selector); error ExecutionFunctionAlreadySet(bytes4 selector); - error InvalidDependenciesProvided(); error InvalidPluginManifest(); error IPluginFunctionNotAllowed(bytes4 selector); - error MissingPluginDependency(address dependency); error NativeFunctionNotAllowed(bytes4 selector); error NullFunctionReference(); error NullPlugin(); error PluginAlreadyInstalled(address plugin); - error PluginDependencyViolation(address plugin); error PluginInstallCallbackFailed(address plugin, bytes revertReason); error PluginInterfaceNotSupported(address plugin); error PluginNotInstalled(address plugin); error ValidationFunctionAlreadySet(bytes4 selector, FunctionReference validationFunction); - modifier notNullFunction(FunctionReference functionReference) { - if (functionReference.isEmpty()) { - revert NullFunctionReference(); - } - _; - } - - modifier notNullPlugin(address plugin) { - if (plugin == address(0)) { - revert NullPlugin(); - } - _; - } - // Storage update operations function _setExecutionFunction(bytes4 selector, bool isPublic, bool allowDefaultValidation, address plugin) internal - notNullPlugin(plugin) { SelectorData storage _selectorData = getAccountStorage().selectorData[selector]; @@ -99,25 +75,40 @@ abstract contract PluginManagerInternals is IPluginManager { _selectorData.allowDefaultValidation = false; } - function _addValidationFunction(bytes4 selector, FunctionReference validationFunction) - internal - notNullFunction(validationFunction) - { - // Fail on duplicate validation functions. Otherwise, dependency validation functions could shadow - // non-depdency validation functions. Then, if a either plugin is uninstalled, it would cause a partial - // uninstall of the other. - if (!getAccountStorage().validationData[validationFunction].selectors.add(toSetValue(selector))) { - revert ValidationFunctionAlreadySet(selector, validationFunction); + function _addValidationFunction(address plugin, ManifestValidation memory mv) internal { + AccountStorage storage _storage = getAccountStorage(); + + FunctionReference validationFunction = FunctionReferenceLib.pack(plugin, mv.functionId); + + if (mv.isDefault) { + _storage.validationData[validationFunction].isDefault = true; + } + + if (mv.isSignatureValidation) { + _storage.validationData[validationFunction].isSignatureValidation = true; + } + + // Add the validation function to the selectors. + uint256 length = mv.selectors.length; + for (uint256 i = 0; i < length; ++i) { + bytes4 selector = mv.selectors[i]; + _storage.validationData[validationFunction].selectors.add(toSetValue(selector)); } } - function _removeValidationFunction(bytes4 selector, FunctionReference validationFunction) - internal - notNullFunction(validationFunction) - { - // May ignore return value, as the manifest hash is validated to ensure that the validation function - // exists. - getAccountStorage().validationData[validationFunction].selectors.remove(toSetValue(selector)); + function _removeValidationFunction(address plugin, ManifestValidation memory mv) internal { + AccountStorage storage _storage = getAccountStorage(); + + FunctionReference validationFunction = FunctionReferenceLib.pack(plugin, mv.functionId); + + _storage.validationData[validationFunction].isDefault = false; + _storage.validationData[validationFunction].isSignatureValidation = false; + + // Clear the selectors + while (_storage.validationData[validationFunction].selectors.length() > 0) { + bytes32 selector = _storage.validationData[validationFunction].selectors.at(0); + _storage.validationData[validationFunction].selectors.remove(selector); + } } function _addExecHooks( @@ -146,16 +137,15 @@ abstract contract PluginManagerInternals is IPluginManager { ); } - function _installPlugin( - address plugin, - bytes32 manifestHash, - bytes memory pluginInstallData, - FunctionReference[] memory dependencies - ) internal { + function _installPlugin(address plugin, bytes32 manifestHash, bytes memory pluginInstallData) internal { AccountStorage storage _storage = getAccountStorage(); + if (plugin == address(0)) { + revert NullPlugin(); + } + // Check if the plugin exists. - if (!_storage.plugins.add(plugin)) { + if (_storage.pluginManifestHashes.contains(plugin)) { revert PluginAlreadyInstalled(plugin); } @@ -170,37 +160,11 @@ abstract contract PluginManagerInternals is IPluginManager { revert InvalidPluginManifest(); } - // Check that the dependencies match the manifest. - if (dependencies.length != manifest.dependencyInterfaceIds.length) { - revert InvalidDependenciesProvided(); - } - - uint256 length = dependencies.length; - for (uint256 i = 0; i < length; ++i) { - // Check the dependency interface id over the address of the dependency. - (address dependencyAddr,) = dependencies[i].unpack(); - - // Check that the dependency is installed. - if (_storage.pluginData[dependencyAddr].manifestHash == bytes32(0)) { - revert MissingPluginDependency(dependencyAddr); - } - - // Check that the dependency supports the expected interface. - if (!ERC165Checker.supportsInterface(dependencyAddr, manifest.dependencyInterfaceIds[i])) { - revert InvalidDependenciesProvided(); - } - - // Increment the dependency's dependents counter. - _storage.pluginData[dependencyAddr].dependentCount += 1; - } - // Add the plugin metadata to the account - _storage.pluginData[plugin].manifestHash = manifestHash; - _storage.pluginData[plugin].dependencies = dependencies; + _storage.pluginManifestHashes.set(plugin, uint256(manifestHash)); // Update components according to the manifest. - - length = manifest.executionFunctions.length; + uint256 length = manifest.executionFunctions.length; for (uint256 i = 0; i < length; ++i) { bytes4 selector = manifest.executionFunctions[i].executionSelector; bool isPublic = manifest.executionFunctions[i].isPublic; @@ -210,17 +174,9 @@ abstract contract PluginManagerInternals is IPluginManager { length = manifest.validationFunctions.length; for (uint256 i = 0; i < length; ++i) { - ManifestAssociatedFunction memory mv = manifest.validationFunctions[i]; - _addValidationFunction( - mv.executionSelector, _resolveManifestFunction(mv.associatedFunction, plugin, dependencies) - ); - } - - length = manifest.signatureValidationFunctions.length; - for (uint256 i = 0; i < length; ++i) { - FunctionReference signatureValidationFunction = - FunctionReferenceLib.pack(plugin, manifest.signatureValidationFunctions[i]); - _storage.validationData[signatureValidationFunction].isSignatureValidation = true; + // Todo: limit this to only "direct runtime call" validation path (old EFP), + // and add a way for the user to specify permission/pre-val hooks here. + _addValidationFunction(plugin, manifest.validationFunctions[i]); } length = manifest.executionHooks.length; @@ -243,7 +199,7 @@ abstract contract PluginManagerInternals is IPluginManager { revert PluginInstallCallbackFailed(plugin, revertReason); } - emit PluginInstalled(plugin, manifestHash, dependencies); + emit PluginInstalled(plugin, manifestHash); } function _uninstallPlugin(address plugin, PluginManifest memory manifest, bytes memory uninstallData) @@ -252,34 +208,19 @@ abstract contract PluginManagerInternals is IPluginManager { AccountStorage storage _storage = getAccountStorage(); // Check if the plugin exists. - if (!_storage.plugins.remove(plugin)) { + if (!_storage.pluginManifestHashes.contains(plugin)) { revert PluginNotInstalled(plugin); } // Check manifest hash. - bytes32 manifestHash = _storage.pluginData[plugin].manifestHash; + bytes32 manifestHash = bytes32(_storage.pluginManifestHashes.get(plugin)); if (!_isValidPluginManifest(manifest, manifestHash)) { revert InvalidPluginManifest(); } - // Ensure that there are no dependent plugins. - if (_storage.pluginData[plugin].dependentCount != 0) { - revert PluginDependencyViolation(plugin); - } - - // Remove this plugin as a dependent from its dependencies. - FunctionReference[] memory dependencies = _storage.pluginData[plugin].dependencies; - uint256 length = dependencies.length; - for (uint256 i = 0; i < length; ++i) { - FunctionReference dependency = dependencies[i]; - (address dependencyAddr,) = dependency.unpack(); - - // Decrement the dependent count for the dependency function. - _storage.pluginData[dependencyAddr].dependentCount -= 1; - } - // Remove components according to the manifest, in reverse order (by component type) of their installation. - length = manifest.executionHooks.length; + + uint256 length = manifest.executionHooks.length; for (uint256 i = 0; i < length; ++i) { ManifestExecutionHook memory mh = manifest.executionHooks[i]; FunctionReference hookFunction = FunctionReferenceLib.pack(plugin, mh.functionId); @@ -287,19 +228,9 @@ abstract contract PluginManagerInternals is IPluginManager { _removeExecHooks(execHooks, hookFunction, mh.isPreHook, mh.isPostHook); } - length = manifest.signatureValidationFunctions.length; - for (uint256 i = 0; i < length; ++i) { - FunctionReference signatureValidationFunction = - FunctionReferenceLib.pack(plugin, manifest.signatureValidationFunctions[i]); - _storage.validationData[signatureValidationFunction].isSignatureValidation = false; - } - length = manifest.validationFunctions.length; for (uint256 i = 0; i < length; ++i) { - ManifestAssociatedFunction memory mv = manifest.validationFunctions[i]; - _removeValidationFunction( - mv.executionSelector, _resolveManifestFunction(mv.associatedFunction, plugin, dependencies) - ); + _removeValidationFunction(plugin, manifest.validationFunctions[i]); } length = manifest.executionFunctions.length; @@ -314,7 +245,7 @@ abstract contract PluginManagerInternals is IPluginManager { } // Remove the plugin metadata from the account. - delete _storage.pluginData[plugin]; + _storage.pluginManifestHashes.remove(plugin); // Clear the plugin storage for the account. bool onUninstallSuccess = true; @@ -334,20 +265,4 @@ abstract contract PluginManagerInternals is IPluginManager { { return manifestHash == keccak256(abi.encode(manifest)); } - - function _resolveManifestFunction( - ManifestFunction memory manifestFunction, - address plugin, - FunctionReference[] memory dependencies - ) internal pure returns (FunctionReference) { - if (manifestFunction.functionType == ManifestAssociatedFunctionType.SELF) { - return FunctionReferenceLib.pack(plugin, manifestFunction.functionId); - } else if (manifestFunction.functionType == ManifestAssociatedFunctionType.DEPENDENCY) { - if (manifestFunction.dependencyIndex >= dependencies.length) { - revert InvalidPluginManifest(); - } - return dependencies[manifestFunction.dependencyIndex]; - } - return FunctionReferenceLib._EMPTY_FUNCTION_REFERENCE; // Empty checks are done elsewhere - } } diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index aa6dfe19..7e59544d 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -102,7 +102,6 @@ contract UpgradeableModularAccount is // EXTERNAL FUNCTIONS /// @notice Initializes the account with a set of plugins - /// @dev No dependencies may be provided with this installation. /// @param plugins The plugins to install /// @param manifestHashes The manifest hashes of the plugins to install /// @param pluginInstallDatas The plugin install datas of the plugins to install @@ -117,10 +116,8 @@ contract UpgradeableModularAccount is revert ArrayLengthMismatch(); } - FunctionReference[] memory emptyDependencies = new FunctionReference[](0); - for (uint256 i = 0; i < length; ++i) { - _installPlugin(plugins[i], manifestHashes[i], pluginInstallDatas[i], emptyDependencies); + _installPlugin(plugins[i], manifestHashes[i], pluginInstallDatas[i]); } emit ModularAccountInitialized(_ENTRY_POINT); @@ -246,13 +243,12 @@ contract UpgradeableModularAccount is /// @inheritdoc IPluginManager /// @notice May be validated by a default validation. - function installPlugin( - address plugin, - bytes32 manifestHash, - bytes calldata pluginInstallData, - FunctionReference[] calldata dependencies - ) external override wrapNativeFunction { - _installPlugin(plugin, manifestHash, pluginInstallData, dependencies); + function installPlugin(address plugin, bytes32 manifestHash, bytes calldata pluginInstallData) + external + override + wrapNativeFunction + { + _installPlugin(plugin, manifestHash, pluginInstallData); } /// @inheritdoc IPluginManager diff --git a/src/interfaces/IPlugin.sol b/src/interfaces/IPlugin.sol index 7259d748..c7ffabde 100644 --- a/src/interfaces/IPlugin.sol +++ b/src/interfaces/IPlugin.sol @@ -3,19 +3,6 @@ pragma solidity ^0.8.25; import {IERC165} from "@openzeppelin/contracts/interfaces/IERC165.sol"; -// Forge formatter will displace the first comment for the enum field out of the enum itself, -// so annotating here to prevent that. -// forgefmt: disable-start -enum ManifestAssociatedFunctionType { - // Function is not defined. - NONE, - // Function belongs to this plugin. - SELF, - // Function belongs to an external plugin provided as a dependency during plugin installation. - DEPENDENCY -} -// forgefmt: disable-end - struct ManifestExecutionFunction { // TODO(erc6900 spec): These fields can be packed into a single word // The selector to install @@ -26,17 +13,12 @@ struct ManifestExecutionFunction { bool allowDefaultValidation; } -/// @dev For functions of type `ManifestAssociatedFunctionType.DEPENDENCY`, the MSCA MUST find the plugin address -/// of the function at `dependencies[dependencyIndex]` during the call to `installPlugin(config)`. -struct ManifestFunction { - ManifestAssociatedFunctionType functionType; +// todo: do we need these at all? Or do we fully switch to `installValidation`? +struct ManifestValidation { uint8 functionId; - uint256 dependencyIndex; -} - -struct ManifestAssociatedFunction { - bytes4 executionSelector; - ManifestFunction associatedFunction; + bool isDefault; + bool isSignatureValidation; + bytes4[] selectors; } struct ManifestExecutionHook { @@ -72,16 +54,11 @@ struct PluginMetadata { struct PluginManifest { // Execution functions defined in this plugin to be installed on the MSCA. ManifestExecutionFunction[] executionFunctions; - ManifestAssociatedFunction[] validationFunctions; + ManifestValidation[] validationFunctions; ManifestExecutionHook[] executionHooks; - uint8[] signatureValidationFunctions; // List of ERC-165 interface IDs to add to account to support introspection checks. This MUST NOT include // IPlugin's interface ID. bytes4[] interfaceIds; - // If this plugin depends on other plugins' validation functions, the interface IDs of those plugins MUST be - // provided here, with its position in the array matching the `dependencyIndex` members of `ManifestFunction` - // structs used in the manifest. - bytes4[] dependencyInterfaceIds; } interface IPlugin is IERC165 { diff --git a/src/interfaces/IPluginManager.sol b/src/interfaces/IPluginManager.sol index d98badbf..dd035b81 100644 --- a/src/interfaces/IPluginManager.sol +++ b/src/interfaces/IPluginManager.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.25; type FunctionReference is bytes21; interface IPluginManager { - event PluginInstalled(address indexed plugin, bytes32 manifestHash, FunctionReference[] dependencies); + event PluginInstalled(address indexed plugin, bytes32 manifestHash); event PluginUninstalled(address indexed plugin, bool indexed onUninstallSucceeded); @@ -13,14 +13,7 @@ interface IPluginManager { /// @param manifestHash The hash of the plugin manifest. /// @param pluginInstallData Optional data to be decoded and used by the plugin to setup initial plugin data /// for the modular account. - /// @param dependencies The dependencies of the plugin, as described in the manifest. Each FunctionReference - /// MUST be composed of an installed plugin's address and a function ID of its validation function. - function installPlugin( - address plugin, - bytes32 manifestHash, - bytes calldata pluginInstallData, - FunctionReference[] calldata dependencies - ) external; + function installPlugin(address plugin, bytes32 manifestHash, bytes calldata pluginInstallData) external; /// @notice Temporary install function - pending a different user-supplied install config & manifest validation /// path. diff --git a/src/plugins/owner/ISingleOwnerPlugin.sol b/src/plugins/owner/ISingleOwnerPlugin.sol index 57bcac80..c8f0df43 100644 --- a/src/plugins/owner/ISingleOwnerPlugin.sol +++ b/src/plugins/owner/ISingleOwnerPlugin.sol @@ -5,8 +5,7 @@ import {IValidation} from "../../interfaces/IValidation.sol"; interface ISingleOwnerPlugin is IValidation { enum FunctionId { - VALIDATION_OWNER, - SIG_VALIDATION + VALIDATION_OWNER } /// @notice This event is emitted when ownership of the account changes. diff --git a/src/plugins/owner/SingleOwnerPlugin.sol b/src/plugins/owner/SingleOwnerPlugin.sol index dbdd41b2..e335708b 100644 --- a/src/plugins/owner/SingleOwnerPlugin.sol +++ b/src/plugins/owner/SingleOwnerPlugin.sol @@ -9,12 +9,7 @@ import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/Messa import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; import {IPluginManager} from "../../interfaces/IPluginManager.sol"; import { - ManifestFunction, - ManifestAssociatedFunctionType, - ManifestAssociatedFunction, - PluginManifest, - PluginMetadata, - SelectorPermission + PluginManifest, ManifestValidation, PluginMetadata, SelectorPermission } from "../../interfaces/IPlugin.sol"; import {IStandardExecutor} from "../../interfaces/IStandardExecutor.sol"; import {IPlugin} from "../../interfaces/IPlugin.sol"; @@ -129,7 +124,7 @@ contract SingleOwnerPlugin is ISingleOwnerPlugin, BasePlugin { override returns (bytes4) { - if (functionId == uint8(FunctionId.SIG_VALIDATION)) { + if (functionId == uint8(FunctionId.VALIDATION_OWNER)) { if (SignatureChecker.isValidSignatureNow(_owners[msg.sender], digest, signature)) { return _1271_MAGIC_VALUE; } @@ -156,35 +151,23 @@ contract SingleOwnerPlugin is ISingleOwnerPlugin, BasePlugin { function pluginManifest() external pure override returns (PluginManifest memory) { PluginManifest memory manifest; - ManifestFunction memory ownerValidationFunction = ManifestFunction({ - functionType: ManifestAssociatedFunctionType.SELF, + // TODO: use default validation instead + bytes4[] memory accountSelectors = new bytes4[](5); + accountSelectors[0] = IStandardExecutor.execute.selector; + accountSelectors[1] = IStandardExecutor.executeBatch.selector; + accountSelectors[2] = IPluginManager.installPlugin.selector; + accountSelectors[3] = IPluginManager.uninstallPlugin.selector; + accountSelectors[4] = UUPSUpgradeable.upgradeToAndCall.selector; + + ManifestValidation memory ownerValidationFunction = ManifestValidation({ functionId: uint8(FunctionId.VALIDATION_OWNER), - dependencyIndex: 0 // Unused. - }); - manifest.validationFunctions = new ManifestAssociatedFunction[](5); - manifest.validationFunctions[0] = ManifestAssociatedFunction({ - executionSelector: IStandardExecutor.execute.selector, - associatedFunction: ownerValidationFunction - }); - manifest.validationFunctions[1] = ManifestAssociatedFunction({ - executionSelector: IStandardExecutor.executeBatch.selector, - associatedFunction: ownerValidationFunction - }); - manifest.validationFunctions[2] = ManifestAssociatedFunction({ - executionSelector: IPluginManager.installPlugin.selector, - associatedFunction: ownerValidationFunction - }); - manifest.validationFunctions[3] = ManifestAssociatedFunction({ - executionSelector: IPluginManager.uninstallPlugin.selector, - associatedFunction: ownerValidationFunction - }); - manifest.validationFunctions[4] = ManifestAssociatedFunction({ - executionSelector: UUPSUpgradeable.upgradeToAndCall.selector, - associatedFunction: ownerValidationFunction + isDefault: false, + isSignatureValidation: true, + selectors: accountSelectors }); - manifest.signatureValidationFunctions = new uint8[](1); - manifest.signatureValidationFunctions[0] = uint8(FunctionId.SIG_VALIDATION); + manifest.validationFunctions = new ManifestValidation[](1); + manifest.validationFunctions[0] = ownerValidationFunction; return manifest; } diff --git a/test/account/AccountExecHooks.t.sol b/test/account/AccountExecHooks.t.sol index f2e6391f..5e5049f8 100644 --- a/test/account/AccountExecHooks.t.sol +++ b/test/account/AccountExecHooks.t.sol @@ -8,7 +8,6 @@ import { PluginManifest } from "../../src/interfaces/IPlugin.sol"; import {IExecutionHook} from "../../src/interfaces/IExecutionHook.sol"; -import {FunctionReference} from "../../src/helpers/FunctionReferenceLib.sol"; import {MockPlugin} from "../mocks/MockPlugin.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; @@ -25,7 +24,7 @@ contract AccountExecHooksTest is AccountTestBase { PluginManifest internal _m1; - event PluginInstalled(address indexed plugin, bytes32 manifestHash, FunctionReference[] dependencies); + event PluginInstalled(address indexed plugin, bytes32 manifestHash); event PluginUninstalled(address indexed plugin, bool indexed callbacksSucceeded); // emitted by MockPlugin event ReceivedCall(bytes msgData, uint256 msgValue); @@ -168,14 +167,13 @@ contract AccountExecHooksTest is AccountTestBase { vm.expectEmit(true, true, true, true); emit ReceivedCall(abi.encodeCall(IPlugin.onInstall, (bytes(""))), 0); vm.expectEmit(true, true, true, true); - emit PluginInstalled(address(mockPlugin1), manifestHash1, new FunctionReference[](0)); + emit PluginInstalled(address(mockPlugin1), manifestHash1); vm.prank(address(entryPoint)); account1.installPlugin({ plugin: address(mockPlugin1), manifestHash: manifestHash1, - pluginInstallData: bytes(""), - dependencies: new FunctionReference[](0) + pluginInstallData: bytes("") }); } diff --git a/test/account/AccountLoupe.t.sol b/test/account/AccountLoupe.t.sol index 4c0ddb88..15803fa8 100644 --- a/test/account/AccountLoupe.t.sol +++ b/test/account/AccountLoupe.t.sol @@ -23,7 +23,7 @@ contract AccountLoupeTest is AccountTestBase { bytes32 manifestHash = keccak256(abi.encode(comprehensivePlugin.pluginManifest())); vm.prank(address(entryPoint)); - account1.installPlugin(address(comprehensivePlugin), manifestHash, "", new FunctionReference[](0)); + account1.installPlugin(address(comprehensivePlugin), manifestHash, ""); FunctionReference[] memory preValidationHooks = new FunctionReference[](2); preValidationHooks[0] = FunctionReferenceLib.pack( diff --git a/test/account/AccountReturnData.t.sol b/test/account/AccountReturnData.t.sol index fc9fd615..99a8b516 100644 --- a/test/account/AccountReturnData.t.sol +++ b/test/account/AccountReturnData.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; +import {FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; import {Call} from "../../src/interfaces/IStandardExecutor.sol"; import {ISingleOwnerPlugin} from "../../src/plugins/owner/ISingleOwnerPlugin.sol"; @@ -31,8 +31,7 @@ contract AccountReturnDataTest is AccountTestBase { account1.installPlugin({ plugin: address(resultCreatorPlugin), manifestHash: resultCreatorManifestHash, - pluginInstallData: "", - dependencies: new FunctionReference[](0) + pluginInstallData: "" }); // Add the result consumer plugin to the account bytes32 resultConsumerManifestHash = keccak256(abi.encode(resultConsumerPlugin.pluginManifest())); @@ -40,8 +39,7 @@ contract AccountReturnDataTest is AccountTestBase { account1.installPlugin({ plugin: address(resultConsumerPlugin), manifestHash: resultConsumerManifestHash, - pluginInstallData: "", - dependencies: new FunctionReference[](0) + pluginInstallData: "" }); } diff --git a/test/account/MultiValidation.t.sol b/test/account/MultiValidation.t.sol index 9c79be9d..e0e69a37 100644 --- a/test/account/MultiValidation.t.sol +++ b/test/account/MultiValidation.t.sol @@ -34,7 +34,7 @@ contract MultiValidationTest is AccountTestBase { function test_overlappingValidationInstall() public { bytes32 manifestHash = keccak256(abi.encode(validator2.pluginManifest())); vm.prank(address(entryPoint)); - account1.installPlugin(address(validator2), manifestHash, abi.encode(owner2), new FunctionReference[](0)); + account1.installPlugin(address(validator2), manifestHash, abi.encode(owner2)); FunctionReference[] memory validations = new FunctionReference[](2); validations[0] = FunctionReferenceLib.pack( diff --git a/test/account/PermittedCallPermissions.t.sol b/test/account/PermittedCallPermissions.t.sol index 517b2080..18257955 100644 --- a/test/account/PermittedCallPermissions.t.sol +++ b/test/account/PermittedCallPermissions.t.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.19; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {FunctionReference} from "../../src/helpers/FunctionReferenceLib.sol"; import {ResultCreatorPlugin} from "../mocks/plugins/ReturnDataPluginMocks.sol"; import {PermittedCallerPlugin} from "../mocks/plugins/PermittedCallMocks.sol"; @@ -27,8 +26,7 @@ contract PermittedCallPermissionsTest is AccountTestBase { account1.installPlugin({ plugin: address(resultCreatorPlugin), manifestHash: resultCreatorManifestHash, - pluginInstallData: "", - dependencies: new FunctionReference[](0) + pluginInstallData: "" }); // Add the permitted caller plugin to the account bytes32 permittedCallerManifestHash = keccak256(abi.encode(permittedCallerPlugin.pluginManifest())); @@ -36,8 +34,7 @@ contract PermittedCallPermissionsTest is AccountTestBase { account1.installPlugin({ plugin: address(permittedCallerPlugin), manifestHash: permittedCallerManifestHash, - pluginInstallData: "", - dependencies: new FunctionReference[](0) + pluginInstallData: "" }); } diff --git a/test/account/SelfCallAuthorization.t.sol b/test/account/SelfCallAuthorization.t.sol index 840f268a..fda2061a 100644 --- a/test/account/SelfCallAuthorization.t.sol +++ b/test/account/SelfCallAuthorization.t.sol @@ -33,7 +33,7 @@ contract SelfCallAuthorizationTest is AccountTestBase { bytes32 manifestHash = keccak256(abi.encode(comprehensivePlugin.pluginManifest())); vm.prank(address(entryPoint)); - account1.installPlugin(address(comprehensivePlugin), manifestHash, "", new FunctionReference[](0)); + account1.installPlugin(address(comprehensivePlugin), manifestHash, ""); comprehensivePluginValidation = FunctionReferenceLib.pack( address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.VALIDATION) diff --git a/test/account/UpgradeableModularAccount.t.sol b/test/account/UpgradeableModularAccount.t.sol index 668852b8..de3fda30 100644 --- a/test/account/UpgradeableModularAccount.t.sol +++ b/test/account/UpgradeableModularAccount.t.sol @@ -10,7 +10,6 @@ import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; import {PluginManagerInternals} from "../../src/account/PluginManagerInternals.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {FunctionReference} from "../../src/helpers/FunctionReferenceLib.sol"; import {PluginManifest} from "../../src/interfaces/IPlugin.sol"; import {IAccountLoupe} from "../../src/interfaces/IAccountLoupe.sol"; import {IPluginManager} from "../../src/interfaces/IPluginManager.sol"; @@ -39,7 +38,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { Counter public counter; PluginManifest internal _manifest; - event PluginInstalled(address indexed plugin, bytes32 manifestHash, FunctionReference[] dependencies); + event PluginInstalled(address indexed plugin, bytes32 manifestHash); event PluginUninstalled(address indexed plugin, bool indexed callbacksSucceeded); event ReceivedCall(bytes msgData, uint256 msgValue); @@ -241,12 +240,11 @@ contract UpgradeableModularAccountTest is AccountTestBase { bytes32 manifestHash = keccak256(abi.encode(tokenReceiverPlugin.pluginManifest())); vm.expectEmit(true, true, true, true); - emit PluginInstalled(address(tokenReceiverPlugin), manifestHash, new FunctionReference[](0)); + emit PluginInstalled(address(tokenReceiverPlugin), manifestHash); IPluginManager(account1).installPlugin({ plugin: address(tokenReceiverPlugin), manifestHash: manifestHash, - pluginInstallData: abi.encode(uint48(1 days)), - dependencies: new FunctionReference[](0) + pluginInstallData: abi.encode(uint48(1 days)) }); address[] memory plugins = IAccountLoupe(account1).getInstalledPlugins(); @@ -266,8 +264,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { IPluginManager(account1).installPlugin({ plugin: address(mockPluginWithBadPermittedExec), manifestHash: manifestHash, - pluginInstallData: "", - dependencies: new FunctionReference[](0) + pluginInstallData: "" }); } @@ -278,8 +275,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { IPluginManager(account1).installPlugin({ plugin: address(tokenReceiverPlugin), manifestHash: bytes32(0), - pluginInstallData: abi.encode(uint48(1 days)), - dependencies: new FunctionReference[](0) + pluginInstallData: abi.encode(uint48(1 days)) }); } @@ -293,8 +289,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { IPluginManager(account1).installPlugin({ plugin: address(badPlugin), manifestHash: bytes32(0), - pluginInstallData: "", - dependencies: new FunctionReference[](0) + pluginInstallData: "" }); } @@ -305,8 +300,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { IPluginManager(account1).installPlugin({ plugin: address(tokenReceiverPlugin), manifestHash: manifestHash, - pluginInstallData: abi.encode(uint48(1 days)), - dependencies: new FunctionReference[](0) + pluginInstallData: abi.encode(uint48(1 days)) }); vm.expectRevert( @@ -317,8 +311,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { IPluginManager(account1).installPlugin({ plugin: address(tokenReceiverPlugin), manifestHash: manifestHash, - pluginInstallData: abi.encode(uint48(1 days)), - dependencies: new FunctionReference[](0) + pluginInstallData: abi.encode(uint48(1 days)) }); } @@ -330,8 +323,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { IPluginManager(account1).installPlugin({ plugin: address(plugin), manifestHash: manifestHash, - pluginInstallData: "", - dependencies: new FunctionReference[](0) + pluginInstallData: "" }); vm.expectEmit(true, true, true, true); @@ -351,8 +343,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { IPluginManager(account1).installPlugin({ plugin: address(plugin), manifestHash: manifestHash, - pluginInstallData: "", - dependencies: new FunctionReference[](0) + pluginInstallData: "" }); vm.expectEmit(true, true, true, true); @@ -376,8 +367,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { IPluginManager(account1).installPlugin({ plugin: address(plugin), manifestHash: manifestHash, - pluginInstallData: "", - dependencies: new FunctionReference[](0) + pluginInstallData: "" }); // Attempt to uninstall with a blank _manifest @@ -404,8 +394,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { IPluginManager(account1).installPlugin({ plugin: address(plugin), manifestHash: manifestHash, - pluginInstallData: "", - dependencies: new FunctionReference[](0) + pluginInstallData: "" }); vm.stopPrank(); @@ -444,7 +433,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { // singleOwnerPlugin.ownerOf(address(account1)); bytes memory signature = abi.encodePacked( - address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.SIG_VALIDATION), r, s, v + address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER), r, s, v ); bytes4 validationResult = IERC1271(address(account1)).isValidSignature(message, signature); diff --git a/test/account/ValidationIntersection.t.sol b/test/account/ValidationIntersection.t.sol index faa24074..d3fce6d8 100644 --- a/test/account/ValidationIntersection.t.sol +++ b/test/account/ValidationIntersection.t.sol @@ -49,14 +49,12 @@ contract ValidationIntersectionTest is AccountTestBase { account1.installPlugin({ plugin: address(noHookPlugin), manifestHash: keccak256(abi.encode(noHookPlugin.pluginManifest())), - pluginInstallData: "", - dependencies: new FunctionReference[](0) + pluginInstallData: "" }); account1.installPlugin({ plugin: address(oneHookPlugin), manifestHash: keccak256(abi.encode(oneHookPlugin.pluginManifest())), - pluginInstallData: "", - dependencies: new FunctionReference[](0) + pluginInstallData: "" }); // TODO: change with new install flow // temporary fix to add the pre-validation hook @@ -77,8 +75,7 @@ contract ValidationIntersectionTest is AccountTestBase { account1.installPlugin({ plugin: address(twoHookPlugin), manifestHash: keccak256(abi.encode(twoHookPlugin.pluginManifest())), - pluginInstallData: "", - dependencies: new FunctionReference[](0) + pluginInstallData: "" }); // temporary fix to add the pre-validation hook preValidationHooks = new FunctionReference[](2); diff --git a/test/mocks/plugins/ComprehensivePlugin.sol b/test/mocks/plugins/ComprehensivePlugin.sol index 4062218b..c901c81a 100644 --- a/test/mocks/plugins/ComprehensivePlugin.sol +++ b/test/mocks/plugins/ComprehensivePlugin.sol @@ -6,9 +6,7 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import { ManifestExecutionHook, ManifestExecutionFunction, - ManifestFunction, - ManifestAssociatedFunctionType, - ManifestAssociatedFunction, + ManifestValidation, PluginManifest, PluginMetadata } from "../../../src/interfaces/IPlugin.sol"; @@ -142,15 +140,15 @@ contract ComprehensivePlugin is IValidation, IValidationHook, IExecutionHook, Ba allowDefaultValidation: false }); - ManifestFunction memory fooValidationFunction = ManifestFunction({ - functionType: ManifestAssociatedFunctionType.SELF, + bytes4[] memory validationSelectors = new bytes4[](1); + validationSelectors[0] = this.foo.selector; + + manifest.validationFunctions = new ManifestValidation[](1); + manifest.validationFunctions[0] = ManifestValidation({ functionId: uint8(FunctionId.VALIDATION), - dependencyIndex: 0 // Unused. - }); - manifest.validationFunctions = new ManifestAssociatedFunction[](1); - manifest.validationFunctions[0] = ManifestAssociatedFunction({ - executionSelector: this.foo.selector, - associatedFunction: fooValidationFunction + isDefault: true, + isSignatureValidation: false, + selectors: validationSelectors }); manifest.executionHooks = new ManifestExecutionHook[](3); diff --git a/test/mocks/plugins/ReturnDataPluginMocks.sol b/test/mocks/plugins/ReturnDataPluginMocks.sol index c0fc0cfe..d7de107c 100644 --- a/test/mocks/plugins/ReturnDataPluginMocks.sol +++ b/test/mocks/plugins/ReturnDataPluginMocks.sol @@ -5,9 +5,7 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import { ManifestExecutionFunction, - ManifestAssociatedFunctionType, - ManifestAssociatedFunction, - ManifestFunction, + ManifestValidation, PluginManifest, PluginMetadata } from "../../../src/interfaces/IPlugin.sol"; @@ -117,14 +115,16 @@ contract ResultConsumerPlugin is BasePlugin, IValidation { function pluginManifest() external pure override returns (PluginManifest memory) { PluginManifest memory manifest; - manifest.validationFunctions = new ManifestAssociatedFunction[](1); - manifest.validationFunctions[0] = ManifestAssociatedFunction({ - executionSelector: IStandardExecutor.execute.selector, - associatedFunction: ManifestFunction({ - functionType: ManifestAssociatedFunctionType.SELF, - functionId: uint8(0), - dependencyIndex: 0 - }) + // todo: this is the exact workflow that would benefit from a "permiteed call" setup in the manifest. + bytes4[] memory validationSelectors = new bytes4[](1); + validationSelectors[0] = IStandardExecutor.execute.selector; + + manifest.validationFunctions = new ManifestValidation[](1); + manifest.validationFunctions[0] = ManifestValidation({ + functionId: 0, + isDefault: true, + isSignatureValidation: false, + selectors: validationSelectors }); manifest.executionFunctions = new ManifestExecutionFunction[](2); diff --git a/test/mocks/plugins/ValidationPluginMocks.sol b/test/mocks/plugins/ValidationPluginMocks.sol index f6ed4a5f..c9bf91f2 100644 --- a/test/mocks/plugins/ValidationPluginMocks.sol +++ b/test/mocks/plugins/ValidationPluginMocks.sol @@ -4,10 +4,8 @@ pragma solidity ^0.8.19; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import { - ManifestFunction, ManifestExecutionFunction, - ManifestAssociatedFunctionType, - ManifestAssociatedFunction, + ManifestValidation, PluginMetadata, PluginManifest } from "../../../src/interfaces/IPlugin.sol"; @@ -105,14 +103,15 @@ contract MockUserOpValidationPlugin is MockBaseUserOpValidationPlugin { allowDefaultValidation: false }); - manifest.validationFunctions = new ManifestAssociatedFunction[](1); - manifest.validationFunctions[0] = ManifestAssociatedFunction({ - executionSelector: this.foo.selector, - associatedFunction: ManifestFunction({ - functionType: ManifestAssociatedFunctionType.SELF, - functionId: uint8(FunctionId.USER_OP_VALIDATION), - dependencyIndex: 0 // Unused. - }) + bytes4[] memory validationSelectors = new bytes4[](1); + validationSelectors[0] = this.foo.selector; + + manifest.validationFunctions = new ManifestValidation[](1); + manifest.validationFunctions[0] = ManifestValidation({ + functionId: uint8(FunctionId.USER_OP_VALIDATION), + isDefault: false, + isSignatureValidation: false, + selectors: validationSelectors }); return manifest; @@ -147,15 +146,15 @@ contract MockUserOpValidation1HookPlugin is MockBaseUserOpValidationPlugin { allowDefaultValidation: false }); - ManifestFunction memory userOpValidationFunctionRef = ManifestFunction({ - functionType: ManifestAssociatedFunctionType.SELF, + bytes4[] memory validationSelectors = new bytes4[](1); + validationSelectors[0] = this.bar.selector; + + manifest.validationFunctions = new ManifestValidation[](2); + manifest.validationFunctions[0] = ManifestValidation({ functionId: uint8(FunctionId.USER_OP_VALIDATION), - dependencyIndex: 0 // Unused. - }); - manifest.validationFunctions = new ManifestAssociatedFunction[](1); - manifest.validationFunctions[0] = ManifestAssociatedFunction({ - executionSelector: this.bar.selector, - associatedFunction: userOpValidationFunctionRef + isDefault: false, + isSignatureValidation: false, + selectors: validationSelectors }); return manifest; @@ -193,15 +192,15 @@ contract MockUserOpValidation2HookPlugin is MockBaseUserOpValidationPlugin { allowDefaultValidation: false }); - ManifestFunction memory userOpValidationFunctionRef = ManifestFunction({ - functionType: ManifestAssociatedFunctionType.SELF, + bytes4[] memory validationSelectors = new bytes4[](1); + validationSelectors[0] = this.baz.selector; + + manifest.validationFunctions = new ManifestValidation[](1); + manifest.validationFunctions[0] = ManifestValidation({ functionId: uint8(FunctionId.USER_OP_VALIDATION), - dependencyIndex: 0 // Unused. - }); - manifest.validationFunctions = new ManifestAssociatedFunction[](1); - manifest.validationFunctions[0] = ManifestAssociatedFunction({ - executionSelector: this.baz.selector, - associatedFunction: userOpValidationFunctionRef + isDefault: false, + isSignatureValidation: false, + selectors: validationSelectors }); return manifest; diff --git a/test/plugin/SingleOwnerPlugin.t.sol b/test/plugin/SingleOwnerPlugin.t.sol index 41997591..f86a29f4 100644 --- a/test/plugin/SingleOwnerPlugin.t.sol +++ b/test/plugin/SingleOwnerPlugin.t.sol @@ -156,7 +156,7 @@ contract SingleOwnerPluginTest is OptimizedTest { // sig check should fail assertEq( plugin.validateSignature( - uint8(ISingleOwnerPlugin.FunctionId.SIG_VALIDATION), + uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER), address(this), digest, abi.encodePacked(r, s, v) @@ -171,7 +171,7 @@ contract SingleOwnerPluginTest is OptimizedTest { // sig check should pass assertEq( plugin.validateSignature( - uint8(ISingleOwnerPlugin.FunctionId.SIG_VALIDATION), + uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER), address(this), digest, abi.encodePacked(r, s, v) @@ -186,7 +186,7 @@ contract SingleOwnerPluginTest is OptimizedTest { bytes memory signature = contractOwner.sign(digest); assertEq( plugin.validateSignature( - uint8(ISingleOwnerPlugin.FunctionId.SIG_VALIDATION), address(this), digest, signature + uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER), address(this), digest, signature ), _1271_MAGIC_VALUE ); diff --git a/test/plugin/TokenReceiverPlugin.t.sol b/test/plugin/TokenReceiverPlugin.t.sol index 0e111020..2f52a988 100644 --- a/test/plugin/TokenReceiverPlugin.t.sol +++ b/test/plugin/TokenReceiverPlugin.t.sol @@ -6,7 +6,6 @@ import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Recei import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {FunctionReference} from "../../src/helpers/FunctionReferenceLib.sol"; import {TokenReceiverPlugin} from "../../src/plugins/TokenReceiverPlugin.sol"; import {MSCAFactoryFixture} from "../mocks/MSCAFactoryFixture.sol"; @@ -56,7 +55,7 @@ contract TokenReceiverPluginTest is OptimizedTest, IERC1155Receiver { bytes32 manifestHash = keccak256(abi.encode(plugin.pluginManifest())); vm.prank(address(entryPoint)); - acct.installPlugin(address(plugin), manifestHash, "", new FunctionReference[](0)); + acct.installPlugin(address(plugin), manifestHash, ""); } function test_failERC721Transfer() public { From 30596f8290041ff1e450f5050ca2afb35b9e0d69 Mon Sep 17 00:00:00 2001 From: Adam Egyed <5456061+adamegyed@users.noreply.github.com> Date: Wed, 10 Jul 2024 13:36:23 -0400 Subject: [PATCH 029/111] style: [v0.8-develop] propose renaming default to global (#83) --- src/account/AccountStorage.sol | 8 +-- src/account/PluginManager2.sol | 14 ++--- src/account/PluginManagerInternals.sol | 14 ++--- src/account/UpgradeableModularAccount.sol | 55 ++++++++++--------- src/interfaces/IPlugin.sol | 4 +- src/interfaces/IPluginManager.sol | 6 +- src/plugins/TokenReceiverPlugin.sol | 6 +- src/plugins/owner/SingleOwnerPlugin.sol | 2 +- test/account/AccountExecHooks.t.sol | 2 +- ...nTest.t.sol => GlobalValidationTest.t.sol} | 24 ++++---- test/account/PerHookData.t.sol | 39 ++++++------- test/account/SelfCallAuthorization.t.sol | 22 ++++---- ...sol => GlobalValidationFactoryFixture.sol} | 2 +- test/mocks/plugins/ComprehensivePlugin.sol | 2 +- test/mocks/plugins/ReturnDataPluginMocks.sol | 8 +-- test/mocks/plugins/ValidationPluginMocks.sol | 6 +- test/utils/AccountTestBase.sol | 24 ++++---- 17 files changed, 117 insertions(+), 121 deletions(-) rename test/account/{DefaultValidationTest.t.sol => GlobalValidationTest.t.sol} (66%) rename test/mocks/{DefaultValidationFactoryFixture.sol => GlobalValidationFactoryFixture.sol} (98%) diff --git a/src/account/AccountStorage.sol b/src/account/AccountStorage.sol index a7645a42..78c06259 100644 --- a/src/account/AccountStorage.sol +++ b/src/account/AccountStorage.sol @@ -19,15 +19,15 @@ struct SelectorData { // 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 isPublic; - // Whether or not a default validation function may be used to validate this function. - bool allowDefaultValidation; + // Whether or not a global validation function may be used to validate this function. + bool allowGlobalValidation; // The execution hooks for this function selector. EnumerableSet.Bytes32Set executionHooks; } struct ValidationData { - // Whether or not this validation can be used as a default validation function. - bool isDefault; + // Whether or not this validation can be used as a global validation function. + bool isGlobal; // Whether or not this validation is a signature validator. bool isSignatureValidation; // How many execution hooks require the UO context. diff --git a/src/account/PluginManager2.sol b/src/account/PluginManager2.sol index dbac17c1..2c9c4d6a 100644 --- a/src/account/PluginManager2.sol +++ b/src/account/PluginManager2.sol @@ -16,7 +16,7 @@ abstract contract PluginManager2 { // Index marking the start of the data for the validation function. uint8 internal constant _RESERVED_VALIDATION_DATA_INDEX = 255; - error DefaultValidationAlreadySet(FunctionReference validationFunction); + error GlobalValidationAlreadySet(FunctionReference validationFunction); error PreValidationAlreadySet(FunctionReference validationFunction, FunctionReference preValidationFunction); error ValidationAlreadySet(bytes4 selector, FunctionReference validationFunction); error ValidationNotSet(bytes4 selector, FunctionReference validationFunction); @@ -25,7 +25,7 @@ abstract contract PluginManager2 { function _installValidation( FunctionReference validationFunction, - bool isDefault, + bool isGlobal, bytes4[] memory selectors, bytes calldata installData, bytes memory preValidationHooks, @@ -80,11 +80,11 @@ abstract contract PluginManager2 { } } - if (isDefault) { - if (_storage.validationData[validationFunction].isDefault) { - revert DefaultValidationAlreadySet(validationFunction); + if (isGlobal) { + if (_storage.validationData[validationFunction].isGlobal) { + revert GlobalValidationAlreadySet(validationFunction); } - _storage.validationData[validationFunction].isDefault = true; + _storage.validationData[validationFunction].isGlobal = true; } for (uint256 i = 0; i < selectors.length; ++i) { @@ -108,7 +108,7 @@ abstract contract PluginManager2 { ) internal { AccountStorage storage _storage = getAccountStorage(); - _storage.validationData[validationFunction].isDefault = false; + _storage.validationData[validationFunction].isGlobal = false; _storage.validationData[validationFunction].isSignatureValidation = false; { diff --git a/src/account/PluginManagerInternals.sol b/src/account/PluginManagerInternals.sol index 40bb4971..4df90e1f 100644 --- a/src/account/PluginManagerInternals.sol +++ b/src/account/PluginManagerInternals.sol @@ -33,7 +33,7 @@ abstract contract PluginManagerInternals is IPluginManager { // Storage update operations - function _setExecutionFunction(bytes4 selector, bool isPublic, bool allowDefaultValidation, address plugin) + function _setExecutionFunction(bytes4 selector, bool isPublic, bool allowGlobalValidation, address plugin) internal { SelectorData storage _selectorData = getAccountStorage().selectorData[selector]; @@ -64,7 +64,7 @@ abstract contract PluginManagerInternals is IPluginManager { _selectorData.plugin = plugin; _selectorData.isPublic = isPublic; - _selectorData.allowDefaultValidation = allowDefaultValidation; + _selectorData.allowGlobalValidation = allowGlobalValidation; } function _removeExecutionFunction(bytes4 selector) internal { @@ -72,7 +72,7 @@ abstract contract PluginManagerInternals is IPluginManager { _selectorData.plugin = address(0); _selectorData.isPublic = false; - _selectorData.allowDefaultValidation = false; + _selectorData.allowGlobalValidation = false; } function _addValidationFunction(address plugin, ManifestValidation memory mv) internal { @@ -81,7 +81,7 @@ abstract contract PluginManagerInternals is IPluginManager { FunctionReference validationFunction = FunctionReferenceLib.pack(plugin, mv.functionId); if (mv.isDefault) { - _storage.validationData[validationFunction].isDefault = true; + _storage.validationData[validationFunction].isGlobal = true; } if (mv.isSignatureValidation) { @@ -101,7 +101,7 @@ abstract contract PluginManagerInternals is IPluginManager { FunctionReference validationFunction = FunctionReferenceLib.pack(plugin, mv.functionId); - _storage.validationData[validationFunction].isDefault = false; + _storage.validationData[validationFunction].isGlobal = false; _storage.validationData[validationFunction].isSignatureValidation = false; // Clear the selectors @@ -168,8 +168,8 @@ abstract contract PluginManagerInternals is IPluginManager { for (uint256 i = 0; i < length; ++i) { bytes4 selector = manifest.executionFunctions[i].executionSelector; bool isPublic = manifest.executionFunctions[i].isPublic; - bool allowDefaultValidation = manifest.executionFunctions[i].allowDefaultValidation; - _setExecutionFunction(selector, isPublic, allowDefaultValidation, plugin); + bool allowGlobalValidation = manifest.executionFunctions[i].allowGlobalValidation; + _setExecutionFunction(selector, isPublic, allowGlobalValidation, plugin); } length = manifest.validationFunctions.length; diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 7e59544d..42db6967 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -77,7 +77,7 @@ contract UpgradeableModularAccount is error UnexpectedAggregator(address plugin, uint8 functionId, address aggregator); error UnrecognizedFunction(bytes4 selector); error UserOpValidationFunctionMissing(bytes4 selector); - error ValidationDoesNotApply(bytes4 selector, address plugin, uint8 functionId, bool isDefault); + error ValidationDoesNotApply(bytes4 selector, address plugin, uint8 functionId, bool isGlobal); error ValidationSignatureSegmentMissing(); error SignatureSegmentOutOfOrder(); @@ -180,7 +180,7 @@ contract UpgradeableModularAccount is } /// @inheritdoc IStandardExecutor - /// @notice May be validated by a default validation. + /// @notice May be validated by a global validation. function execute(address target, uint256 value, bytes calldata data) external payable @@ -192,7 +192,7 @@ contract UpgradeableModularAccount is } /// @inheritdoc IStandardExecutor - /// @notice May be validated by a default validation function. + /// @notice May be validated by a global validation function. function executeBatch(Call[] calldata calls) external payable @@ -218,8 +218,8 @@ contract UpgradeableModularAccount is FunctionReference runtimeValidationFunction = FunctionReference.wrap(bytes21(authorization[:21])); // Check if the runtime validation function is allowed to be called - bool isDefaultValidation = uint8(authorization[21]) == 1; - _checkIfValidationAppliesCallData(data, runtimeValidationFunction, isDefaultValidation); + bool isGlobalValidation = uint8(authorization[21]) == 1; + _checkIfValidationAppliesCallData(data, runtimeValidationFunction, isGlobalValidation); _doRuntimeValidation(runtimeValidationFunction, data, authorization[22:]); @@ -242,7 +242,7 @@ contract UpgradeableModularAccount is } /// @inheritdoc IPluginManager - /// @notice May be validated by a default validation. + /// @notice May be validated by a global validation. function installPlugin(address plugin, bytes32 manifestHash, bytes calldata pluginInstallData) external override @@ -252,7 +252,7 @@ contract UpgradeableModularAccount is } /// @inheritdoc IPluginManager - /// @notice May be validated by a default validation. + /// @notice May be validated by a global validation. function uninstallPlugin(address plugin, bytes calldata config, bytes calldata pluginUninstallData) external override @@ -269,40 +269,41 @@ contract UpgradeableModularAccount is _uninstallPlugin(plugin, manifest, pluginUninstallData); } - /// @notice Initializes the account with a validation function added to the default pool. + /// @notice Initializes the account with a validation function added to the global pool. /// TODO: remove and merge with regular initialization, after we figure out a better install/uninstall workflow /// with user install configs. /// @dev This function is only callable once, and only by the EntryPoint. - function initializeWithValidation( FunctionReference validationFunction, - bool shared, + bool isGlobal, bytes4[] memory selectors, bytes calldata installData, bytes calldata preValidationHooks, bytes calldata permissionHooks ) external initializer { - _installValidation(validationFunction, shared, selectors, installData, preValidationHooks, permissionHooks); + _installValidation( + validationFunction, isGlobal, selectors, installData, preValidationHooks, permissionHooks + ); emit ModularAccountInitialized(_ENTRY_POINT); } /// @inheritdoc IPluginManager - /// @notice May be validated by a default validation. + /// @notice May be validated by a global validation. function installValidation( FunctionReference validationFunction, - bool isDefault, + bool isGlobal, bytes4[] memory selectors, bytes calldata installData, bytes calldata preValidationHooks, bytes calldata permissionHooks ) external wrapNativeFunction { _installValidation( - validationFunction, isDefault, selectors, installData, preValidationHooks, permissionHooks + validationFunction, isGlobal, selectors, installData, preValidationHooks, permissionHooks ); } /// @inheritdoc IPluginManager - /// @notice May be validated by a default validation. + /// @notice May be validated by a global validation. function uninstallValidation( FunctionReference validationFunction, bytes calldata uninstallData, @@ -330,7 +331,7 @@ contract UpgradeableModularAccount is } /// @inheritdoc UUPSUpgradeable - /// @notice May be validated by a default validation. + /// @notice May be validated by a global validation. function upgradeToAndCall(address newImplementation, bytes memory data) public payable @@ -381,9 +382,9 @@ contract UpgradeableModularAccount is // Revert if the provided `authorization` less than 21 bytes long, rather than right-padding. FunctionReference userOpValidationFunction = FunctionReference.wrap(bytes21(userOp.signature[:21])); - bool isDefaultValidation = uint8(userOp.signature[21]) == 1; + bool isGlobalValidation = uint8(userOp.signature[21]) == 1; - _checkIfValidationAppliesCallData(userOp.callData, userOpValidationFunction, isDefaultValidation); + _checkIfValidationAppliesCallData(userOp.callData, userOpValidationFunction, isGlobalValidation); // Check if there are permission hooks associated with the validator, and revert if the call isn't to // `executeUserOp` @@ -612,7 +613,7 @@ contract UpgradeableModularAccount is function _checkIfValidationAppliesCallData( bytes calldata callData, FunctionReference validationFunction, - bool isDefault + bool isGlobal ) internal view { bytes4 outerSelector = bytes4(callData[:4]); if (outerSelector == this.executeUserOp.selector) { @@ -622,7 +623,7 @@ contract UpgradeableModularAccount is outerSelector = bytes4(callData[:4]); } - _checkIfValidationAppliesSelector(outerSelector, validationFunction, isDefault); + _checkIfValidationAppliesSelector(outerSelector, validationFunction, isGlobal); if (outerSelector == IStandardExecutor.execute.selector) { (address target,,) = abi.decode(callData[4:], (address, uint256, bytes)); @@ -656,7 +657,7 @@ contract UpgradeableModularAccount is revert SelfCallRecursionDepthExceeded(); } - _checkIfValidationAppliesSelector(nestedSelector, validationFunction, isDefault); + _checkIfValidationAppliesSelector(nestedSelector, validationFunction, isGlobal); } } } @@ -665,24 +666,24 @@ contract UpgradeableModularAccount is function _checkIfValidationAppliesSelector( bytes4 selector, FunctionReference validationFunction, - bool isDefault + bool isGlobal ) internal view { AccountStorage storage _storage = getAccountStorage(); // Check that the provided validation function is applicable to the selector - if (isDefault) { - if (!_defaultValidationAllowed(selector) || !_storage.validationData[validationFunction].isDefault) { + if (isGlobal) { + if (!_globalValidationAllowed(selector) || !_storage.validationData[validationFunction].isGlobal) { revert UserOpValidationFunctionMissing(selector); } } else { - // Not default validation, but per-selector + // Not global validation, but per-selector if (!getAccountStorage().validationData[validationFunction].selectors.contains(toSetValue(selector))) { revert UserOpValidationFunctionMissing(selector); } } } - function _defaultValidationAllowed(bytes4 selector) internal view returns (bool) { + function _globalValidationAllowed(bytes4 selector) internal view returns (bool) { if ( selector == this.execute.selector || selector == this.executeBatch.selector || selector == this.installPlugin.selector || selector == this.uninstallPlugin.selector @@ -692,7 +693,7 @@ contract UpgradeableModularAccount is return true; } - return getAccountStorage().selectorData[selector].allowDefaultValidation; + return getAccountStorage().selectorData[selector].allowGlobalValidation; } function _checkPermittedCallerIfNotFromEP() internal view { diff --git a/src/interfaces/IPlugin.sol b/src/interfaces/IPlugin.sol index c7ffabde..eb10e96b 100644 --- a/src/interfaces/IPlugin.sol +++ b/src/interfaces/IPlugin.sol @@ -9,8 +9,8 @@ struct ManifestExecutionFunction { bytes4 executionSelector; // If true, the function won't need runtime validation, and can be called by anyone. bool isPublic; - // If true, the function can be validated by a default validation function. - bool allowDefaultValidation; + // If true, the function can be validated by a global validation function. + bool allowGlobalValidation; } // todo: do we need these at all? Or do we fully switch to `installValidation`? diff --git a/src/interfaces/IPluginManager.sol b/src/interfaces/IPluginManager.sol index dd035b81..d73da718 100644 --- a/src/interfaces/IPluginManager.sol +++ b/src/interfaces/IPluginManager.sol @@ -17,19 +17,19 @@ interface IPluginManager { /// @notice Temporary install function - pending a different user-supplied install config & manifest validation /// path. - /// Installs a validation function across a set of execution selectors, and optionally mark it as a default + /// Installs a validation function across a set of execution selectors, and optionally mark it as a global /// validation. /// TODO: remove or update. /// @dev This does not validate anything against the manifest - the caller must ensure validity. /// @param validationFunction The validation function to install. - /// @param isDefault Whether the validation function applies for all selectors in the default pool. + /// @param isGlobal Whether the validation function applies for all selectors in the global pool. /// @param selectors The selectors to install the validation function for. /// @param installData Optional data to be decoded and used by the plugin to setup initial plugin state. /// @param preValidationHooks Optional pre-validation hooks to install for the validation function. /// @param permissionHooks Optional permission hooks to install for the validation function. function installValidation( FunctionReference validationFunction, - bool isDefault, + bool isGlobal, bytes4[] memory selectors, bytes calldata installData, bytes calldata preValidationHooks, diff --git a/src/plugins/TokenReceiverPlugin.sol b/src/plugins/TokenReceiverPlugin.sol index 5bfddc0f..d326a9d4 100644 --- a/src/plugins/TokenReceiverPlugin.sol +++ b/src/plugins/TokenReceiverPlugin.sol @@ -62,17 +62,17 @@ contract TokenReceiverPlugin is BasePlugin, IERC721Receiver, IERC1155Receiver { manifest.executionFunctions[0] = ManifestExecutionFunction({ executionSelector: this.onERC721Received.selector, isPublic: true, - allowDefaultValidation: false + allowGlobalValidation: false }); manifest.executionFunctions[1] = ManifestExecutionFunction({ executionSelector: this.onERC1155Received.selector, isPublic: true, - allowDefaultValidation: false + allowGlobalValidation: false }); manifest.executionFunctions[2] = ManifestExecutionFunction({ executionSelector: this.onERC1155BatchReceived.selector, isPublic: true, - allowDefaultValidation: false + allowGlobalValidation: false }); manifest.interfaceIds = new bytes4[](2); diff --git a/src/plugins/owner/SingleOwnerPlugin.sol b/src/plugins/owner/SingleOwnerPlugin.sol index e335708b..779d3839 100644 --- a/src/plugins/owner/SingleOwnerPlugin.sol +++ b/src/plugins/owner/SingleOwnerPlugin.sol @@ -151,7 +151,7 @@ contract SingleOwnerPlugin is ISingleOwnerPlugin, BasePlugin { function pluginManifest() external pure override returns (PluginManifest memory) { PluginManifest memory manifest; - // TODO: use default validation instead + // TODO: use global validation instead bytes4[] memory accountSelectors = new bytes4[](5); accountSelectors[0] = IStandardExecutor.execute.selector; accountSelectors[1] = IStandardExecutor.executeBatch.selector; diff --git a/test/account/AccountExecHooks.t.sol b/test/account/AccountExecHooks.t.sol index 5e5049f8..14ad57fc 100644 --- a/test/account/AccountExecHooks.t.sol +++ b/test/account/AccountExecHooks.t.sol @@ -36,7 +36,7 @@ contract AccountExecHooksTest is AccountTestBase { ManifestExecutionFunction({ executionSelector: _EXEC_SELECTOR, isPublic: true, - allowDefaultValidation: false + allowGlobalValidation: false }) ); } diff --git a/test/account/DefaultValidationTest.t.sol b/test/account/GlobalValidationTest.t.sol similarity index 66% rename from test/account/DefaultValidationTest.t.sol rename to test/account/GlobalValidationTest.t.sol index 7324e176..c242657c 100644 --- a/test/account/DefaultValidationTest.t.sol +++ b/test/account/GlobalValidationTest.t.sol @@ -7,19 +7,19 @@ import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/Messa import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; -import {DefaultValidationFactoryFixture} from "../mocks/DefaultValidationFactoryFixture.sol"; +import {GlobalValidationFactoryFixture} from "../mocks/GlobalValidationFactoryFixture.sol"; -contract DefaultValidationTest is AccountTestBase { +contract GlobalValidationTest is AccountTestBase { using MessageHashUtils for bytes32; - DefaultValidationFactoryFixture public defaultValidationFactoryFixture; + GlobalValidationFactoryFixture public globalValidationFactoryFixture; address public ethRecipient; function setUp() public { - defaultValidationFactoryFixture = new DefaultValidationFactoryFixture(entryPoint, singleOwnerPlugin); + globalValidationFactoryFixture = new GlobalValidationFactoryFixture(entryPoint, singleOwnerPlugin); - account1 = UpgradeableModularAccount(payable(defaultValidationFactoryFixture.getAddress(owner1, 0))); + account1 = UpgradeableModularAccount(payable(globalValidationFactoryFixture.getAddress(owner1, 0))); vm.deal(address(account1), 100 ether); @@ -27,13 +27,13 @@ contract DefaultValidationTest is AccountTestBase { vm.deal(ethRecipient, 1 wei); } - function test_defaultValidation_userOp_simple() public { + function test_globalValidation_userOp_simple() public { PackedUserOperation memory userOp = PackedUserOperation({ sender: address(account1), nonce: 0, initCode: abi.encodePacked( - defaultValidationFactoryFixture, - abi.encodeCall(DefaultValidationFactoryFixture.createAccount, (owner1, 0)) + globalValidationFactoryFixture, + abi.encodeCall(globalValidationFactoryFixture.createAccount, (owner1, 0)) ), callData: abi.encodeCall(UpgradeableModularAccount.execute, (ethRecipient, 1 wei, "")), accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), @@ -46,7 +46,7 @@ contract DefaultValidationTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = _encodeSignature(_ownerValidation, DEFAULT_VALIDATION, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -56,14 +56,14 @@ contract DefaultValidationTest is AccountTestBase { assertEq(ethRecipient.balance, 2 wei); } - function test_defaultValidation_runtime_simple() public { + function test_globalValidation_runtime_simple() public { // Deploy the account first - defaultValidationFactoryFixture.createAccount(owner1, 0); + globalValidationFactoryFixture.createAccount(owner1, 0); vm.prank(owner1); account1.executeWithAuthorization( abi.encodeCall(UpgradeableModularAccount.execute, (ethRecipient, 1 wei, "")), - _encodeSignature(_ownerValidation, DEFAULT_VALIDATION, "") + _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, "") ); assertEq(ethRecipient.balance, 2 wei); diff --git a/test/account/PerHookData.t.sol b/test/account/PerHookData.t.sol index 73729251..44641238 100644 --- a/test/account/PerHookData.t.sol +++ b/test/account/PerHookData.t.sol @@ -37,9 +37,8 @@ contract PerHookDataTest is CustomValidationTestBase { PreValidationHookData[] memory preValidationHookData = new PreValidationHookData[](1); preValidationHookData[0] = PreValidationHookData({index: 0, validationData: abi.encodePacked(_counter)}); - userOp.signature = _encodeSignature( - _ownerValidation, DEFAULT_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v) - ); + userOp.signature = + _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -60,9 +59,8 @@ contract PerHookDataTest is CustomValidationTestBase { validationData: abi.encodePacked(address(0x1234123412341234123412341234123412341234)) }); - userOp.signature = _encodeSignature( - _ownerValidation, DEFAULT_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v) - ); + userOp.signature = + _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -82,7 +80,7 @@ contract PerHookDataTest is CustomValidationTestBase { (PackedUserOperation memory userOp, bytes32 userOpHash) = _getCounterUserOP(); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = _encodeSignature(_ownerValidation, DEFAULT_VALIDATION, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -106,9 +104,8 @@ contract PerHookDataTest is CustomValidationTestBase { preValidationHookData[0] = PreValidationHookData({index: 0, validationData: abi.encodePacked(_counter)}); preValidationHookData[1] = PreValidationHookData({index: 1, validationData: abi.encodePacked(_counter)}); - userOp.signature = _encodeSignature( - _ownerValidation, DEFAULT_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v) - ); + userOp.signature = + _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -145,9 +142,8 @@ contract PerHookDataTest is CustomValidationTestBase { PreValidationHookData[] memory preValidationHookData = new PreValidationHookData[](1); preValidationHookData[0] = PreValidationHookData({index: 0, validationData: abi.encodePacked(beneficiary)}); - userOp.signature = _encodeSignature( - _ownerValidation, DEFAULT_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v) - ); + userOp.signature = + _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -170,9 +166,8 @@ contract PerHookDataTest is CustomValidationTestBase { PreValidationHookData[] memory preValidationHookData = new PreValidationHookData[](1); preValidationHookData[0] = PreValidationHookData({index: 0, validationData: ""}); - userOp.signature = _encodeSignature( - _ownerValidation, DEFAULT_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v) - ); + userOp.signature = + _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -200,7 +195,7 @@ contract PerHookDataTest is CustomValidationTestBase { UpgradeableModularAccount.execute, (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) ), - _encodeSignature(_ownerValidation, DEFAULT_VALIDATION, preValidationHookData, "") + _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, preValidationHookData, "") ); assertEq(_counter.number(), 1); @@ -227,7 +222,7 @@ contract PerHookDataTest is CustomValidationTestBase { UpgradeableModularAccount.execute, (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) ), - _encodeSignature(_ownerValidation, DEFAULT_VALIDATION, preValidationHookData, "") + _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, preValidationHookData, "") ); } @@ -246,7 +241,7 @@ contract PerHookDataTest is CustomValidationTestBase { UpgradeableModularAccount.execute, (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) ), - _encodeSignature(_ownerValidation, DEFAULT_VALIDATION, "") + _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, "") ); } @@ -264,7 +259,7 @@ contract PerHookDataTest is CustomValidationTestBase { UpgradeableModularAccount.execute, (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) ), - _encodeSignature(_ownerValidation, DEFAULT_VALIDATION, preValidationHookData, "") + _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, preValidationHookData, "") ); } @@ -285,7 +280,7 @@ contract PerHookDataTest is CustomValidationTestBase { ); account1.executeWithAuthorization( abi.encodeCall(UpgradeableModularAccount.execute, (beneficiary, 1 wei, "")), - _encodeSignature(_ownerValidation, DEFAULT_VALIDATION, preValidationHookData, "") + _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, preValidationHookData, "") ); } @@ -300,7 +295,7 @@ contract PerHookDataTest is CustomValidationTestBase { UpgradeableModularAccount.execute, (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) ), - _encodeSignature(_ownerValidation, DEFAULT_VALIDATION, preValidationHookData, "") + _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, preValidationHookData, "") ); } diff --git a/test/account/SelfCallAuthorization.t.sol b/test/account/SelfCallAuthorization.t.sol index fda2061a..a2cc7838 100644 --- a/test/account/SelfCallAuthorization.t.sol +++ b/test/account/SelfCallAuthorization.t.sol @@ -10,20 +10,20 @@ import {IStandardExecutor, Call} from "../../src/interfaces/IStandardExecutor.so import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; -import {DefaultValidationFactoryFixture} from "../mocks/DefaultValidationFactoryFixture.sol"; +import {GlobalValidationFactoryFixture} from "../mocks/GlobalValidationFactoryFixture.sol"; import {ComprehensivePlugin} from "../mocks/plugins/ComprehensivePlugin.sol"; contract SelfCallAuthorizationTest is AccountTestBase { - DefaultValidationFactoryFixture public defaultValidationFactoryFixture; + GlobalValidationFactoryFixture public globalValidationFactoryFixture; ComprehensivePlugin public comprehensivePlugin; FunctionReference public comprehensivePluginValidation; function setUp() public { - defaultValidationFactoryFixture = new DefaultValidationFactoryFixture(entryPoint, singleOwnerPlugin); + globalValidationFactoryFixture = new GlobalValidationFactoryFixture(entryPoint, singleOwnerPlugin); - account1 = UpgradeableModularAccount(payable(defaultValidationFactoryFixture.createAccount(owner1, 0))); + account1 = UpgradeableModularAccount(payable(globalValidationFactoryFixture.createAccount(owner1, 0))); vm.deal(address(account1), 100 ether); @@ -41,7 +41,7 @@ contract SelfCallAuthorizationTest is AccountTestBase { } function test_selfCallFails_userOp() public { - // Uses default validation + // Uses global validation _runUserOp( abi.encodeCall(ComprehensivePlugin.foo, ()), abi.encodeWithSelector( @@ -57,7 +57,7 @@ contract SelfCallAuthorizationTest is AccountTestBase { } function test_selfCallFails_execUserOp() public { - // Uses default validation + // Uses global validation _runUserOp( abi.encodePacked(IAccountExecute.executeUserOp.selector, abi.encodeCall(ComprehensivePlugin.foo, ())), abi.encodeWithSelector( @@ -73,7 +73,7 @@ contract SelfCallAuthorizationTest is AccountTestBase { } function test_selfCallFails_runtime() public { - // Uses default validation + // Uses global validation _runtimeCall( abi.encodeCall(ComprehensivePlugin.foo, ()), abi.encodeWithSelector( @@ -84,7 +84,7 @@ contract SelfCallAuthorizationTest is AccountTestBase { } function test_selfCallPrivilegeEscalation_prevented_userOp() public { - // Using default validation, self-call bypasses custom validation needed for ComprehensivePlugin.foo + // Using global validation, self-call bypasses custom validation needed for ComprehensivePlugin.foo _runUserOp( abi.encodeCall( UpgradeableModularAccount.execute, @@ -116,7 +116,7 @@ contract SelfCallAuthorizationTest is AccountTestBase { } function test_selfCallPrivilegeEscalation_prevented_execUserOp() public { - // Using default validation, self-call bypasses custom validation needed for ComprehensivePlugin.foo + // Using global validation, self-call bypasses custom validation needed for ComprehensivePlugin.foo _runUserOp( abi.encodePacked( IAccountExecute.executeUserOp.selector, @@ -153,7 +153,7 @@ contract SelfCallAuthorizationTest is AccountTestBase { } function test_selfCallPrivilegeEscalation_prevented_runtime() public { - // Using default validation, self-call bypasses custom validation needed for ComprehensivePlugin.foo + // Using global validation, self-call bypasses custom validation needed for ComprehensivePlugin.foo _runtimeCall( abi.encodeCall( UpgradeableModularAccount.execute, @@ -312,7 +312,7 @@ contract SelfCallAuthorizationTest is AccountTestBase { UpgradeableModularAccount.installValidation, (comprehensivePluginValidation, false, selectors, "", "", "") ), - _encodeSignature(_ownerValidation, DEFAULT_VALIDATION, "") + _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, "") ); } diff --git a/test/mocks/DefaultValidationFactoryFixture.sol b/test/mocks/GlobalValidationFactoryFixture.sol similarity index 98% rename from test/mocks/DefaultValidationFactoryFixture.sol rename to test/mocks/GlobalValidationFactoryFixture.sol index 0145ab8a..bc61fb21 100644 --- a/test/mocks/DefaultValidationFactoryFixture.sol +++ b/test/mocks/GlobalValidationFactoryFixture.sol @@ -12,7 +12,7 @@ import {SingleOwnerPlugin} from "../../src/plugins/owner/SingleOwnerPlugin.sol"; import {OptimizedTest} from "../utils/OptimizedTest.sol"; -contract DefaultValidationFactoryFixture is OptimizedTest { +contract GlobalValidationFactoryFixture is OptimizedTest { UpgradeableModularAccount public accountImplementation; SingleOwnerPlugin public singleOwnerPlugin; bytes32 private immutable _PROXY_BYTECODE_HASH; diff --git a/test/mocks/plugins/ComprehensivePlugin.sol b/test/mocks/plugins/ComprehensivePlugin.sol index c901c81a..cd455c88 100644 --- a/test/mocks/plugins/ComprehensivePlugin.sol +++ b/test/mocks/plugins/ComprehensivePlugin.sol @@ -137,7 +137,7 @@ contract ComprehensivePlugin is IValidation, IValidationHook, IExecutionHook, Ba manifest.executionFunctions[0] = ManifestExecutionFunction({ executionSelector: this.foo.selector, isPublic: false, - allowDefaultValidation: false + allowGlobalValidation: false }); bytes4[] memory validationSelectors = new bytes4[](1); diff --git a/test/mocks/plugins/ReturnDataPluginMocks.sol b/test/mocks/plugins/ReturnDataPluginMocks.sol index d7de107c..211f79af 100644 --- a/test/mocks/plugins/ReturnDataPluginMocks.sol +++ b/test/mocks/plugins/ReturnDataPluginMocks.sol @@ -44,12 +44,12 @@ contract ResultCreatorPlugin is BasePlugin { manifest.executionFunctions[0] = ManifestExecutionFunction({ executionSelector: this.foo.selector, isPublic: true, - allowDefaultValidation: false + allowGlobalValidation: false }); manifest.executionFunctions[1] = ManifestExecutionFunction({ executionSelector: this.bar.selector, isPublic: false, - allowDefaultValidation: false + allowGlobalValidation: false }); return manifest; @@ -131,12 +131,12 @@ contract ResultConsumerPlugin is BasePlugin, IValidation { manifest.executionFunctions[0] = ManifestExecutionFunction({ executionSelector: this.checkResultFallback.selector, isPublic: true, - allowDefaultValidation: false + allowGlobalValidation: false }); manifest.executionFunctions[1] = ManifestExecutionFunction({ executionSelector: this.checkResultExecuteWithAuthorization.selector, isPublic: true, - allowDefaultValidation: false + allowGlobalValidation: false }); return manifest; diff --git a/test/mocks/plugins/ValidationPluginMocks.sol b/test/mocks/plugins/ValidationPluginMocks.sol index c9bf91f2..a59d5ee3 100644 --- a/test/mocks/plugins/ValidationPluginMocks.sol +++ b/test/mocks/plugins/ValidationPluginMocks.sol @@ -100,7 +100,7 @@ contract MockUserOpValidationPlugin is MockBaseUserOpValidationPlugin { manifest.executionFunctions[0] = ManifestExecutionFunction({ executionSelector: this.foo.selector, isPublic: false, - allowDefaultValidation: false + allowGlobalValidation: false }); bytes4[] memory validationSelectors = new bytes4[](1); @@ -143,7 +143,7 @@ contract MockUserOpValidation1HookPlugin is MockBaseUserOpValidationPlugin { manifest.executionFunctions[0] = ManifestExecutionFunction({ executionSelector: this.bar.selector, isPublic: false, - allowDefaultValidation: false + allowGlobalValidation: false }); bytes4[] memory validationSelectors = new bytes4[](1); @@ -189,7 +189,7 @@ contract MockUserOpValidation2HookPlugin is MockBaseUserOpValidationPlugin { manifest.executionFunctions[0] = ManifestExecutionFunction({ executionSelector: this.baz.selector, isPublic: false, - allowDefaultValidation: false + allowGlobalValidation: false }); bytes4[] memory validationSelectors = new bytes4[](1); diff --git a/test/utils/AccountTestBase.sol b/test/utils/AccountTestBase.sol index cc11334d..4383f393 100644 --- a/test/utils/AccountTestBase.sol +++ b/test/utils/AccountTestBase.sol @@ -33,7 +33,7 @@ abstract contract AccountTestBase is OptimizedTest { FunctionReference internal _ownerValidation; uint8 public constant SELECTOR_ASSOCIATED_VALIDATION = 0; - uint8 public constant DEFAULT_VALIDATION = 1; + uint8 public constant GLOBAL_VALIDATION = 1; uint256 public constant CALL_GAS_LIMIT = 100000; uint256 public constant VERIFICATION_GAS_LIMIT = 1200000; @@ -102,7 +102,7 @@ abstract contract AccountTestBase is OptimizedTest { FunctionReferenceLib.pack( address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) ), - DEFAULT_VALIDATION, + GLOBAL_VALIDATION, abi.encodePacked(r, s, v) ); @@ -157,7 +157,7 @@ abstract contract AccountTestBase is OptimizedTest { FunctionReferenceLib.pack( address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) ), - DEFAULT_VALIDATION, + GLOBAL_VALIDATION, "" ) ); @@ -174,7 +174,7 @@ abstract contract AccountTestBase is OptimizedTest { FunctionReferenceLib.pack( address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) ), - DEFAULT_VALIDATION, + GLOBAL_VALIDATION, "" ) ); @@ -210,11 +210,11 @@ abstract contract AccountTestBase is OptimizedTest { // helper function to encode a signature, according to the per-hook and per-validation data format. function _encodeSignature( FunctionReference validationFunction, - uint8 defaultOrNot, + uint8 globalOrNot, PreValidationHookData[] memory preValidationHookData, bytes memory validationData ) internal pure returns (bytes memory) { - bytes memory sig = abi.encodePacked(validationFunction, defaultOrNot); + bytes memory sig = abi.encodePacked(validationFunction, globalOrNot); for (uint256 i = 0; i < preValidationHookData.length; ++i) { sig = abi.encodePacked( @@ -233,13 +233,13 @@ abstract contract AccountTestBase is OptimizedTest { } // overload for the case where there are no pre-validation hooks - function _encodeSignature( - FunctionReference validationFunction, - uint8 defaultOrNot, - bytes memory validationData - ) internal pure returns (bytes memory) { + function _encodeSignature(FunctionReference validationFunction, uint8 globalOrNot, bytes memory validationData) + internal + pure + returns (bytes memory) + { PreValidationHookData[] memory emptyPreValidationHookData = new PreValidationHookData[](0); - return _encodeSignature(validationFunction, defaultOrNot, emptyPreValidationHookData, validationData); + return _encodeSignature(validationFunction, globalOrNot, emptyPreValidationHookData, validationData); } // helper function to pack validation data with an index, according to the sparse calldata segment spec. From d50ae10a979e4eb49e2b80d2260a2a7f06d8722f Mon Sep 17 00:00:00 2001 From: Adam Egyed <5456061+adamegyed@users.noreply.github.com> Date: Thu, 11 Jul 2024 15:53:46 -0400 Subject: [PATCH 030/111] feat: [v0.8-develop] update SingleOwnerPlugin to use installValidation [1/2] (#91) --- src/account/PluginManager2.sol | 67 ++++------ src/account/UpgradeableModularAccount.sol | 18 +-- src/helpers/ValidationConfigLib.sol | 92 +++++++++++++ src/interfaces/IPluginManager.sol | 8 +- src/plugins/owner/ISingleOwnerPlugin.sol | 36 ----- src/plugins/owner/SingleOwnerPlugin.sol | 123 +++++++----------- test/account/AccountLoupe.t.sol | 60 +++++---- test/account/AccountReturnData.t.sol | 14 +- test/account/MultiValidation.t.sol | 39 +++--- test/account/PerHookData.t.sol | 12 +- test/account/SelfCallAuthorization.t.sol | 3 +- test/account/UpgradeableModularAccount.t.sol | 55 ++++---- test/account/ValidationIntersection.t.sol | 7 +- test/mocks/GlobalValidationFactoryFixture.sol | 11 +- test/mocks/MSCAFactoryFixture.sol | 25 ++-- test/plugin/SingleOwnerPlugin.t.sol | 90 ++++++------- test/samples/AllowlistPlugin.t.sol | 12 +- test/utils/AccountTestBase.sol | 31 ++--- test/utils/CustomValidationTestBase.sol | 11 +- test/utils/TestConstants.sol | 4 + 20 files changed, 368 insertions(+), 350 deletions(-) create mode 100644 src/helpers/ValidationConfigLib.sol delete mode 100644 src/plugins/owner/ISingleOwnerPlugin.sol create mode 100644 test/utils/TestConstants.sol diff --git a/src/account/PluginManager2.sol b/src/account/PluginManager2.sol index 2c9c4d6a..28eb0ecf 100644 --- a/src/account/PluginManager2.sol +++ b/src/account/PluginManager2.sol @@ -4,19 +4,20 @@ pragma solidity ^0.8.25; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {IPlugin} from "../interfaces/IPlugin.sol"; -import {FunctionReference} from "../interfaces/IPluginManager.sol"; +import {FunctionReference, ValidationConfig} from "../interfaces/IPluginManager.sol"; import {FunctionReferenceLib} from "../helpers/FunctionReferenceLib.sol"; -import {AccountStorage, getAccountStorage, toSetValue, toFunctionReference} from "./AccountStorage.sol"; +import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; +import {ValidationData, getAccountStorage, toSetValue, toFunctionReference} from "./AccountStorage.sol"; import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; // Temporary additional functions for a user-controlled install flow for validation functions. abstract contract PluginManager2 { using EnumerableSet for EnumerableSet.Bytes32Set; + using ValidationConfigLib for ValidationConfig; // Index marking the start of the data for the validation function. uint8 internal constant _RESERVED_VALIDATION_DATA_INDEX = 255; - error GlobalValidationAlreadySet(FunctionReference validationFunction); error PreValidationAlreadySet(FunctionReference validationFunction, FunctionReference preValidationFunction); error ValidationAlreadySet(bytes4 selector, FunctionReference validationFunction); error ValidationNotSet(bytes4 selector, FunctionReference validationFunction); @@ -24,17 +25,14 @@ abstract contract PluginManager2 { error PreValidationHookLimitExceeded(); function _installValidation( - FunctionReference validationFunction, - bool isGlobal, + ValidationConfig validationConfig, bytes4[] memory selectors, bytes calldata installData, bytes memory preValidationHooks, bytes memory permissionHooks - ) - // TODO: flag for signature validation - internal - { - AccountStorage storage _storage = getAccountStorage(); + ) internal { + ValidationData storage _validationData = + getAccountStorage().validationData[validationConfig.functionReference()]; if (preValidationHooks.length > 0) { (FunctionReference[] memory preValidationFunctions, bytes[] memory initDatas) = @@ -43,7 +41,7 @@ abstract contract PluginManager2 { for (uint256 i = 0; i < preValidationFunctions.length; ++i) { FunctionReference preValidationFunction = preValidationFunctions[i]; - _storage.validationData[validationFunction].preValidationHooks.push(preValidationFunction); + _validationData.preValidationHooks.push(preValidationFunction); if (initDatas[i].length > 0) { (address preValidationPlugin,) = FunctionReferenceLib.unpack(preValidationFunction); @@ -52,10 +50,7 @@ abstract contract PluginManager2 { } // Avoid collision between reserved index and actual indices - if ( - _storage.validationData[validationFunction].preValidationHooks.length - > _RESERVED_VALIDATION_DATA_INDEX - ) { + if (_validationData.preValidationHooks.length > _RESERVED_VALIDATION_DATA_INDEX) { revert PreValidationHookLimitExceeded(); } } @@ -67,10 +62,8 @@ abstract contract PluginManager2 { for (uint256 i = 0; i < permissionFunctions.length; ++i) { ExecutionHook memory permissionFunction = permissionFunctions[i]; - if ( - !_storage.validationData[validationFunction].permissionHooks.add(toSetValue(permissionFunction)) - ) { - revert PermissionAlreadySet(validationFunction, permissionFunction); + if (!_validationData.permissionHooks.add(toSetValue(permissionFunction))) { + revert PermissionAlreadySet(validationConfig.functionReference(), permissionFunction); } if (initDatas[i].length > 0) { @@ -80,22 +73,18 @@ abstract contract PluginManager2 { } } - if (isGlobal) { - if (_storage.validationData[validationFunction].isGlobal) { - revert GlobalValidationAlreadySet(validationFunction); - } - _storage.validationData[validationFunction].isGlobal = true; - } + _validationData.isGlobal = validationConfig.isGlobal(); + _validationData.isSignatureValidation = validationConfig.isSignatureValidation(); for (uint256 i = 0; i < selectors.length; ++i) { bytes4 selector = selectors[i]; - if (!_storage.validationData[validationFunction].selectors.add(toSetValue(selector))) { - revert ValidationAlreadySet(selector, validationFunction); + if (!_validationData.selectors.add(toSetValue(selector))) { + revert ValidationAlreadySet(selector, validationConfig.functionReference()); } } if (installData.length > 0) { - (address plugin,) = FunctionReferenceLib.unpack(validationFunction); + address plugin = validationConfig.plugin(); IPlugin(plugin).onInstall(installData); } } @@ -106,17 +95,16 @@ abstract contract PluginManager2 { bytes calldata preValidationHookUninstallData, bytes calldata permissionHookUninstallData ) internal { - AccountStorage storage _storage = getAccountStorage(); + ValidationData storage _validationData = getAccountStorage().validationData[validationFunction]; - _storage.validationData[validationFunction].isGlobal = false; - _storage.validationData[validationFunction].isSignatureValidation = false; + _validationData.isGlobal = false; + _validationData.isSignatureValidation = false; { bytes[] memory preValidationHookUninstallDatas = abi.decode(preValidationHookUninstallData, (bytes[])); // Clear pre validation hooks - FunctionReference[] storage preValidationHooks = - _storage.validationData[validationFunction].preValidationHooks; + FunctionReference[] storage preValidationHooks = _validationData.preValidationHooks; for (uint256 i = 0; i < preValidationHooks.length; ++i) { FunctionReference preValidationFunction = preValidationHooks[i]; if (preValidationHookUninstallDatas[0].length > 0) { @@ -124,15 +112,14 @@ abstract contract PluginManager2 { IPlugin(preValidationPlugin).onUninstall(preValidationHookUninstallDatas[0]); } } - delete _storage.validationData[validationFunction].preValidationHooks; + delete _validationData.preValidationHooks; } { bytes[] memory permissionHookUninstallDatas = abi.decode(permissionHookUninstallData, (bytes[])); // Clear permission hooks - EnumerableSet.Bytes32Set storage permissionHooks = - _storage.validationData[validationFunction].permissionHooks; + EnumerableSet.Bytes32Set storage permissionHooks = _validationData.permissionHooks; uint256 i = 0; while (permissionHooks.length() > 0) { FunctionReference permissionHook = toFunctionReference(permissionHooks.at(0)); @@ -141,12 +128,12 @@ abstract contract PluginManager2 { IPlugin(permissionHookPlugin).onUninstall(permissionHookUninstallDatas[i++]); } } - delete _storage.validationData[validationFunction].preValidationHooks; + delete _validationData.preValidationHooks; // Clear selectors - while (_storage.validationData[validationFunction].selectors.length() > 0) { - bytes32 selector = _storage.validationData[validationFunction].selectors.at(0); - _storage.validationData[validationFunction].selectors.remove(selector); + while (_validationData.selectors.length() > 0) { + bytes32 selector = _validationData.selectors.at(0); + _validationData.selectors.remove(selector); } if (uninstallData.length > 0) { diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 42db6967..d2d1b2cc 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -11,13 +11,14 @@ import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {FunctionReferenceLib} from "../helpers/FunctionReferenceLib.sol"; +import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; import {SparseCalldataSegmentLib} from "../helpers/SparseCalldataSegmentLib.sol"; import {_coalescePreValidation, _coalesceValidation} from "../helpers/ValidationDataHelpers.sol"; import {IPlugin, PluginManifest} from "../interfaces/IPlugin.sol"; import {IValidation} from "../interfaces/IValidation.sol"; import {IValidationHook} from "../interfaces/IValidationHook.sol"; import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; -import {FunctionReference, IPluginManager} from "../interfaces/IPluginManager.sol"; +import {FunctionReference, IPluginManager, ValidationConfig} from "../interfaces/IPluginManager.sol"; import {IStandardExecutor, Call} from "../interfaces/IStandardExecutor.sol"; import {AccountExecutor} from "./AccountExecutor.sol"; import {AccountLoupe} from "./AccountLoupe.sol"; @@ -41,6 +42,7 @@ contract UpgradeableModularAccount is { using EnumerableSet for EnumerableSet.Bytes32Set; using FunctionReferenceLib for FunctionReference; + using ValidationConfigLib for ValidationConfig; using SparseCalldataSegmentLib for bytes; struct PostExecToRun { @@ -274,32 +276,26 @@ contract UpgradeableModularAccount is /// with user install configs. /// @dev This function is only callable once, and only by the EntryPoint. function initializeWithValidation( - FunctionReference validationFunction, - bool isGlobal, + ValidationConfig validationConfig, bytes4[] memory selectors, bytes calldata installData, bytes calldata preValidationHooks, bytes calldata permissionHooks ) external initializer { - _installValidation( - validationFunction, isGlobal, selectors, installData, preValidationHooks, permissionHooks - ); + _installValidation(validationConfig, selectors, installData, preValidationHooks, permissionHooks); emit ModularAccountInitialized(_ENTRY_POINT); } /// @inheritdoc IPluginManager /// @notice May be validated by a global validation. function installValidation( - FunctionReference validationFunction, - bool isGlobal, + ValidationConfig validationConfig, bytes4[] memory selectors, bytes calldata installData, bytes calldata preValidationHooks, bytes calldata permissionHooks ) external wrapNativeFunction { - _installValidation( - validationFunction, isGlobal, selectors, installData, preValidationHooks, permissionHooks - ); + _installValidation(validationConfig, selectors, installData, preValidationHooks, permissionHooks); } /// @inheritdoc IPluginManager diff --git a/src/helpers/ValidationConfigLib.sol b/src/helpers/ValidationConfigLib.sol new file mode 100644 index 00000000..71639f80 --- /dev/null +++ b/src/helpers/ValidationConfigLib.sol @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.25; + +import {FunctionReference, ValidationConfig} from "../interfaces/IPluginManager.sol"; + +// Validation config is a packed representation of a validation function and flags for its configuration. +// Layout: +// 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA________________________ // Address +// 0x________________________________________BB______________________ // Function ID +// 0x__________________________________________CC____________________ // isGlobal +// 0x____________________________________________DD__________________ // isSignatureValidation +// 0x______________________________________________000000000000000000 // unused + +library ValidationConfigLib { + function pack(FunctionReference _validationFunction, bool _isGlobal, bool _isSignatureValidation) + internal + pure + returns (ValidationConfig) + { + return ValidationConfig.wrap( + bytes23( + bytes23(FunctionReference.unwrap(_validationFunction)) + // isGlobal flag stored in the 22nd byte + | bytes23(bytes32(_isGlobal ? uint256(1) << 80 : 0)) + // isSignatureValidation flag stored in the 23rd byte + | bytes23(bytes32(_isSignatureValidation ? uint256(1) << 72 : 0)) + ) + ); + } + + function pack(address _plugin, uint8 _functionId, bool _isGlobal, bool _isSignatureValidation) + internal + pure + returns (ValidationConfig) + { + return ValidationConfig.wrap( + bytes23( + // plugin address stored in the first 20 bytes + bytes23(bytes20(_plugin)) + // functionId stored in the 21st byte + | bytes23(bytes32(uint256(_functionId) << 168)) + // isGlobal flag stored in the 22nd byte + | bytes23(bytes32(_isGlobal ? uint256(1) << 80 : 0)) + // isSignatureValidation flag stored in the 23rd byte + | bytes23(bytes32(_isSignatureValidation ? uint256(1) << 72 : 0)) + ) + ); + } + + function unpackUnderlying(ValidationConfig config) + internal + pure + returns (address _plugin, uint8 _functionId, bool _isGlobal, bool _isSignatureValidation) + { + bytes23 configBytes = ValidationConfig.unwrap(config); + _plugin = address(bytes20(configBytes)); + _functionId = uint8(configBytes[20]); + _isGlobal = uint8(configBytes[21]) == 1; + _isSignatureValidation = uint8(configBytes[22]) == 1; + } + + function unpack(ValidationConfig config) + internal + pure + returns (FunctionReference _validationFunction, bool _isGlobal, bool _isSignatureValidation) + { + bytes23 configBytes = ValidationConfig.unwrap(config); + _validationFunction = FunctionReference.wrap(bytes21(configBytes)); + _isGlobal = uint8(configBytes[21]) == 1; + _isSignatureValidation = uint8(configBytes[22]) == 1; + } + + function plugin(ValidationConfig config) internal pure returns (address) { + return address(bytes20(ValidationConfig.unwrap(config))); + } + + function functionId(ValidationConfig config) internal pure returns (uint8) { + return uint8(ValidationConfig.unwrap(config)[20]); + } + + function functionReference(ValidationConfig config) internal pure returns (FunctionReference) { + return FunctionReference.wrap(bytes21(ValidationConfig.unwrap(config))); + } + + function isGlobal(ValidationConfig config) internal pure returns (bool) { + return uint8(ValidationConfig.unwrap(config)[21]) == 1; + } + + function isSignatureValidation(ValidationConfig config) internal pure returns (bool) { + return uint8(ValidationConfig.unwrap(config)[22]) == 1; + } +} diff --git a/src/interfaces/IPluginManager.sol b/src/interfaces/IPluginManager.sol index d73da718..bf1296e1 100644 --- a/src/interfaces/IPluginManager.sol +++ b/src/interfaces/IPluginManager.sol @@ -3,6 +3,8 @@ pragma solidity ^0.8.25; type FunctionReference is bytes21; +type ValidationConfig is bytes23; + interface IPluginManager { event PluginInstalled(address indexed plugin, bytes32 manifestHash); @@ -21,15 +23,13 @@ interface IPluginManager { /// validation. /// TODO: remove or update. /// @dev This does not validate anything against the manifest - the caller must ensure validity. - /// @param validationFunction The validation function to install. - /// @param isGlobal Whether the validation function applies for all selectors in the global pool. + /// @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 decoded and used by the plugin to setup initial plugin state. /// @param preValidationHooks Optional pre-validation hooks to install for the validation function. /// @param permissionHooks Optional permission hooks to install for the validation function. function installValidation( - FunctionReference validationFunction, - bool isGlobal, + ValidationConfig validationConfig, bytes4[] memory selectors, bytes calldata installData, bytes calldata preValidationHooks, diff --git a/src/plugins/owner/ISingleOwnerPlugin.sol b/src/plugins/owner/ISingleOwnerPlugin.sol deleted file mode 100644 index c8f0df43..00000000 --- a/src/plugins/owner/ISingleOwnerPlugin.sol +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.25; - -import {IValidation} from "../../interfaces/IValidation.sol"; - -interface ISingleOwnerPlugin is IValidation { - enum FunctionId { - VALIDATION_OWNER - } - - /// @notice This event is emitted when ownership of the account changes. - /// @param account The account whose ownership changed. - /// @param previousOwner The address of the previous owner. - /// @param newOwner The address of the new owner. - event OwnershipTransferred(address indexed account, address indexed previousOwner, address indexed newOwner); - - error NotAuthorized(); - - /// @notice Transfer ownership of the account to `newOwner`. - /// @dev This function is installed on the account as part of plugin installation, and should - /// only be called from an account. - /// @param newOwner The address of the new owner. - function transferOwnership(address newOwner) external; - - /// @notice Get the owner of the account. - /// @dev This function is installed on the account as part of plugin installation, and should - /// only be called from an account. - /// @return The address of the owner. - function owner() external view returns (address); - - /// @notice Get the owner of `account`. - /// @dev This function is not installed on the account, and can be called by anyone. - /// @param account The account to get the owner of. - /// @return The address of the owner. - function ownerOf(address account) external view returns (address); -} diff --git a/src/plugins/owner/SingleOwnerPlugin.sol b/src/plugins/owner/SingleOwnerPlugin.sol index 779d3839..dbb5d56a 100644 --- a/src/plugins/owner/SingleOwnerPlugin.sol +++ b/src/plugins/owner/SingleOwnerPlugin.sol @@ -6,16 +6,10 @@ import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/Signa import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; -import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; -import {IPluginManager} from "../../interfaces/IPluginManager.sol"; -import { - PluginManifest, ManifestValidation, PluginMetadata, SelectorPermission -} from "../../interfaces/IPlugin.sol"; -import {IStandardExecutor} from "../../interfaces/IStandardExecutor.sol"; +import {PluginManifest, PluginMetadata, SelectorPermission} from "../../interfaces/IPlugin.sol"; import {IPlugin} from "../../interfaces/IPlugin.sol"; import {IValidation} from "../../interfaces/IValidation.sol"; import {BasePlugin, IERC165} from "../BasePlugin.sol"; -import {ISingleOwnerPlugin} from "./ISingleOwnerPlugin.sol"; /// @title Single Owner Plugin /// @author ERC-6900 Authors @@ -33,13 +27,13 @@ import {ISingleOwnerPlugin} from "./ISingleOwnerPlugin.sol"; /// the account, violating storage access rules. This also means that the /// owner of a modular account may not be another modular account if you want to /// send user operations through a bundler. -contract SingleOwnerPlugin is ISingleOwnerPlugin, BasePlugin { +contract SingleOwnerPlugin is IValidation, BasePlugin { using ECDSA for bytes32; using MessageHashUtils for bytes32; - string public constant NAME = "Single Owner Plugin"; - string public constant VERSION = "1.0.0"; - string public constant AUTHOR = "ERC-6900 Authors"; + string internal constant _NAME = "Single Owner Plugin"; + string internal constant _VERSION = "1.0.0"; + string internal constant _AUTHOR = "ERC-6900 Authors"; uint256 internal constant _SIG_VALIDATION_PASSED = 0; uint256 internal constant _SIG_VALIDATION_FAILED = 1; @@ -48,15 +42,27 @@ contract SingleOwnerPlugin is ISingleOwnerPlugin, BasePlugin { bytes4 internal constant _1271_MAGIC_VALUE = 0x1626ba7e; bytes4 internal constant _1271_INVALID = 0xffffffff; - mapping(address => address) internal _owners; + mapping(uint8 id => mapping(address account => address)) public owners; + + /// @notice This event is emitted when ownership of the account changes. + /// @param account The account whose ownership changed. + /// @param previousOwner The address of the previous owner. + /// @param newOwner The address of the new owner. + event OwnershipTransferred(address indexed account, address indexed previousOwner, address indexed newOwner); + + error AlreadyInitialized(); + error NotAuthorized(); + error NotInitialized(); // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ // ┃ Execution functions ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - /// @inheritdoc ISingleOwnerPlugin - function transferOwnership(address newOwner) external { - _transferOwnership(newOwner); + /// @notice Transfer ownership of an account and ID to `newOwner`. The caller address (`msg.sender`) is user to + /// identify the account. + /// @param newOwner The address of the new owner. + function transferOwnership(uint8 id, address newOwner) external { + _transferOwnership(id, newOwner); } // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ @@ -65,12 +71,14 @@ contract SingleOwnerPlugin is ISingleOwnerPlugin, BasePlugin { /// @inheritdoc IPlugin function onInstall(bytes calldata data) external override { - _transferOwnership(abi.decode(data, (address))); + (uint8 id, address owner) = abi.decode(data, (uint8, address)); + _transferOwnership(id, owner); } /// @inheritdoc IPlugin - function onUninstall(bytes calldata) external override { - _transferOwnership(address(0)); + function onUninstall(bytes calldata data) external override { + uint8 id = abi.decode(data, (uint8)); + _transferOwnership(id, address(0)); } /// @inheritdoc IValidation @@ -79,14 +87,11 @@ contract SingleOwnerPlugin is ISingleOwnerPlugin, BasePlugin { view override { - if (functionId == uint8(FunctionId.VALIDATION_OWNER)) { - // Validate that the sender is the owner of the account or self. - if (sender != _owners[msg.sender] && sender != msg.sender) { - revert NotAuthorized(); - } - return; + // Validate that the sender is the owner of the account or self. + if (sender != owners[functionId][msg.sender]) { + revert NotAuthorized(); } - revert NotImplemented(); + return; } /// @inheritdoc IValidation @@ -96,15 +101,15 @@ contract SingleOwnerPlugin is ISingleOwnerPlugin, BasePlugin { override returns (uint256) { - if (functionId == uint8(FunctionId.VALIDATION_OWNER)) { - // Validate the user op signature against the owner. - (address signer,,) = (userOpHash.toEthSignedMessageHash()).tryRecover(userOp.signature); - if (signer == address(0) || signer != _owners[msg.sender]) { - return _SIG_VALIDATION_FAILED; - } + // Validate the user op signature against the owner. + if ( + SignatureChecker.isValidSignatureNow( + owners[functionId][msg.sender], userOpHash.toEthSignedMessageHash(), userOp.signature + ) + ) { return _SIG_VALIDATION_PASSED; } - revert NotImplemented(); + return _SIG_VALIDATION_FAILED; } // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ @@ -124,60 +129,28 @@ contract SingleOwnerPlugin is ISingleOwnerPlugin, BasePlugin { override returns (bytes4) { - if (functionId == uint8(FunctionId.VALIDATION_OWNER)) { - if (SignatureChecker.isValidSignatureNow(_owners[msg.sender], digest, signature)) { - return _1271_MAGIC_VALUE; - } - return _1271_INVALID; + if (SignatureChecker.isValidSignatureNow(owners[functionId][msg.sender], digest, signature)) { + return _1271_MAGIC_VALUE; } - revert NotImplemented(); - } - - /// @inheritdoc ISingleOwnerPlugin - function owner() external view returns (address) { - return _owners[msg.sender]; + return _1271_INVALID; } // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ // ┃ Plugin view functions ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - /// @inheritdoc ISingleOwnerPlugin - function ownerOf(address account) external view returns (address) { - return _owners[account]; - } - /// @inheritdoc IPlugin function pluginManifest() external pure override returns (PluginManifest memory) { PluginManifest memory manifest; - - // TODO: use global validation instead - bytes4[] memory accountSelectors = new bytes4[](5); - accountSelectors[0] = IStandardExecutor.execute.selector; - accountSelectors[1] = IStandardExecutor.executeBatch.selector; - accountSelectors[2] = IPluginManager.installPlugin.selector; - accountSelectors[3] = IPluginManager.uninstallPlugin.selector; - accountSelectors[4] = UUPSUpgradeable.upgradeToAndCall.selector; - - ManifestValidation memory ownerValidationFunction = ManifestValidation({ - functionId: uint8(FunctionId.VALIDATION_OWNER), - isDefault: false, - isSignatureValidation: true, - selectors: accountSelectors - }); - - manifest.validationFunctions = new ManifestValidation[](1); - manifest.validationFunctions[0] = ownerValidationFunction; - return manifest; } /// @inheritdoc IPlugin function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { PluginMetadata memory metadata; - metadata.name = NAME; - metadata.version = VERSION; - metadata.author = AUTHOR; + metadata.name = _NAME; + metadata.version = _VERSION; + metadata.author = _AUTHOR; // Permission strings string memory modifyOwnershipPermission = "Modify Ownership"; @@ -198,16 +171,18 @@ contract SingleOwnerPlugin is ISingleOwnerPlugin, BasePlugin { /// @inheritdoc BasePlugin function supportsInterface(bytes4 interfaceId) public view override(BasePlugin, IERC165) returns (bool) { - return interfaceId == type(ISingleOwnerPlugin).interfaceId || super.supportsInterface(interfaceId); + return interfaceId == type(IValidation).interfaceId || super.supportsInterface(interfaceId); } // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ // ┃ Internal / Private functions ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - function _transferOwnership(address newOwner) internal { - address previousOwner = _owners[msg.sender]; - _owners[msg.sender] = newOwner; + // Transfers ownership and emits and event. + function _transferOwnership(uint8 id, address newOwner) internal { + address previousOwner = owners[id][msg.sender]; + owners[id][msg.sender] = newOwner; + // Todo: include id in event emit OwnershipTransferred(msg.sender, previousOwner, newOwner); } } diff --git a/test/account/AccountLoupe.t.sol b/test/account/AccountLoupe.t.sol index 15803fa8..69563b51 100644 --- a/test/account/AccountLoupe.t.sol +++ b/test/account/AccountLoupe.t.sol @@ -9,49 +9,29 @@ import {IPluginManager} from "../../src/interfaces/IPluginManager.sol"; import {IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; import {ComprehensivePlugin} from "../mocks/plugins/ComprehensivePlugin.sol"; -import {AccountTestBase} from "../utils/AccountTestBase.sol"; +import {CustomValidationTestBase} from "../utils/CustomValidationTestBase.sol"; -contract AccountLoupeTest is AccountTestBase { +contract AccountLoupeTest is CustomValidationTestBase { ComprehensivePlugin public comprehensivePlugin; event ReceivedCall(bytes msgData, uint256 msgValue); function setUp() public { - _transferOwnershipToTest(); - comprehensivePlugin = new ComprehensivePlugin(); + _customValidationSetup(); + bytes32 manifestHash = keccak256(abi.encode(comprehensivePlugin.pluginManifest())); vm.prank(address(entryPoint)); account1.installPlugin(address(comprehensivePlugin), manifestHash, ""); - - FunctionReference[] memory preValidationHooks = new FunctionReference[](2); - preValidationHooks[0] = FunctionReferenceLib.pack( - address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.PRE_VALIDATION_HOOK_1) - ); - preValidationHooks[1] = FunctionReferenceLib.pack( - address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.PRE_VALIDATION_HOOK_2) - ); - - bytes[] memory installDatas = new bytes[](2); - vm.prank(address(entryPoint)); - account1.installValidation( - _ownerValidation, - true, - new bytes4[](0), - bytes(""), - abi.encode(preValidationHooks, installDatas), - bytes("") - ); } function test_pluginLoupe_getInstalledPlugins_initial() public { address[] memory plugins = account1.getInstalledPlugins(); - assertEq(plugins.length, 2); + assertEq(plugins.length, 1); - assertEq(plugins[0], address(singleOwnerPlugin)); - assertEq(plugins[1], address(comprehensivePlugin)); + assertEq(plugins[0], address(comprehensivePlugin)); } function test_pluginLoupe_getExecutionFunctionHandler_native() public { @@ -157,4 +137,32 @@ contract AccountLoupeTest is AccountTestBase { ) ); } + + // Test config + + function _initialValidationConfig() + internal + virtual + override + returns (FunctionReference, bool, bool, bytes4[] memory, bytes memory, bytes memory, bytes memory) + { + FunctionReference[] memory preValidationHooks = new FunctionReference[](2); + preValidationHooks[0] = FunctionReferenceLib.pack( + address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.PRE_VALIDATION_HOOK_1) + ); + preValidationHooks[1] = FunctionReferenceLib.pack( + address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.PRE_VALIDATION_HOOK_2) + ); + + bytes[] memory installDatas = new bytes[](2); + return ( + _ownerValidation, + true, + true, + new bytes4[](0), + bytes(""), + abi.encode(preValidationHooks, installDatas), + "" + ); + } } diff --git a/test/account/AccountReturnData.t.sol b/test/account/AccountReturnData.t.sol index 99a8b516..1738c722 100644 --- a/test/account/AccountReturnData.t.sol +++ b/test/account/AccountReturnData.t.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.19; import {FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; import {Call} from "../../src/interfaces/IStandardExecutor.sol"; -import {ISingleOwnerPlugin} from "../../src/plugins/owner/ISingleOwnerPlugin.sol"; import { RegularResultContract, @@ -11,6 +10,7 @@ import { ResultConsumerPlugin } from "../mocks/plugins/ReturnDataPluginMocks.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; +import {TEST_DEFAULT_OWNER_FUNCTION_ID} from "../utils/TestConstants.sol"; // Tests all the different ways that return data can be read from plugins through an account contract AccountReturnDataTest is AccountTestBase { @@ -58,10 +58,8 @@ contract AccountReturnDataTest is AccountTestBase { (address(regularResultContract), 0, abi.encodeCall(RegularResultContract.foo, ())) ), _encodeSignature( - FunctionReferenceLib.pack( - address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) - ), - SELECTOR_ASSOCIATED_VALIDATION, + FunctionReferenceLib.pack(address(singleOwnerPlugin), TEST_DEFAULT_OWNER_FUNCTION_ID), + GLOBAL_VALIDATION, "" ) ); @@ -88,10 +86,8 @@ contract AccountReturnDataTest is AccountTestBase { bytes memory retData = account1.executeWithAuthorization( abi.encodeCall(account1.executeBatch, (calls)), _encodeSignature( - FunctionReferenceLib.pack( - address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) - ), - SELECTOR_ASSOCIATED_VALIDATION, + FunctionReferenceLib.pack(address(singleOwnerPlugin), TEST_DEFAULT_OWNER_FUNCTION_ID), + GLOBAL_VALIDATION, "" ) ); diff --git a/test/account/MultiValidation.t.sol b/test/account/MultiValidation.t.sol index e0e69a37..5254c50d 100644 --- a/test/account/MultiValidation.t.sol +++ b/test/account/MultiValidation.t.sol @@ -11,10 +11,11 @@ import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAcc import {FunctionReference} from "../../src/interfaces/IPluginManager.sol"; import {IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; import {FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; +import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import {SingleOwnerPlugin} from "../../src/plugins/owner/SingleOwnerPlugin.sol"; -import {ISingleOwnerPlugin} from "../../src/plugins/owner/ISingleOwnerPlugin.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; +import {TEST_DEFAULT_OWNER_FUNCTION_ID} from "../utils/TestConstants.sol"; contract MultiValidationTest is AccountTestBase { using ECDSA for bytes32; @@ -32,16 +33,18 @@ contract MultiValidationTest is AccountTestBase { } function test_overlappingValidationInstall() public { - bytes32 manifestHash = keccak256(abi.encode(validator2.pluginManifest())); vm.prank(address(entryPoint)); - account1.installPlugin(address(validator2), manifestHash, abi.encode(owner2)); + account1.installValidation( + ValidationConfigLib.pack(address(validator2), TEST_DEFAULT_OWNER_FUNCTION_ID, true, true), + new bytes4[](0), + abi.encode(TEST_DEFAULT_OWNER_FUNCTION_ID, owner2), + "", + "" + ); FunctionReference[] memory validations = new FunctionReference[](2); - validations[0] = FunctionReferenceLib.pack( - address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) - ); - validations[1] = - FunctionReferenceLib.pack(address(validator2), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER)); + validations[0] = FunctionReferenceLib.pack(address(singleOwnerPlugin), TEST_DEFAULT_OWNER_FUNCTION_ID); + validations[1] = FunctionReferenceLib.pack(address(validator2), TEST_DEFAULT_OWNER_FUNCTION_ID); bytes4[] memory selectors0 = account1.getSelectors(validations[0]); bytes4[] memory selectors1 = account1.getSelectors(validations[1]); @@ -68,10 +71,8 @@ contract MultiValidationTest is AccountTestBase { account1.executeWithAuthorization( abi.encodeCall(IStandardExecutor.execute, (address(0), 0, "")), _encodeSignature( - FunctionReferenceLib.pack( - address(validator2), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) - ), - SELECTOR_ASSOCIATED_VALIDATION, + FunctionReferenceLib.pack(address(validator2), TEST_DEFAULT_OWNER_FUNCTION_ID), + GLOBAL_VALIDATION, "" ) ); @@ -80,10 +81,8 @@ contract MultiValidationTest is AccountTestBase { account1.executeWithAuthorization( abi.encodeCall(IStandardExecutor.execute, (address(0), 0, "")), _encodeSignature( - FunctionReferenceLib.pack( - address(validator2), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) - ), - SELECTOR_ASSOCIATED_VALIDATION, + FunctionReferenceLib.pack(address(validator2), TEST_DEFAULT_OWNER_FUNCTION_ID), + GLOBAL_VALIDATION, "" ) ); @@ -110,8 +109,8 @@ contract MultiValidationTest is AccountTestBase { bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); userOp.signature = _encodeSignature( - FunctionReferenceLib.pack(address(validator2), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER)), - SELECTOR_ASSOCIATED_VALIDATION, + FunctionReferenceLib.pack(address(validator2), TEST_DEFAULT_OWNER_FUNCTION_ID), + GLOBAL_VALIDATION, abi.encodePacked(r, s, v) ); @@ -125,8 +124,8 @@ contract MultiValidationTest is AccountTestBase { userOp.nonce = 1; (v, r, s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); userOp.signature = _encodeSignature( - FunctionReferenceLib.pack(address(validator2), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER)), - SELECTOR_ASSOCIATED_VALIDATION, + FunctionReferenceLib.pack(address(validator2), TEST_DEFAULT_OWNER_FUNCTION_ID), + GLOBAL_VALIDATION, abi.encodePacked(r, s, v) ); diff --git a/test/account/PerHookData.t.sol b/test/account/PerHookData.t.sol index 44641238..9c90cf5c 100644 --- a/test/account/PerHookData.t.sol +++ b/test/account/PerHookData.t.sol @@ -325,7 +325,7 @@ contract PerHookDataTest is CustomValidationTestBase { internal virtual override - returns (FunctionReference, bool, bytes4[] memory, bytes memory, bytes memory, bytes memory) + returns (FunctionReference, bool, bool, bytes4[] memory, bytes memory, bytes memory, bytes memory) { FunctionReference accessControlHook = FunctionReferenceLib.pack( address(_accessControlHookPlugin), uint8(MockAccessControlHookPlugin.FunctionId.PRE_VALIDATION_HOOK) @@ -340,6 +340,14 @@ contract PerHookDataTest is CustomValidationTestBase { bytes memory packedPreValidationHooks = abi.encode(preValidationHooks, preValidationHookData); - return (_ownerValidation, true, new bytes4[](0), abi.encode(owner1), packedPreValidationHooks, ""); + return ( + _ownerValidation, + true, + true, + new bytes4[](0), + abi.encode(TEST_DEFAULT_OWNER_FUNCTION_ID, owner1), + packedPreValidationHooks, + "" + ); } } diff --git a/test/account/SelfCallAuthorization.t.sol b/test/account/SelfCallAuthorization.t.sol index a2cc7838..c97194fd 100644 --- a/test/account/SelfCallAuthorization.t.sol +++ b/test/account/SelfCallAuthorization.t.sol @@ -8,6 +8,7 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {IStandardExecutor, Call} from "../../src/interfaces/IStandardExecutor.sol"; import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; +import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; import {GlobalValidationFactoryFixture} from "../mocks/GlobalValidationFactoryFixture.sol"; @@ -310,7 +311,7 @@ contract SelfCallAuthorizationTest is AccountTestBase { account1.executeWithAuthorization( abi.encodeCall( UpgradeableModularAccount.installValidation, - (comprehensivePluginValidation, false, selectors, "", "", "") + (ValidationConfigLib.pack(comprehensivePluginValidation, false, false), selectors, "", "", "") ), _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, "") ); diff --git a/test/account/UpgradeableModularAccount.t.sol b/test/account/UpgradeableModularAccount.t.sol index de3fda30..dda78c66 100644 --- a/test/account/UpgradeableModularAccount.t.sol +++ b/test/account/UpgradeableModularAccount.t.sol @@ -16,12 +16,12 @@ import {IPluginManager} from "../../src/interfaces/IPluginManager.sol"; import {Call} from "../../src/interfaces/IStandardExecutor.sol"; import {SingleOwnerPlugin} from "../../src/plugins/owner/SingleOwnerPlugin.sol"; import {TokenReceiverPlugin} from "../../src/plugins/TokenReceiverPlugin.sol"; -import {ISingleOwnerPlugin} from "../../src/plugins/owner/ISingleOwnerPlugin.sol"; import {Counter} from "../mocks/Counter.sol"; import {ComprehensivePlugin} from "../mocks/plugins/ComprehensivePlugin.sol"; import {MockPlugin} from "../mocks/MockPlugin.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; +import {TEST_DEFAULT_OWNER_FUNCTION_ID} from "../utils/TestConstants.sol"; contract UpgradeableModularAccountTest is AccountTestBase { using ECDSA for bytes32; @@ -77,8 +77,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = - _encodeSignature(_ownerValidation, SELECTOR_ASSOCIATED_VALIDATION, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -95,7 +94,11 @@ contract UpgradeableModularAccountTest is AccountTestBase { initCode: abi.encodePacked(address(factory), abi.encodeCall(factory.createAccount, (owner2, 0))), callData: abi.encodeCall( UpgradeableModularAccount.execute, - (address(singleOwnerPlugin), 0, abi.encodeCall(SingleOwnerPlugin.transferOwnership, (owner2))) + ( + address(singleOwnerPlugin), + 0, + abi.encodeCall(SingleOwnerPlugin.transferOwnership, (TEST_DEFAULT_OWNER_FUNCTION_ID, owner2)) + ) ), accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), preVerificationGas: 0, @@ -107,8 +110,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = - _encodeSignature(_ownerValidation, SELECTOR_ASSOCIATED_VALIDATION, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -134,8 +136,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = - _encodeSignature(_ownerValidation, SELECTOR_ASSOCIATED_VALIDATION, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -161,8 +162,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = - _encodeSignature(_ownerValidation, SELECTOR_ASSOCIATED_VALIDATION, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -190,8 +190,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = - _encodeSignature(_ownerValidation, SELECTOR_ASSOCIATED_VALIDATION, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -222,8 +221,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = - _encodeSignature(_ownerValidation, SELECTOR_ASSOCIATED_VALIDATION, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -248,9 +246,8 @@ contract UpgradeableModularAccountTest is AccountTestBase { }); address[] memory plugins = IAccountLoupe(account1).getInstalledPlugins(); - assertEq(plugins.length, 2); - assertEq(plugins[0], address(singleOwnerPlugin)); - assertEq(plugins[1], address(tokenReceiverPlugin)); + assertEq(plugins.length, 1); + assertEq(plugins[0], address(tokenReceiverPlugin)); } function test_installPlugin_PermittedCallSelectorNotInstalled() public { @@ -330,8 +327,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { emit PluginUninstalled(address(plugin), true); IPluginManager(account1).uninstallPlugin({plugin: address(plugin), config: "", pluginUninstallData: ""}); address[] memory plugins = IAccountLoupe(account1).getInstalledPlugins(); - assertEq(plugins.length, 1); - assertEq(plugins[0], address(singleOwnerPlugin)); + assertEq(plugins.length, 0); } function test_uninstallPlugin_manifestParameter() public { @@ -354,8 +350,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { pluginUninstallData: "" }); address[] memory plugins = IAccountLoupe(account1).getInstalledPlugins(); - assertEq(plugins.length, 1); - assertEq(plugins[0], address(singleOwnerPlugin)); + assertEq(plugins.length, 0); } function test_uninstallPlugin_invalidManifestFails() public { @@ -380,9 +375,8 @@ contract UpgradeableModularAccountTest is AccountTestBase { pluginUninstallData: "" }); address[] memory plugins = IAccountLoupe(account1).getInstalledPlugins(); - assertEq(plugins.length, 2); - assertEq(plugins[0], address(singleOwnerPlugin)); - assertEq(plugins[1], address(plugin)); + assertEq(plugins.length, 1); + assertEq(plugins[0], address(plugin)); } function _installPluginWithExecHooks() internal returns (MockPlugin plugin) { @@ -415,14 +409,16 @@ contract UpgradeableModularAccountTest is AccountTestBase { } function test_transferOwnership() public { - assertEq(singleOwnerPlugin.ownerOf(address(account1)), owner1); + assertEq(singleOwnerPlugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, address(account1)), owner1); vm.prank(address(entryPoint)); account1.execute( - address(singleOwnerPlugin), 0, abi.encodeCall(SingleOwnerPlugin.transferOwnership, (owner2)) + address(singleOwnerPlugin), + 0, + abi.encodeCall(SingleOwnerPlugin.transferOwnership, (TEST_DEFAULT_OWNER_FUNCTION_ID, owner2)) ); - assertEq(singleOwnerPlugin.ownerOf(address(account1)), owner2); + assertEq(singleOwnerPlugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, address(account1)), owner2); } function test_isValidSignature() public { @@ -432,9 +428,8 @@ contract UpgradeableModularAccountTest is AccountTestBase { // singleOwnerPlugin.ownerOf(address(account1)); - bytes memory signature = abi.encodePacked( - address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER), r, s, v - ); + bytes memory signature = + abi.encodePacked(address(singleOwnerPlugin), TEST_DEFAULT_OWNER_FUNCTION_ID, r, s, v); bytes4 validationResult = IERC1271(address(account1)).isValidSignature(message, signature); diff --git a/test/account/ValidationIntersection.t.sol b/test/account/ValidationIntersection.t.sol index d3fce6d8..7f245031 100644 --- a/test/account/ValidationIntersection.t.sol +++ b/test/account/ValidationIntersection.t.sol @@ -5,6 +5,7 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; +import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import { MockBaseUserOpValidationPlugin, @@ -65,8 +66,7 @@ contract ValidationIntersectionTest is AccountTestBase { }); bytes[] memory installDatas = new bytes[](1); account1.installValidation( - oneHookValidation, - true, + ValidationConfigLib.pack(oneHookValidation, true, true), new bytes4[](0), bytes(""), abi.encode(preValidationHooks, installDatas), @@ -89,8 +89,7 @@ contract ValidationIntersectionTest is AccountTestBase { }); installDatas = new bytes[](2); account1.installValidation( - twoHookValidation, - true, + ValidationConfigLib.pack(twoHookValidation, true, true), new bytes4[](0), bytes(""), abi.encode(preValidationHooks, installDatas), diff --git a/test/mocks/GlobalValidationFactoryFixture.sol b/test/mocks/GlobalValidationFactoryFixture.sol index bc61fb21..abe8c1c9 100644 --- a/test/mocks/GlobalValidationFactoryFixture.sol +++ b/test/mocks/GlobalValidationFactoryFixture.sol @@ -6,11 +6,11 @@ import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.s import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; -import {ISingleOwnerPlugin} from "../../src/plugins/owner/ISingleOwnerPlugin.sol"; +import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import {SingleOwnerPlugin} from "../../src/plugins/owner/SingleOwnerPlugin.sol"; import {OptimizedTest} from "../utils/OptimizedTest.sol"; +import {TEST_DEFAULT_OWNER_FUNCTION_ID} from "../utils/TestConstants.sol"; contract GlobalValidationFactoryFixture is OptimizedTest { UpgradeableModularAccount public accountImplementation; @@ -50,16 +50,13 @@ contract GlobalValidationFactoryFixture is OptimizedTest { // short circuit if exists if (addr.code.length == 0) { - bytes memory pluginInstallData = abi.encode(owner); + bytes memory pluginInstallData = abi.encode(TEST_DEFAULT_OWNER_FUNCTION_ID, owner); // not necessary to check return addr since next call will fail if so new ERC1967Proxy{salt: getSalt(owner, salt)}(address(accountImplementation), ""); // point proxy to actual implementation and init plugins UpgradeableModularAccount(payable(addr)).initializeWithValidation( - FunctionReferenceLib.pack( - address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) - ), - true, + ValidationConfigLib.pack(address(singleOwnerPlugin), TEST_DEFAULT_OWNER_FUNCTION_ID, true, true), new bytes4[](0), pluginInstallData, "", diff --git a/test/mocks/MSCAFactoryFixture.sol b/test/mocks/MSCAFactoryFixture.sol index cc8ae60a..8ca3a51f 100644 --- a/test/mocks/MSCAFactoryFixture.sol +++ b/test/mocks/MSCAFactoryFixture.sol @@ -6,9 +6,11 @@ import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.s import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; +import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import {SingleOwnerPlugin} from "../../src/plugins/owner/SingleOwnerPlugin.sol"; import {OptimizedTest} from "../utils/OptimizedTest.sol"; +import {TEST_DEFAULT_OWNER_FUNCTION_ID} from "../utils/TestConstants.sol"; /** * @title MSCAFactoryFixture @@ -24,10 +26,6 @@ contract MSCAFactoryFixture is OptimizedTest { IEntryPoint public entryPoint; - address public self; - - bytes32 public singleOwnerPluginManifestHash; - constructor(IEntryPoint _entryPoint, SingleOwnerPlugin _singleOwnerPlugin) { entryPoint = _entryPoint; accountImplementation = _deployUpgradeableModularAccount(_entryPoint); @@ -35,10 +33,6 @@ contract MSCAFactoryFixture is OptimizedTest { abi.encodePacked(type(ERC1967Proxy).creationCode, abi.encode(address(accountImplementation), "")) ); singleOwnerPlugin = _singleOwnerPlugin; - self = address(this); - // The manifest hash is set this way in this factory just for testing purposes. - // For production factories the manifest hashes should be passed as a constructor argument. - singleOwnerPluginManifestHash = keccak256(abi.encode(singleOwnerPlugin.pluginManifest())); } /** @@ -53,17 +47,18 @@ contract MSCAFactoryFixture is OptimizedTest { // short circuit if exists if (addr.code.length == 0) { - address[] memory plugins = new address[](1); - plugins[0] = address(singleOwnerPlugin); - bytes32[] memory pluginManifestHashes = new bytes32[](1); - pluginManifestHashes[0] = keccak256(abi.encode(singleOwnerPlugin.pluginManifest())); - bytes[] memory pluginInstallData = new bytes[](1); - pluginInstallData[0] = abi.encode(owner); + bytes memory pluginInstallData = abi.encode(TEST_DEFAULT_OWNER_FUNCTION_ID, owner); // not necessary to check return addr since next call will fail if so new ERC1967Proxy{salt: getSalt(owner, salt)}(address(accountImplementation), ""); // point proxy to actual implementation and init plugins - UpgradeableModularAccount(payable(addr)).initialize(plugins, pluginManifestHashes, pluginInstallData); + UpgradeableModularAccount(payable(addr)).initializeWithValidation( + ValidationConfigLib.pack(address(singleOwnerPlugin), TEST_DEFAULT_OWNER_FUNCTION_ID, true, true), + new bytes4[](0), + pluginInstallData, + "", + "" + ); } return UpgradeableModularAccount(payable(addr)); diff --git a/test/plugin/SingleOwnerPlugin.t.sol b/test/plugin/SingleOwnerPlugin.t.sol index f86a29f4..ddfe4d41 100644 --- a/test/plugin/SingleOwnerPlugin.t.sol +++ b/test/plugin/SingleOwnerPlugin.t.sol @@ -7,9 +7,10 @@ import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {SingleOwnerPlugin} from "../../src/plugins/owner/SingleOwnerPlugin.sol"; -import {ISingleOwnerPlugin} from "../../src/plugins/owner/ISingleOwnerPlugin.sol"; + import {ContractOwner} from "../mocks/ContractOwner.sol"; import {OptimizedTest} from "../utils/OptimizedTest.sol"; +import {TEST_DEFAULT_OWNER_FUNCTION_ID} from "../utils/TestConstants.sol"; contract SingleOwnerPluginTest is OptimizedTest { using ECDSA for bytes32; @@ -51,74 +52,74 @@ contract SingleOwnerPluginTest is OptimizedTest { function test_uninitializedOwner() public { vm.startPrank(a); - assertEq(address(0), plugin.owner()); + assertEq(address(0), plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); } function test_ownerInitialization() public { vm.startPrank(a); - assertEq(address(0), plugin.owner()); - plugin.transferOwnership(owner1); - assertEq(owner1, plugin.owner()); + assertEq(address(0), plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); + plugin.transferOwnership(TEST_DEFAULT_OWNER_FUNCTION_ID, owner1); + assertEq(owner1, plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); } function test_ownerInitializationEvent() public { vm.startPrank(a); - assertEq(address(0), plugin.owner()); + assertEq(address(0), plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); vm.expectEmit(true, true, true, true); emit OwnershipTransferred(a, address(0), owner1); - plugin.transferOwnership(owner1); - assertEq(owner1, plugin.owner()); + plugin.transferOwnership(TEST_DEFAULT_OWNER_FUNCTION_ID, owner1); + assertEq(owner1, plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); } function test_ownerMigration() public { vm.startPrank(a); - assertEq(address(0), plugin.owner()); - plugin.transferOwnership(owner1); - assertEq(owner1, plugin.owner()); - plugin.transferOwnership(owner2); - assertEq(owner2, plugin.owner()); + assertEq(address(0), plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); + plugin.transferOwnership(TEST_DEFAULT_OWNER_FUNCTION_ID, owner1); + assertEq(owner1, plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); + plugin.transferOwnership(TEST_DEFAULT_OWNER_FUNCTION_ID, owner2); + assertEq(owner2, plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); } function test_ownerMigrationEvents() public { vm.startPrank(a); - assertEq(address(0), plugin.owner()); + assertEq(address(0), plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); vm.expectEmit(true, true, true, true); emit OwnershipTransferred(a, address(0), owner1); - plugin.transferOwnership(owner1); - assertEq(owner1, plugin.owner()); + plugin.transferOwnership(TEST_DEFAULT_OWNER_FUNCTION_ID, owner1); + assertEq(owner1, plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); vm.expectEmit(true, true, true, true); emit OwnershipTransferred(a, owner1, owner2); - plugin.transferOwnership(owner2); - assertEq(owner2, plugin.owner()); + plugin.transferOwnership(TEST_DEFAULT_OWNER_FUNCTION_ID, owner2); + assertEq(owner2, plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); } function test_ownerForSender() public { vm.startPrank(a); - assertEq(address(0), plugin.owner()); - plugin.transferOwnership(owner1); - assertEq(owner1, plugin.owner()); + assertEq(address(0), plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); + plugin.transferOwnership(TEST_DEFAULT_OWNER_FUNCTION_ID, owner1); + assertEq(owner1, plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); vm.startPrank(b); - assertEq(address(0), plugin.owner()); - plugin.transferOwnership(owner2); - assertEq(owner2, plugin.owner()); + assertEq(address(0), plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, b)); + plugin.transferOwnership(TEST_DEFAULT_OWNER_FUNCTION_ID, owner2); + assertEq(owner2, plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, b)); } function test_requireOwner() public { vm.startPrank(a); - assertEq(address(0), plugin.owner()); - plugin.transferOwnership(owner1); - assertEq(owner1, plugin.owner()); - plugin.validateRuntime(uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER), owner1, 0, "", ""); + assertEq(address(0), plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); + plugin.transferOwnership(TEST_DEFAULT_OWNER_FUNCTION_ID, owner1); + assertEq(owner1, plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); + plugin.validateRuntime(TEST_DEFAULT_OWNER_FUNCTION_ID, owner1, 0, "", ""); vm.startPrank(b); - vm.expectRevert(ISingleOwnerPlugin.NotAuthorized.selector); - plugin.validateRuntime(uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER), owner1, 0, "", ""); + vm.expectRevert(SingleOwnerPlugin.NotAuthorized.selector); + plugin.validateRuntime(TEST_DEFAULT_OWNER_FUNCTION_ID, owner1, 0, "", ""); } function testFuzz_validateUserOpSig(string memory salt, PackedUserOperation memory userOp) public { @@ -133,16 +134,15 @@ contract SingleOwnerPluginTest is OptimizedTest { userOp.signature = abi.encodePacked(r, s, v); // sig check should fail - uint256 success = - plugin.validateUserOp(uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER), userOp, userOpHash); + uint256 success = plugin.validateUserOp(TEST_DEFAULT_OWNER_FUNCTION_ID, userOp, userOpHash); assertEq(success, 1); // transfer ownership to signer - plugin.transferOwnership(signer); - assertEq(signer, plugin.owner()); + plugin.transferOwnership(TEST_DEFAULT_OWNER_FUNCTION_ID, signer); + assertEq(signer, plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); // sig check should pass - success = plugin.validateUserOp(uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER), userOp, userOpHash); + success = plugin.validateUserOp(TEST_DEFAULT_OWNER_FUNCTION_ID, userOp, userOpHash); assertEq(success, 0); } @@ -156,25 +156,19 @@ contract SingleOwnerPluginTest is OptimizedTest { // sig check should fail assertEq( plugin.validateSignature( - uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER), - address(this), - digest, - abi.encodePacked(r, s, v) + TEST_DEFAULT_OWNER_FUNCTION_ID, address(this), digest, abi.encodePacked(r, s, v) ), bytes4(0xFFFFFFFF) ); // transfer ownership to signer - plugin.transferOwnership(signer); - assertEq(signer, plugin.owner()); + plugin.transferOwnership(TEST_DEFAULT_OWNER_FUNCTION_ID, signer); + assertEq(signer, plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); // sig check should pass assertEq( plugin.validateSignature( - uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER), - address(this), - digest, - abi.encodePacked(r, s, v) + TEST_DEFAULT_OWNER_FUNCTION_ID, address(this), digest, abi.encodePacked(r, s, v) ), _1271_MAGIC_VALUE ); @@ -182,12 +176,10 @@ contract SingleOwnerPluginTest is OptimizedTest { function testFuzz_isValidSignatureForContractOwner(bytes32 digest) public { vm.startPrank(a); - plugin.transferOwnership(address(contractOwner)); + plugin.transferOwnership(TEST_DEFAULT_OWNER_FUNCTION_ID, address(contractOwner)); bytes memory signature = contractOwner.sign(digest); assertEq( - plugin.validateSignature( - uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER), address(this), digest, signature - ), + plugin.validateSignature(TEST_DEFAULT_OWNER_FUNCTION_ID, address(this), digest, signature), _1271_MAGIC_VALUE ); } diff --git a/test/samples/AllowlistPlugin.t.sol b/test/samples/AllowlistPlugin.t.sol index da70c8c3..d81d5f79 100644 --- a/test/samples/AllowlistPlugin.t.sol +++ b/test/samples/AllowlistPlugin.t.sol @@ -290,7 +290,7 @@ contract AllowlistPluginTest is CustomValidationTestBase { internal virtual override - returns (FunctionReference, bool, bytes4[] memory, bytes memory, bytes memory, bytes memory) + returns (FunctionReference, bool, bool, bytes4[] memory, bytes memory, bytes memory, bytes memory) { FunctionReference accessControlHook = FunctionReferenceLib.pack( address(allowlistPlugin), uint8(AllowlistPlugin.FunctionId.PRE_VALIDATION_HOOK) @@ -305,7 +305,15 @@ contract AllowlistPluginTest is CustomValidationTestBase { bytes memory packedPreValidationHooks = abi.encode(preValidationHooks, preValidationHookData); - return (_ownerValidation, true, new bytes4[](0), abi.encode(owner1), packedPreValidationHooks, ""); + return ( + _ownerValidation, + true, + true, + new bytes4[](0), + abi.encode(TEST_DEFAULT_OWNER_FUNCTION_ID, owner1), + packedPreValidationHooks, + "" + ); } // Unfortunately, this is a feature that solidity has only implemented in via-ir, so we need to do it manually diff --git a/test/utils/AccountTestBase.sol b/test/utils/AccountTestBase.sol index 4383f393..a312fbdf 100644 --- a/test/utils/AccountTestBase.sol +++ b/test/utils/AccountTestBase.sol @@ -8,10 +8,10 @@ import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/Messa import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; import {IStandardExecutor, Call} from "../../src/interfaces/IStandardExecutor.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {ISingleOwnerPlugin} from "../../src/plugins/owner/ISingleOwnerPlugin.sol"; import {SingleOwnerPlugin} from "../../src/plugins/owner/SingleOwnerPlugin.sol"; import {OptimizedTest} from "./OptimizedTest.sol"; +import {TEST_DEFAULT_OWNER_FUNCTION_ID as EXT_CONST_TEST_DEFAULT_OWNER_FUNCTION_ID} from "./TestConstants.sol"; import {MSCAFactoryFixture} from "../mocks/MSCAFactoryFixture.sol"; @@ -35,6 +35,9 @@ abstract contract AccountTestBase is OptimizedTest { uint8 public constant SELECTOR_ASSOCIATED_VALIDATION = 0; uint8 public constant GLOBAL_VALIDATION = 1; + // Re-declare the constant to prevent derived test contracts from having to import it + uint8 public constant TEST_DEFAULT_OWNER_FUNCTION_ID = EXT_CONST_TEST_DEFAULT_OWNER_FUNCTION_ID; + uint256 public constant CALL_GAS_LIMIT = 100000; uint256 public constant VERIFICATION_GAS_LIMIT = 1200000; @@ -54,9 +57,7 @@ abstract contract AccountTestBase is OptimizedTest { account1 = factory.createAccount(owner1, 0); vm.deal(address(account1), 100 ether); - _ownerValidation = FunctionReferenceLib.pack( - address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) - ); + _ownerValidation = FunctionReferenceLib.pack(address(singleOwnerPlugin), TEST_DEFAULT_OWNER_FUNCTION_ID); } function _runExecUserOp(address target, bytes memory callData) internal { @@ -99,9 +100,7 @@ abstract contract AccountTestBase is OptimizedTest { (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); userOp.signature = _encodeSignature( - FunctionReferenceLib.pack( - address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) - ), + FunctionReferenceLib.pack(address(singleOwnerPlugin), TEST_DEFAULT_OWNER_FUNCTION_ID), GLOBAL_VALIDATION, abi.encodePacked(r, s, v) ); @@ -154,9 +153,7 @@ abstract contract AccountTestBase is OptimizedTest { account1.executeWithAuthorization( callData, _encodeSignature( - FunctionReferenceLib.pack( - address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) - ), + FunctionReferenceLib.pack(address(singleOwnerPlugin), TEST_DEFAULT_OWNER_FUNCTION_ID), GLOBAL_VALIDATION, "" ) @@ -171,9 +168,7 @@ abstract contract AccountTestBase is OptimizedTest { account1.executeWithAuthorization( callData, _encodeSignature( - FunctionReferenceLib.pack( - address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) - ), + FunctionReferenceLib.pack(address(singleOwnerPlugin), TEST_DEFAULT_OWNER_FUNCTION_ID), GLOBAL_VALIDATION, "" ) @@ -189,14 +184,14 @@ abstract contract AccountTestBase is OptimizedTest { ( address(singleOwnerPlugin), 0, - abi.encodeCall(SingleOwnerPlugin.transferOwnership, (address(this))) + abi.encodeCall( + SingleOwnerPlugin.transferOwnership, (TEST_DEFAULT_OWNER_FUNCTION_ID, address(this)) + ) ) ), _encodeSignature( - FunctionReferenceLib.pack( - address(singleOwnerPlugin), uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER) - ), - SELECTOR_ASSOCIATED_VALIDATION, + FunctionReferenceLib.pack(address(singleOwnerPlugin), TEST_DEFAULT_OWNER_FUNCTION_ID), + GLOBAL_VALIDATION, "" ) ); diff --git a/test/utils/CustomValidationTestBase.sol b/test/utils/CustomValidationTestBase.sol index 1bf76c3a..2244c865 100644 --- a/test/utils/CustomValidationTestBase.sol +++ b/test/utils/CustomValidationTestBase.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.25; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {FunctionReference} from "../../src/helpers/FunctionReferenceLib.sol"; +import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {AccountTestBase} from "./AccountTestBase.sol"; @@ -16,7 +17,8 @@ abstract contract CustomValidationTestBase is AccountTestBase { function _customValidationSetup() internal { ( FunctionReference validationFunction, - bool shared, + bool isGlobal, + bool isSignatureValidation, bytes4[] memory selectors, bytes memory installData, bytes memory preValidationHooks, @@ -28,7 +30,11 @@ abstract contract CustomValidationTestBase is AccountTestBase { account1 = UpgradeableModularAccount(payable(new ERC1967Proxy{salt: 0}(accountImplementation, ""))); account1.initializeWithValidation( - validationFunction, shared, selectors, installData, preValidationHooks, permissionHooks + ValidationConfigLib.pack(validationFunction, isGlobal, isSignatureValidation), + selectors, + installData, + preValidationHooks, + permissionHooks ); vm.deal(address(account1), 100 ether); @@ -40,6 +46,7 @@ abstract contract CustomValidationTestBase is AccountTestBase { returns ( FunctionReference validationFunction, bool shared, + bool isSignatureValidation, bytes4[] memory selectors, bytes memory installData, bytes memory preValidationHooks, diff --git a/test/utils/TestConstants.sol b/test/utils/TestConstants.sol new file mode 100644 index 00000000..923692a7 --- /dev/null +++ b/test/utils/TestConstants.sol @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.25; + +uint8 constant TEST_DEFAULT_OWNER_FUNCTION_ID = 0; From 42d3db81b6ba6372b056f967e378da285b5c7222 Mon Sep 17 00:00:00 2001 From: Adam Egyed <5456061+adamegyed@users.noreply.github.com> Date: Thu, 11 Jul 2024 15:58:12 -0400 Subject: [PATCH 031/111] feat: [v0.8-develop] remove duplicate test factory [2/2] (#92) --- test/account/GlobalValidationTest.t.sol | 32 +++---- test/account/SelfCallAuthorization.t.sol | 9 -- test/mocks/GlobalValidationFactoryFixture.sol | 84 ------------------- 3 files changed, 17 insertions(+), 108 deletions(-) delete mode 100644 test/mocks/GlobalValidationFactoryFixture.sol diff --git a/test/account/GlobalValidationTest.t.sol b/test/account/GlobalValidationTest.t.sol index c242657c..9f40f806 100644 --- a/test/account/GlobalValidationTest.t.sol +++ b/test/account/GlobalValidationTest.t.sol @@ -5,23 +5,28 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; +import {FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; -import {GlobalValidationFactoryFixture} from "../mocks/GlobalValidationFactoryFixture.sol"; contract GlobalValidationTest is AccountTestBase { using MessageHashUtils for bytes32; - GlobalValidationFactoryFixture public globalValidationFactoryFixture; - address public ethRecipient; + // A separate account and owner that isn't deployed yet, used to test initcode + address public owner2; + uint256 public owner2Key; + UpgradeableModularAccount public account2; + function setUp() public { - globalValidationFactoryFixture = new GlobalValidationFactoryFixture(entryPoint, singleOwnerPlugin); + (owner2, owner2Key) = makeAddrAndKey("owner2"); - account1 = UpgradeableModularAccount(payable(globalValidationFactoryFixture.getAddress(owner1, 0))); + // Compute counterfactual address + account2 = UpgradeableModularAccount(payable(factory.getAddress(owner2, 0))); + vm.deal(address(account2), 100 ether); - vm.deal(address(account1), 100 ether); + _ownerValidation = FunctionReferenceLib.pack(address(singleOwnerPlugin), TEST_DEFAULT_OWNER_FUNCTION_ID); ethRecipient = makeAddr("ethRecipient"); vm.deal(ethRecipient, 1 wei); @@ -29,12 +34,9 @@ contract GlobalValidationTest is AccountTestBase { function test_globalValidation_userOp_simple() public { PackedUserOperation memory userOp = PackedUserOperation({ - sender: address(account1), + sender: address(account2), nonce: 0, - initCode: abi.encodePacked( - globalValidationFactoryFixture, - abi.encodeCall(globalValidationFactoryFixture.createAccount, (owner1, 0)) - ), + initCode: abi.encodePacked(address(factory), abi.encodeCall(factory.createAccount, (owner2, 0))), callData: abi.encodeCall(UpgradeableModularAccount.execute, (ethRecipient, 1 wei, "")), accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), preVerificationGas: 0, @@ -45,7 +47,7 @@ contract GlobalValidationTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); userOp.signature = _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); @@ -58,10 +60,10 @@ contract GlobalValidationTest is AccountTestBase { function test_globalValidation_runtime_simple() public { // Deploy the account first - globalValidationFactoryFixture.createAccount(owner1, 0); + factory.createAccount(owner2, 0); - vm.prank(owner1); - account1.executeWithAuthorization( + vm.prank(owner2); + account2.executeWithAuthorization( abi.encodeCall(UpgradeableModularAccount.execute, (ethRecipient, 1 wei, "")), _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, "") ); diff --git a/test/account/SelfCallAuthorization.t.sol b/test/account/SelfCallAuthorization.t.sol index c97194fd..5bcf64b3 100644 --- a/test/account/SelfCallAuthorization.t.sol +++ b/test/account/SelfCallAuthorization.t.sol @@ -11,23 +11,14 @@ import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/Functio import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; -import {GlobalValidationFactoryFixture} from "../mocks/GlobalValidationFactoryFixture.sol"; import {ComprehensivePlugin} from "../mocks/plugins/ComprehensivePlugin.sol"; contract SelfCallAuthorizationTest is AccountTestBase { - GlobalValidationFactoryFixture public globalValidationFactoryFixture; - ComprehensivePlugin public comprehensivePlugin; FunctionReference public comprehensivePluginValidation; function setUp() public { - globalValidationFactoryFixture = new GlobalValidationFactoryFixture(entryPoint, singleOwnerPlugin); - - account1 = UpgradeableModularAccount(payable(globalValidationFactoryFixture.createAccount(owner1, 0))); - - vm.deal(address(account1), 100 ether); - // install the comprehensive plugin to get new exec functions with different validations configured. comprehensivePlugin = new ComprehensivePlugin(); diff --git a/test/mocks/GlobalValidationFactoryFixture.sol b/test/mocks/GlobalValidationFactoryFixture.sol deleted file mode 100644 index abe8c1c9..00000000 --- a/test/mocks/GlobalValidationFactoryFixture.sol +++ /dev/null @@ -1,84 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; - -import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; -import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; - -import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; -import {SingleOwnerPlugin} from "../../src/plugins/owner/SingleOwnerPlugin.sol"; - -import {OptimizedTest} from "../utils/OptimizedTest.sol"; -import {TEST_DEFAULT_OWNER_FUNCTION_ID} from "../utils/TestConstants.sol"; - -contract GlobalValidationFactoryFixture is OptimizedTest { - UpgradeableModularAccount public accountImplementation; - SingleOwnerPlugin public singleOwnerPlugin; - bytes32 private immutable _PROXY_BYTECODE_HASH; - - uint32 public constant UNSTAKE_DELAY = 1 weeks; - - IEntryPoint public entryPoint; - - address public self; - - bytes32 public singleOwnerPluginManifestHash; - - constructor(IEntryPoint _entryPoint, SingleOwnerPlugin _singleOwnerPlugin) { - entryPoint = _entryPoint; - accountImplementation = _deployUpgradeableModularAccount(_entryPoint); - _PROXY_BYTECODE_HASH = keccak256( - abi.encodePacked(type(ERC1967Proxy).creationCode, abi.encode(address(accountImplementation), "")) - ); - singleOwnerPlugin = _singleOwnerPlugin; - self = address(this); - // The manifest hash is set this way in this factory just for testing purposes. - // For production factories the manifest hashes should be passed as a constructor argument. - singleOwnerPluginManifestHash = keccak256(abi.encode(singleOwnerPlugin.pluginManifest())); - } - - /** - * create an account, and return its address. - * returns the address even if the account is already deployed. - * Note that during user operation execution, this method is called only if the account is not deployed. - * This method returns an existing account address so that entryPoint.getSenderAddress() would work even after - * account creation - */ - function createAccount(address owner, uint256 salt) public returns (UpgradeableModularAccount) { - address addr = Create2.computeAddress(getSalt(owner, salt), _PROXY_BYTECODE_HASH); - - // short circuit if exists - if (addr.code.length == 0) { - bytes memory pluginInstallData = abi.encode(TEST_DEFAULT_OWNER_FUNCTION_ID, owner); - // not necessary to check return addr since next call will fail if so - new ERC1967Proxy{salt: getSalt(owner, salt)}(address(accountImplementation), ""); - - // point proxy to actual implementation and init plugins - UpgradeableModularAccount(payable(addr)).initializeWithValidation( - ValidationConfigLib.pack(address(singleOwnerPlugin), TEST_DEFAULT_OWNER_FUNCTION_ID, true, true), - new bytes4[](0), - pluginInstallData, - "", - "" - ); - } - - return UpgradeableModularAccount(payable(addr)); - } - - /** - * calculate the counterfactual address of this account as it would be returned by createAccount() - */ - function getAddress(address owner, uint256 salt) public view returns (address) { - return Create2.computeAddress(getSalt(owner, salt), _PROXY_BYTECODE_HASH); - } - - function addStake() external payable { - entryPoint.addStake{value: msg.value}(UNSTAKE_DELAY); - } - - function getSalt(address owner, uint256 salt) public pure returns (bytes32) { - return keccak256(abi.encodePacked(owner, salt)); - } -} From 4279a97835c385a7be1487533b2035df274a6473 Mon Sep 17 00:00:00 2001 From: Adam Egyed <5456061+adamegyed@users.noreply.github.com> Date: Tue, 16 Jul 2024 19:26:49 -0400 Subject: [PATCH 032/111] fix: [v0.8-develop] cleanup requireUOHookCount (#95) --- src/account/AccountStorage.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/account/AccountStorage.sol b/src/account/AccountStorage.sol index 78c06259..242ddff8 100644 --- a/src/account/AccountStorage.sol +++ b/src/account/AccountStorage.sol @@ -30,8 +30,6 @@ struct ValidationData { bool isGlobal; // Whether or not this validation is a signature validator. bool isSignatureValidation; - // How many execution hooks require the UO context. - uint8 requireUOHookCount; // The pre validation hooks for this function selector. FunctionReference[] preValidationHooks; // Permission hooks for this validation function. From c74d1ff296042228f3e59a2bbe2487c2f2c196ab Mon Sep 17 00:00:00 2001 From: howydev <132113803+howydev@users.noreply.github.com> Date: Wed, 12 Jun 2024 16:04:58 -0400 Subject: [PATCH 033/111] feat: add native token spend limit plugin --- src/plugins/NativeTokenLimitPlugin.sol | 152 +++++++++++++++++ test/plugin/NativeTokenLimitPlugin.t.sol | 206 +++++++++++++++++++++++ 2 files changed, 358 insertions(+) create mode 100644 src/plugins/NativeTokenLimitPlugin.sol create mode 100644 test/plugin/NativeTokenLimitPlugin.t.sol diff --git a/src/plugins/NativeTokenLimitPlugin.sol b/src/plugins/NativeTokenLimitPlugin.sol new file mode 100644 index 00000000..5127f18a --- /dev/null +++ b/src/plugins/NativeTokenLimitPlugin.sol @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.25; + +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; +import {UserOperationLib} from "@eth-infinitism/account-abstraction/core/UserOperationLib.sol"; +import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import {IAccountExecute} from "@eth-infinitism/account-abstraction/interfaces/IAccountExecute.sol"; + +import {PluginManifest, PluginMetadata} from "../interfaces/IPlugin.sol"; +import {IStandardExecutor, Call} from "../interfaces/IStandardExecutor.sol"; +import {IPlugin} from "../interfaces/IPlugin.sol"; +import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; +import {IValidationHook} from "../interfaces/IValidationHook.sol"; +import {BasePlugin, IERC165} from "./BasePlugin.sol"; + +/// @title Native Token Limit Plugin +/// @author ERC-6900 Authors +/// @notice This plugin supports a single total native token spend limit. +/// It tracks a total spend limit across UserOperation gas limits and native token transfers. +/// If a paymaster is used, UO gas would not cause the limit to decrease. +contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { + using UserOperationLib for PackedUserOperation; + using EnumerableSet for EnumerableSet.Bytes32Set; + + string public constant NAME = "Native Token Limit"; + string public constant VERSION = "1.0.0"; + string public constant AUTHOR = "ERC-6900 Authors"; + + mapping(address account => uint256[] limits) public limits; + + error ExceededNativeTokenLimit(); + error ExceededNumberOfEntities(); + + function updateLimits(uint8 functionId, uint256 newLimit) external { + limits[msg.sender][functionId] = newLimit; + } + + /// @inheritdoc IExecutionHook + function preExecutionHook(uint8 functionId, bytes calldata data) external override returns (bytes memory) { + bytes calldata callData; + bytes4 execSelector; + + // TODO: plugins should never have to do these gymnastics + execSelector = bytes4(data[52:56]); + if (execSelector == IAccountExecute.executeUserOp.selector) { + callData = data[56:]; + execSelector = bytes4(callData); + } else { + callData = data[52:]; + } + + uint256 value; + // Get value being sent + if (execSelector == IStandardExecutor.execute.selector) { + value = uint256(bytes32(callData[36:68])); + } else if (execSelector == IStandardExecutor.executeBatch.selector) { + Call[] memory calls = abi.decode(callData[4:], (Call[])); + for (uint256 i = 0; i < calls.length; i++) { + value += calls[i].value; + } + } + + uint256 limit = limits[msg.sender][functionId]; + if (value > limit) { + revert ExceededNativeTokenLimit(); + } + limits[msg.sender][functionId] = limit - value; + + return ""; + } + + /// @inheritdoc IExecutionHook + function postExecutionHook(uint8, bytes calldata) external pure override { + revert NotImplemented(); + } + + // No implementation, no revert + // Runtime spends no account gas, and we check native token spend limits in exec hooks + function preRuntimeValidationHook(uint8 functionId, address, uint256, bytes calldata) external pure override { + // silence warnings + (functionId); + } + + /// @inheritdoc IValidationHook + function preUserOpValidationHook(uint8 functionId, PackedUserOperation calldata userOp, bytes32) + external + returns (uint256) + { + // Decrease limit only if no paymaster is used + if (userOp.paymasterAndData.length == 0) { + uint256 vgl = UserOperationLib.unpackVerificationGasLimit(userOp); + uint256 cgl = UserOperationLib.unpackCallGasLimit(userOp); + uint256 totalGas = userOp.preVerificationGas + vgl + cgl; + uint256 usage = totalGas * UserOperationLib.unpackMaxFeePerGas(userOp); + + uint256 limit = limits[msg.sender][functionId]; + if (usage > limit) { + revert ExceededNativeTokenLimit(); + } + limits[msg.sender][functionId] = limit - usage; + } + return 0; + } + + /// @inheritdoc IPlugin + function onInstall(bytes calldata data) external override { + uint256[] memory spendLimits = abi.decode(data, (uint256[])); + + for (uint256 i = 0; i < spendLimits.length; i++) { + limits[msg.sender].push(spendLimits[i]); + } + + if (limits[msg.sender].length > type(uint8).max) { + revert ExceededNumberOfEntities(); + } + } + + /// @inheritdoc IPlugin + function onUninstall(bytes calldata data) external override { + uint8 functionId = abi.decode(data, (uint8)); + delete limits[msg.sender][functionId]; + } + + /// @inheritdoc IPlugin + function pluginManifest() external pure override returns (PluginManifest memory) { + // silence warnings + PluginManifest memory manifest; + return manifest; + } + + /// @inheritdoc IPlugin + function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { + PluginMetadata memory metadata; + metadata.name = NAME; + metadata.version = VERSION; + metadata.author = AUTHOR; + + metadata.permissionRequest = new string[](2); + metadata.permissionRequest[0] = "native-token-limit"; + metadata.permissionRequest[1] = "gas-limit"; + return metadata; + } + + // ┏━━━━━━━━━━━━━━━┓ + // ┃ EIP-165 ┃ + // ┗━━━━━━━━━━━━━━━┛ + + /// @inheritdoc BasePlugin + function supportsInterface(bytes4 interfaceId) public view override(BasePlugin, IERC165) returns (bool) { + return super.supportsInterface(interfaceId); + } +} diff --git a/test/plugin/NativeTokenLimitPlugin.t.sol b/test/plugin/NativeTokenLimitPlugin.t.sol new file mode 100644 index 00000000..4284d36e --- /dev/null +++ b/test/plugin/NativeTokenLimitPlugin.t.sol @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.sol"; +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; + +import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; +import {FunctionReference} from "../../src/helpers/FunctionReferenceLib.sol"; +import {NativeTokenLimitPlugin} from "../../src/plugins/NativeTokenLimitPlugin.sol"; +import {MockPlugin} from "../mocks/MockPlugin.sol"; +import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; +import {FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; +import {IStandardExecutor, Call} from "../../src/interfaces/IStandardExecutor.sol"; +import {PluginManifest} from "../../src/interfaces/IPlugin.sol"; + +import {MSCAFactoryFixture} from "../mocks/MSCAFactoryFixture.sol"; +import {OptimizedTest} from "../utils/OptimizedTest.sol"; + +contract NativeTokenLimitPluginTest is OptimizedTest { + EntryPoint public entryPoint = new EntryPoint(); + address public recipient = address(1); + address payable public bundler = payable(address(2)); + PluginManifest internal _m; + MockPlugin public validationPlugin = new MockPlugin(_m); + FunctionReference public validationFunction; + + UpgradeableModularAccount public acct; + NativeTokenLimitPlugin public plugin = new NativeTokenLimitPlugin(); + uint256 public spendLimit = 10 ether; + + function setUp() public { + // Set up a validator with hooks from the gas spend limit plugin attached + + MSCAFactoryFixture factory = new MSCAFactoryFixture(entryPoint, _deploySingleOwnerPlugin()); + + acct = factory.createAccount(address(this), 0); + + vm.deal(address(acct), 10 ether); + + FunctionReference[] memory preValidationHooks = new FunctionReference[](1); + preValidationHooks[0] = FunctionReferenceLib.pack(address(plugin), 0); + + ExecutionHook[] memory permissionHooks = new ExecutionHook[](1); + permissionHooks[0] = ExecutionHook({ + hookFunction: FunctionReferenceLib.pack(address(plugin), 0), + isPreHook: true, + isPostHook: false, + requireUOContext: false + }); + + uint256[] memory spendLimits = new uint256[](1); + spendLimits[0] = spendLimit; + + bytes[] memory preValHooksInitDatas = new bytes[](1); + preValHooksInitDatas[0] = ""; + + bytes[] memory permissionInitDatas = new bytes[](1); + permissionInitDatas[0] = abi.encode(spendLimits); + + vm.prank(address(acct)); + acct.installValidation( + FunctionReferenceLib.pack(address(validationPlugin), 0), + true, + new bytes4[](0), + new bytes(0), + abi.encode(preValidationHooks, preValHooksInitDatas), + abi.encode(permissionHooks, permissionInitDatas) + ); + + validationFunction = FunctionReferenceLib.pack(address(validationPlugin), 0); + } + + function _getExecuteWithValue(uint256 value) internal view returns (bytes memory) { + return abi.encodeCall(UpgradeableModularAccount.execute, (recipient, value, "")); + } + + function _getPackedUO(uint256 gas1, uint256 gas2, uint256 gas3, uint256 gasPrice, bytes memory callData) + internal + view + returns (PackedUserOperation memory uo) + { + uo = PackedUserOperation({ + sender: address(acct), + nonce: 0, + initCode: "", + callData: abi.encodePacked(UpgradeableModularAccount.executeUserOp.selector, callData), + accountGasLimits: bytes32(bytes16(uint128(gas1))) | bytes32(uint256(gas2)), + preVerificationGas: gas3, + gasFees: bytes32(uint256(uint128(gasPrice))), + paymasterAndData: "", + signature: abi.encodePacked(FunctionReferenceLib.pack(address(validationPlugin), 0), uint8(1)) + }); + } + + function test_userOp_gasLimit() public { + vm.startPrank(address(entryPoint)); + + // uses 10e - 200000 of gas + assertEq(plugin.limits(address(acct), 0), 10 ether); + uint256 result = acct.validateUserOp( + _getPackedUO(100000, 100000, 10 ether - 400000, 1, _getExecuteWithValue(0)), bytes32(0), 0 + ); + assertEq(plugin.limits(address(acct), 0), 200000); + + uint256 expected = uint256(type(uint48).max) << 160; + assertEq(result, expected); + + // uses 200k + 1 wei of gas + vm.expectRevert(NativeTokenLimitPlugin.ExceededNativeTokenLimit.selector); + result = acct.validateUserOp(_getPackedUO(100000, 100000, 1, 1, _getExecuteWithValue(0)), bytes32(0), 0); + } + + function test_userOp_executeLimit() public { + vm.startPrank(address(entryPoint)); + + // uses 5e of native tokens + assertEq(plugin.limits(address(acct), 0), 10 ether); + acct.executeUserOp(_getPackedUO(0, 0, 0, 0, _getExecuteWithValue(5 ether)), bytes32(0)); + assertEq(plugin.limits(address(acct), 0), 5 ether); + + // uses 5e + 1wei of native tokens + vm.expectRevert( + abi.encodePacked( + UpgradeableModularAccount.PreExecHookReverted.selector, + abi.encode( + address(plugin), + uint8(0), + abi.encodePacked(NativeTokenLimitPlugin.ExceededNativeTokenLimit.selector) + ) + ) + ); + acct.executeUserOp(_getPackedUO(0, 0, 0, 0, _getExecuteWithValue(5 ether + 1)), bytes32(0)); + } + + function test_userOp_executeBatchLimit() public { + Call[] memory calls = new Call[](3); + calls[0] = Call({target: recipient, value: 1, data: ""}); + calls[1] = Call({target: recipient, value: 1 ether, data: ""}); + calls[2] = Call({target: recipient, value: 5 ether + 100000, data: ""}); + + vm.startPrank(address(entryPoint)); + assertEq(plugin.limits(address(acct), 0), 10 ether); + acct.executeUserOp( + _getPackedUO(0, 0, 0, 0, abi.encodeCall(IStandardExecutor.executeBatch, (calls))), bytes32(0) + ); + assertEq(plugin.limits(address(acct), 0), 10 ether - 6 ether - 100001); + assertEq(recipient.balance, 6 ether + 100001); + } + + function test_userOp_combinedExecLimit_success() public { + assertEq(plugin.limits(address(acct), 0), 10 ether); + PackedUserOperation[] memory uos = new PackedUserOperation[](1); + uos[0] = _getPackedUO(100000, 100000, 100000, 1, _getExecuteWithValue(5 ether)); + entryPoint.handleOps(uos, bundler); + + assertEq(plugin.limits(address(acct), 0), 5 ether - 300000); + assertEq(recipient.balance, 5 ether); + } + + function test_userOp_combinedExecBatchLimit_success() public { + Call[] memory calls = new Call[](3); + calls[0] = Call({target: recipient, value: 1, data: ""}); + calls[1] = Call({target: recipient, value: 1 ether, data: ""}); + calls[2] = Call({target: recipient, value: 5 ether + 100000, data: ""}); + + vm.startPrank(address(entryPoint)); + assertEq(plugin.limits(address(acct), 0), 10 ether); + PackedUserOperation[] memory uos = new PackedUserOperation[](1); + uos[0] = _getPackedUO(200000, 200000, 200000, 1, abi.encodeCall(IStandardExecutor.executeBatch, (calls))); + entryPoint.handleOps(uos, bundler); + + assertEq(plugin.limits(address(acct), 0), 10 ether - 6 ether - 700001); + assertEq(recipient.balance, 6 ether + 100001); + } + + function test_userOp_combinedExecLimit_failExec() public { + assertEq(plugin.limits(address(acct), 0), 10 ether); + PackedUserOperation[] memory uos = new PackedUserOperation[](1); + uos[0] = _getPackedUO(100000, 100000, 100000, 1, _getExecuteWithValue(10 ether)); + entryPoint.handleOps(uos, bundler); + + assertEq(plugin.limits(address(acct), 0), 10 ether - 300000); + assertEq(recipient.balance, 0); + } + + function test_runtime_executeLimit() public { + assertEq(plugin.limits(address(acct), 0), 10 ether); + acct.executeWithAuthorization( + _getExecuteWithValue(5 ether), abi.encodePacked(validationFunction, uint8(1)) + ); + assertEq(plugin.limits(address(acct), 0), 5 ether); + } + + function test_runtime_executeBatchLimit() public { + Call[] memory calls = new Call[](3); + calls[0] = Call({target: recipient, value: 1, data: ""}); + calls[1] = Call({target: recipient, value: 1 ether, data: ""}); + calls[2] = Call({target: recipient, value: 5 ether + 100000, data: ""}); + + assertEq(plugin.limits(address(acct), 0), 10 ether); + acct.executeWithAuthorization( + abi.encodeCall(IStandardExecutor.executeBatch, (calls)), abi.encodePacked(validationFunction, uint8(1)) + ); + assertEq(plugin.limits(address(acct), 0), 4 ether - 100001); + } +} From 8e7e33df69c604c3deeb45561394bbde272cff65 Mon Sep 17 00:00:00 2001 From: howydev <132113803+howydev@users.noreply.github.com> Date: Thu, 27 Jun 2024 15:23:25 -0400 Subject: [PATCH 034/111] chore: update --- src/plugins/NativeTokenLimitPlugin.sol | 37 ++++++++++++++---------- test/plugin/NativeTokenLimitPlugin.t.sol | 3 +- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/plugins/NativeTokenLimitPlugin.sol b/src/plugins/NativeTokenLimitPlugin.sol index 5127f18a..19a192d3 100644 --- a/src/plugins/NativeTokenLimitPlugin.sol +++ b/src/plugins/NativeTokenLimitPlugin.sol @@ -4,12 +4,12 @@ pragma solidity ^0.8.25; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {UserOperationLib} from "@eth-infinitism/account-abstraction/core/UserOperationLib.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import {IAccountExecute} from "@eth-infinitism/account-abstraction/interfaces/IAccountExecute.sol"; import {PluginManifest, PluginMetadata} from "../interfaces/IPlugin.sol"; import {IStandardExecutor, Call} from "../interfaces/IStandardExecutor.sol"; import {IPlugin} from "../interfaces/IPlugin.sol"; import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; +import {IPermissionHook} from "../interfaces/IPermissionHook.sol"; import {IValidationHook} from "../interfaces/IValidationHook.sol"; import {BasePlugin, IERC165} from "./BasePlugin.sol"; @@ -18,7 +18,7 @@ import {BasePlugin, IERC165} from "./BasePlugin.sol"; /// @notice This plugin supports a single total native token spend limit. /// It tracks a total spend limit across UserOperation gas limits and native token transfers. /// If a paymaster is used, UO gas would not cause the limit to decrease. -contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { +contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IPermissionHook, IValidationHook { using UserOperationLib for PackedUserOperation; using EnumerableSet for EnumerableSet.Bytes32Set; @@ -36,25 +36,30 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { } /// @inheritdoc IExecutionHook - function preExecutionHook(uint8 functionId, bytes calldata data) external override returns (bytes memory) { - bytes calldata callData; - bytes4 execSelector; - - // TODO: plugins should never have to do these gymnastics - execSelector = bytes4(data[52:56]); - if (execSelector == IAccountExecute.executeUserOp.selector) { - callData = data[56:]; - execSelector = bytes4(callData); - } else { - callData = data[52:]; - } + function preExecutionHook(uint8 functionId, address, uint256, bytes calldata data) + external + override + returns (bytes memory) + { + return _checkAndDecrementLimit(functionId, data); + } + + function preUserOpExecutionHook(uint8 functionId, PackedUserOperation calldata uo) + external + override + returns (bytes memory) + { + return _checkAndDecrementLimit(functionId, uo.callData); + } + function _checkAndDecrementLimit(uint8 functionId, bytes calldata data) internal returns (bytes memory) { + bytes4 execSelector = bytes4(data); uint256 value; // Get value being sent if (execSelector == IStandardExecutor.execute.selector) { - value = uint256(bytes32(callData[36:68])); + value = uint256(bytes32(data[36:68])); } else if (execSelector == IStandardExecutor.executeBatch.selector) { - Call[] memory calls = abi.decode(callData[4:], (Call[])); + Call[] memory calls = abi.decode(data[4:], (Call[])); for (uint256 i = 0; i < calls.length; i++) { value += calls[i].value; } diff --git a/test/plugin/NativeTokenLimitPlugin.t.sol b/test/plugin/NativeTokenLimitPlugin.t.sol index 4284d36e..81b8f7ea 100644 --- a/test/plugin/NativeTokenLimitPlugin.t.sol +++ b/test/plugin/NativeTokenLimitPlugin.t.sol @@ -44,8 +44,7 @@ contract NativeTokenLimitPluginTest is OptimizedTest { permissionHooks[0] = ExecutionHook({ hookFunction: FunctionReferenceLib.pack(address(plugin), 0), isPreHook: true, - isPostHook: false, - requireUOContext: false + isPostHook: false }); uint256[] memory spendLimits = new uint256[](1); From 953bc11aad49d9c811805bbe1b8d46ee5228c5cb Mon Sep 17 00:00:00 2001 From: howydev <132113803+howydev@users.noreply.github.com> Date: Thu, 27 Jun 2024 15:42:51 -0400 Subject: [PATCH 035/111] fix: tests --- test/plugin/NativeTokenLimitPlugin.t.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/plugin/NativeTokenLimitPlugin.t.sol b/test/plugin/NativeTokenLimitPlugin.t.sol index 81b8f7ea..07a554d2 100644 --- a/test/plugin/NativeTokenLimitPlugin.t.sol +++ b/test/plugin/NativeTokenLimitPlugin.t.sol @@ -149,10 +149,10 @@ contract NativeTokenLimitPluginTest is OptimizedTest { function test_userOp_combinedExecLimit_success() public { assertEq(plugin.limits(address(acct), 0), 10 ether); PackedUserOperation[] memory uos = new PackedUserOperation[](1); - uos[0] = _getPackedUO(100000, 100000, 100000, 1, _getExecuteWithValue(5 ether)); + uos[0] = _getPackedUO(200000, 200000, 200000, 1, _getExecuteWithValue(5 ether)); entryPoint.handleOps(uos, bundler); - assertEq(plugin.limits(address(acct), 0), 5 ether - 300000); + assertEq(plugin.limits(address(acct), 0), 5 ether - 600000); assertEq(recipient.balance, 5 ether); } @@ -175,10 +175,10 @@ contract NativeTokenLimitPluginTest is OptimizedTest { function test_userOp_combinedExecLimit_failExec() public { assertEq(plugin.limits(address(acct), 0), 10 ether); PackedUserOperation[] memory uos = new PackedUserOperation[](1); - uos[0] = _getPackedUO(100000, 100000, 100000, 1, _getExecuteWithValue(10 ether)); + uos[0] = _getPackedUO(200000, 200000, 200000, 1, _getExecuteWithValue(10 ether)); entryPoint.handleOps(uos, bundler); - assertEq(plugin.limits(address(acct), 0), 10 ether - 300000); + assertEq(plugin.limits(address(acct), 0), 10 ether - 600000); assertEq(recipient.balance, 0); } From 75d245e9bf3eb69f0e81f752101edf3f493a85ad Mon Sep 17 00:00:00 2001 From: howydev <132113803+howydev@users.noreply.github.com> Date: Fri, 28 Jun 2024 16:50:04 -0400 Subject: [PATCH 036/111] chore: remove permission hooks, add new internal helper function for devX, fix lint --- src/plugins/BasePlugin.sol | 24 +++++++ src/plugins/NativeTokenLimitPlugin.sol | 99 ++++++++++++-------------- 2 files changed, 70 insertions(+), 53 deletions(-) diff --git a/src/plugins/BasePlugin.sol b/src/plugins/BasePlugin.sol index 3b0fd521..18345b14 100644 --- a/src/plugins/BasePlugin.sol +++ b/src/plugins/BasePlugin.sol @@ -2,6 +2,8 @@ pragma solidity ^0.8.25; import {ERC165, IERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +import {IAccountExecute} from "@eth-infinitism/account-abstraction/interfaces/IAccountExecute.sol"; +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {IPlugin} from "../interfaces/IPlugin.sol"; @@ -27,4 +29,26 @@ abstract contract BasePlugin is ERC165, IPlugin { function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { return interfaceId == type(IPlugin).interfaceId || super.supportsInterface(interfaceId); } + + function _getSelectorAndCalldata(bytes calldata data) internal view returns (bytes4, bytes memory) { + if (bytes4(data[:4]) == IAccountExecute.executeUserOp.selector) { + (PackedUserOperation memory uo,) = abi.decode(data[4:], (PackedUserOperation, bytes32)); + bytes4 selector; + bytes memory callData = uo.callData; + // Bytes arr representation: [bytes32(len), bytes4(executeUserOp.selector), bytes4(actualSelector), + // bytes(actualCallData)] + // 1. Copy actualSelector into a new var + // 2. Shorten bytes arry by 8 by: store length - 8 into the new pointer location + // 3. Move the callData pointer by 8 + assembly { + selector := mload(add(callData, 36)) + + let len := mload(callData) + mstore(add(callData, 8), sub(len, 8)) + callData := add(callData, 8) + } + return (selector, callData); + } + return (bytes4(data[:4]), data[4:]); + } } diff --git a/src/plugins/NativeTokenLimitPlugin.sol b/src/plugins/NativeTokenLimitPlugin.sol index 19a192d3..5fd806d6 100644 --- a/src/plugins/NativeTokenLimitPlugin.sol +++ b/src/plugins/NativeTokenLimitPlugin.sol @@ -9,7 +9,6 @@ import {PluginManifest, PluginMetadata} from "../interfaces/IPlugin.sol"; import {IStandardExecutor, Call} from "../interfaces/IStandardExecutor.sol"; import {IPlugin} from "../interfaces/IPlugin.sol"; import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; -import {IPermissionHook} from "../interfaces/IPermissionHook.sol"; import {IValidationHook} from "../interfaces/IValidationHook.sol"; import {BasePlugin, IERC165} from "./BasePlugin.sol"; @@ -18,7 +17,8 @@ import {BasePlugin, IERC165} from "./BasePlugin.sol"; /// @notice This plugin supports a single total native token spend limit. /// It tracks a total spend limit across UserOperation gas limits and native token transfers. /// If a paymaster is used, UO gas would not cause the limit to decrease. -contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IPermissionHook, IValidationHook { + +contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { using UserOperationLib for PackedUserOperation; using EnumerableSet for EnumerableSet.Bytes32Set; @@ -35,57 +35,6 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IPermissionHook, limits[msg.sender][functionId] = newLimit; } - /// @inheritdoc IExecutionHook - function preExecutionHook(uint8 functionId, address, uint256, bytes calldata data) - external - override - returns (bytes memory) - { - return _checkAndDecrementLimit(functionId, data); - } - - function preUserOpExecutionHook(uint8 functionId, PackedUserOperation calldata uo) - external - override - returns (bytes memory) - { - return _checkAndDecrementLimit(functionId, uo.callData); - } - - function _checkAndDecrementLimit(uint8 functionId, bytes calldata data) internal returns (bytes memory) { - bytes4 execSelector = bytes4(data); - uint256 value; - // Get value being sent - if (execSelector == IStandardExecutor.execute.selector) { - value = uint256(bytes32(data[36:68])); - } else if (execSelector == IStandardExecutor.executeBatch.selector) { - Call[] memory calls = abi.decode(data[4:], (Call[])); - for (uint256 i = 0; i < calls.length; i++) { - value += calls[i].value; - } - } - - uint256 limit = limits[msg.sender][functionId]; - if (value > limit) { - revert ExceededNativeTokenLimit(); - } - limits[msg.sender][functionId] = limit - value; - - return ""; - } - - /// @inheritdoc IExecutionHook - function postExecutionHook(uint8, bytes calldata) external pure override { - revert NotImplemented(); - } - - // No implementation, no revert - // Runtime spends no account gas, and we check native token spend limits in exec hooks - function preRuntimeValidationHook(uint8 functionId, address, uint256, bytes calldata) external pure override { - // silence warnings - (functionId); - } - /// @inheritdoc IValidationHook function preUserOpValidationHook(uint8 functionId, PackedUserOperation calldata userOp, bytes32) external @@ -107,6 +56,15 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IPermissionHook, return 0; } + /// @inheritdoc IExecutionHook + function preExecutionHook(uint8 functionId, address, uint256, bytes calldata data) + external + override + returns (bytes memory) + { + return _checkAndDecrementLimit(functionId, data); + } + /// @inheritdoc IPlugin function onInstall(bytes calldata data) external override { uint256[] memory spendLimits = abi.decode(data, (uint256[])); @@ -126,6 +84,18 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IPermissionHook, delete limits[msg.sender][functionId]; } + /// @inheritdoc IExecutionHook + function postExecutionHook(uint8, bytes calldata) external pure override { + revert NotImplemented(); + } + + // No implementation, no revert + // Runtime spends no account gas, and we check native token spend limits in exec hooks + function preRuntimeValidationHook(uint8 functionId, address, uint256, bytes calldata) external pure override { + // silence warnings + (functionId); + } + /// @inheritdoc IPlugin function pluginManifest() external pure override returns (PluginManifest memory) { // silence warnings @@ -154,4 +124,27 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IPermissionHook, function supportsInterface(bytes4 interfaceId) public view override(BasePlugin, IERC165) returns (bool) { return super.supportsInterface(interfaceId); } + + function _checkAndDecrementLimit(uint8 functionId, bytes calldata data) internal returns (bytes memory) { + (bytes4 selector, bytes memory callData) = _getSelectorAndCalldata(data); + + uint256 value; + // Get value being sent + if (selector == IStandardExecutor.execute.selector) { + (, value) = abi.decode(callData, (address, uint256)); + } else if (selector == IStandardExecutor.executeBatch.selector) { + Call[] memory calls = abi.decode(callData, (Call[])); + for (uint256 i = 0; i < calls.length; i++) { + value += calls[i].value; + } + } + + uint256 limit = limits[msg.sender][functionId]; + if (value > limit) { + revert ExceededNativeTokenLimit(); + } + limits[msg.sender][functionId] = limit - value; + + return ""; + } } From c2d0910e7e65e4226ff5e5a8e1706c7c35da480f Mon Sep 17 00:00:00 2001 From: howydev <132113803+howydev@users.noreply.github.com> Date: Mon, 8 Jul 2024 13:34:38 -0400 Subject: [PATCH 037/111] fix: move to associated storage --- src/plugins/NativeTokenLimitPlugin.sol | 27 ++++++++++--------- test/plugin/NativeTokenLimitPlugin.t.sol | 34 ++++++++++++------------ 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/src/plugins/NativeTokenLimitPlugin.sol b/src/plugins/NativeTokenLimitPlugin.sol index 5fd806d6..984b373b 100644 --- a/src/plugins/NativeTokenLimitPlugin.sol +++ b/src/plugins/NativeTokenLimitPlugin.sol @@ -26,13 +26,13 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { string public constant VERSION = "1.0.0"; string public constant AUTHOR = "ERC-6900 Authors"; - mapping(address account => uint256[] limits) public limits; + mapping(uint256 funcIds => mapping(address account => uint256 limit)) public limits; error ExceededNativeTokenLimit(); error ExceededNumberOfEntities(); function updateLimits(uint8 functionId, uint256 newLimit) external { - limits[msg.sender][functionId] = newLimit; + limits[functionId][msg.sender] = newLimit; } /// @inheritdoc IValidationHook @@ -47,11 +47,11 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { uint256 totalGas = userOp.preVerificationGas + vgl + cgl; uint256 usage = totalGas * UserOperationLib.unpackMaxFeePerGas(userOp); - uint256 limit = limits[msg.sender][functionId]; + uint256 limit = limits[functionId][msg.sender]; if (usage > limit) { revert ExceededNativeTokenLimit(); } - limits[msg.sender][functionId] = limit - usage; + limits[functionId][msg.sender] = limit - usage; } return 0; } @@ -67,21 +67,24 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { /// @inheritdoc IPlugin function onInstall(bytes calldata data) external override { - uint256[] memory spendLimits = abi.decode(data, (uint256[])); + (uint8 startFunctionId, uint256[] memory spendLimits) = abi.decode(data, (uint8, uint256[])); - for (uint256 i = 0; i < spendLimits.length; i++) { - limits[msg.sender].push(spendLimits[i]); + if (startFunctionId + spendLimits.length > type(uint8).max) { + revert ExceededNumberOfEntities(); } - if (limits[msg.sender].length > type(uint8).max) { - revert ExceededNumberOfEntities(); + for (uint256 i = 0; i < spendLimits.length; i++) { + limits[i + startFunctionId][msg.sender] = spendLimits[i]; } } /// @inheritdoc IPlugin function onUninstall(bytes calldata data) external override { + // This is the highest functionId that's being used by the account uint8 functionId = abi.decode(data, (uint8)); - delete limits[msg.sender][functionId]; + for (uint256 i = 0; i < functionId; i++) { + delete limits[i][msg.sender]; + } } /// @inheritdoc IExecutionHook @@ -139,11 +142,11 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { } } - uint256 limit = limits[msg.sender][functionId]; + uint256 limit = limits[functionId][msg.sender]; if (value > limit) { revert ExceededNativeTokenLimit(); } - limits[msg.sender][functionId] = limit - value; + limits[functionId][msg.sender] = limit - value; return ""; } diff --git a/test/plugin/NativeTokenLimitPlugin.t.sol b/test/plugin/NativeTokenLimitPlugin.t.sol index 07a554d2..b8072238 100644 --- a/test/plugin/NativeTokenLimitPlugin.t.sol +++ b/test/plugin/NativeTokenLimitPlugin.t.sol @@ -54,7 +54,7 @@ contract NativeTokenLimitPluginTest is OptimizedTest { preValHooksInitDatas[0] = ""; bytes[] memory permissionInitDatas = new bytes[](1); - permissionInitDatas[0] = abi.encode(spendLimits); + permissionInitDatas[0] = abi.encode(0, spendLimits); vm.prank(address(acct)); acct.installValidation( @@ -95,11 +95,11 @@ contract NativeTokenLimitPluginTest is OptimizedTest { vm.startPrank(address(entryPoint)); // uses 10e - 200000 of gas - assertEq(plugin.limits(address(acct), 0), 10 ether); + assertEq(plugin.limits(0, address(acct)), 10 ether); uint256 result = acct.validateUserOp( _getPackedUO(100000, 100000, 10 ether - 400000, 1, _getExecuteWithValue(0)), bytes32(0), 0 ); - assertEq(plugin.limits(address(acct), 0), 200000); + assertEq(plugin.limits(0, address(acct)), 200000); uint256 expected = uint256(type(uint48).max) << 160; assertEq(result, expected); @@ -113,9 +113,9 @@ contract NativeTokenLimitPluginTest is OptimizedTest { vm.startPrank(address(entryPoint)); // uses 5e of native tokens - assertEq(plugin.limits(address(acct), 0), 10 ether); + assertEq(plugin.limits(0, address(acct)), 10 ether); acct.executeUserOp(_getPackedUO(0, 0, 0, 0, _getExecuteWithValue(5 ether)), bytes32(0)); - assertEq(plugin.limits(address(acct), 0), 5 ether); + assertEq(plugin.limits(0, address(acct)), 5 ether); // uses 5e + 1wei of native tokens vm.expectRevert( @@ -138,21 +138,21 @@ contract NativeTokenLimitPluginTest is OptimizedTest { calls[2] = Call({target: recipient, value: 5 ether + 100000, data: ""}); vm.startPrank(address(entryPoint)); - assertEq(plugin.limits(address(acct), 0), 10 ether); + assertEq(plugin.limits(0, address(acct)), 10 ether); acct.executeUserOp( _getPackedUO(0, 0, 0, 0, abi.encodeCall(IStandardExecutor.executeBatch, (calls))), bytes32(0) ); - assertEq(plugin.limits(address(acct), 0), 10 ether - 6 ether - 100001); + assertEq(plugin.limits(0, address(acct)), 10 ether - 6 ether - 100001); assertEq(recipient.balance, 6 ether + 100001); } function test_userOp_combinedExecLimit_success() public { - assertEq(plugin.limits(address(acct), 0), 10 ether); + assertEq(plugin.limits(0, address(acct)), 10 ether); PackedUserOperation[] memory uos = new PackedUserOperation[](1); uos[0] = _getPackedUO(200000, 200000, 200000, 1, _getExecuteWithValue(5 ether)); entryPoint.handleOps(uos, bundler); - assertEq(plugin.limits(address(acct), 0), 5 ether - 600000); + assertEq(plugin.limits(0, address(acct)), 5 ether - 600000); assertEq(recipient.balance, 5 ether); } @@ -163,31 +163,31 @@ contract NativeTokenLimitPluginTest is OptimizedTest { calls[2] = Call({target: recipient, value: 5 ether + 100000, data: ""}); vm.startPrank(address(entryPoint)); - assertEq(plugin.limits(address(acct), 0), 10 ether); + assertEq(plugin.limits(0, address(acct)), 10 ether); PackedUserOperation[] memory uos = new PackedUserOperation[](1); uos[0] = _getPackedUO(200000, 200000, 200000, 1, abi.encodeCall(IStandardExecutor.executeBatch, (calls))); entryPoint.handleOps(uos, bundler); - assertEq(plugin.limits(address(acct), 0), 10 ether - 6 ether - 700001); + assertEq(plugin.limits(0, address(acct)), 10 ether - 6 ether - 700001); assertEq(recipient.balance, 6 ether + 100001); } function test_userOp_combinedExecLimit_failExec() public { - assertEq(plugin.limits(address(acct), 0), 10 ether); + assertEq(plugin.limits(0, address(acct)), 10 ether); PackedUserOperation[] memory uos = new PackedUserOperation[](1); uos[0] = _getPackedUO(200000, 200000, 200000, 1, _getExecuteWithValue(10 ether)); entryPoint.handleOps(uos, bundler); - assertEq(plugin.limits(address(acct), 0), 10 ether - 600000); + assertEq(plugin.limits(0, address(acct)), 10 ether - 600000); assertEq(recipient.balance, 0); } function test_runtime_executeLimit() public { - assertEq(plugin.limits(address(acct), 0), 10 ether); + assertEq(plugin.limits(0, address(acct)), 10 ether); acct.executeWithAuthorization( _getExecuteWithValue(5 ether), abi.encodePacked(validationFunction, uint8(1)) ); - assertEq(plugin.limits(address(acct), 0), 5 ether); + assertEq(plugin.limits(0, address(acct)), 5 ether); } function test_runtime_executeBatchLimit() public { @@ -196,10 +196,10 @@ contract NativeTokenLimitPluginTest is OptimizedTest { calls[1] = Call({target: recipient, value: 1 ether, data: ""}); calls[2] = Call({target: recipient, value: 5 ether + 100000, data: ""}); - assertEq(plugin.limits(address(acct), 0), 10 ether); + assertEq(plugin.limits(0, address(acct)), 10 ether); acct.executeWithAuthorization( abi.encodeCall(IStandardExecutor.executeBatch, (calls)), abi.encodePacked(validationFunction, uint8(1)) ); - assertEq(plugin.limits(address(acct), 0), 4 ether - 100001); + assertEq(plugin.limits(0, address(acct)), 4 ether - 100001); } } From 2743097e27c22d685c1627a4901455c39d56ca38 Mon Sep 17 00:00:00 2001 From: howydev <132113803+howydev@users.noreply.github.com> Date: Mon, 8 Jul 2024 13:38:44 -0400 Subject: [PATCH 038/111] feat: add special paymaster list to still count towards limits --- src/plugins/BasePlugin.sol | 2 +- src/plugins/NativeTokenLimitPlugin.sol | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/plugins/BasePlugin.sol b/src/plugins/BasePlugin.sol index 18345b14..f5c8523b 100644 --- a/src/plugins/BasePlugin.sol +++ b/src/plugins/BasePlugin.sol @@ -30,7 +30,7 @@ abstract contract BasePlugin is ERC165, IPlugin { return interfaceId == type(IPlugin).interfaceId || super.supportsInterface(interfaceId); } - function _getSelectorAndCalldata(bytes calldata data) internal view returns (bytes4, bytes memory) { + function _getSelectorAndCalldata(bytes calldata data) internal pure returns (bytes4, bytes memory) { if (bytes4(data[:4]) == IAccountExecute.executeUserOp.selector) { (PackedUserOperation memory uo,) = abi.decode(data[4:], (PackedUserOperation, bytes32)); bytes4 selector; diff --git a/src/plugins/NativeTokenLimitPlugin.sol b/src/plugins/NativeTokenLimitPlugin.sol index 984b373b..777484ce 100644 --- a/src/plugins/NativeTokenLimitPlugin.sol +++ b/src/plugins/NativeTokenLimitPlugin.sol @@ -16,8 +16,8 @@ import {BasePlugin, IERC165} from "./BasePlugin.sol"; /// @author ERC-6900 Authors /// @notice This plugin supports a single total native token spend limit. /// It tracks a total spend limit across UserOperation gas limits and native token transfers. -/// If a paymaster is used, UO gas would not cause the limit to decrease. - +/// If a non whitelisted paymaster is used, UO gas would not cause the limit to decrease. +/// If a whitelisted paymaster is used, gas is still counted towards the limit contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { using UserOperationLib for PackedUserOperation; using EnumerableSet for EnumerableSet.Bytes32Set; @@ -27,6 +27,9 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { string public constant AUTHOR = "ERC-6900 Authors"; mapping(uint256 funcIds => mapping(address account => uint256 limit)) public limits; + // Accounts should add paymasters that still use the accounts tokens here + // E.g. ERC20 paymasters that pull funds from the account + mapping(address paymaster => mapping(address account => bool allowed)) public specialPaymasters; error ExceededNativeTokenLimit(); error ExceededNumberOfEntities(); @@ -35,13 +38,20 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { limits[functionId][msg.sender] = newLimit; } + function updateSpecialPaymaster(address paymaster, bool allowed) external { + specialPaymasters[paymaster][msg.sender] = allowed; + } + /// @inheritdoc IValidationHook function preUserOpValidationHook(uint8 functionId, PackedUserOperation calldata userOp, bytes32) external returns (uint256) { - // Decrease limit only if no paymaster is used - if (userOp.paymasterAndData.length == 0) { + // Decrease limit only if no paymaster is used, or if its a special paymaster + if ( + userOp.paymasterAndData.length == 0 + || specialPaymasters[address(bytes20(userOp.paymasterAndData[:20]))][msg.sender] + ) { uint256 vgl = UserOperationLib.unpackVerificationGasLimit(userOp); uint256 cgl = UserOperationLib.unpackCallGasLimit(userOp); uint256 totalGas = userOp.preVerificationGas + vgl + cgl; From a19fcda20b525408849369f6fdb920db9145d16f Mon Sep 17 00:00:00 2001 From: howydev <132113803+howydev@users.noreply.github.com> Date: Wed, 10 Jul 2024 13:57:13 -0400 Subject: [PATCH 039/111] c --- src/plugins/NativeTokenLimitPlugin.sol | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/plugins/NativeTokenLimitPlugin.sol b/src/plugins/NativeTokenLimitPlugin.sol index 777484ce..cb84a4ee 100644 --- a/src/plugins/NativeTokenLimitPlugin.sol +++ b/src/plugins/NativeTokenLimitPlugin.sol @@ -22,9 +22,9 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { using UserOperationLib for PackedUserOperation; using EnumerableSet for EnumerableSet.Bytes32Set; - string public constant NAME = "Native Token Limit"; - string public constant VERSION = "1.0.0"; - string public constant AUTHOR = "ERC-6900 Authors"; + string internal constant NAME = "Native Token Limit"; + string internal constant VERSION = "1.0.0"; + string internal constant AUTHOR = "ERC-6900 Authors"; mapping(uint256 funcIds => mapping(address account => uint256 limit)) public limits; // Accounts should add paymasters that still use the accounts tokens here @@ -104,10 +104,7 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { // No implementation, no revert // Runtime spends no account gas, and we check native token spend limits in exec hooks - function preRuntimeValidationHook(uint8 functionId, address, uint256, bytes calldata) external pure override { - // silence warnings - (functionId); - } + function preRuntimeValidationHook(uint8, address, uint256, bytes calldata) external pure override {} /// @inheritdoc IPlugin function pluginManifest() external pure override returns (PluginManifest memory) { @@ -135,7 +132,7 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { /// @inheritdoc BasePlugin function supportsInterface(bytes4 interfaceId) public view override(BasePlugin, IERC165) returns (bool) { - return super.supportsInterface(interfaceId); + return interfaceId == type(IExecutionHook).interfaceId || super.supportsInterface(interfaceId); } function _checkAndDecrementLimit(uint8 functionId, bytes calldata data) internal returns (bytes memory) { From 162131c78a97660eec2afdf1eecfcdeceb4a5af1 Mon Sep 17 00:00:00 2001 From: howydev <132113803+howydev@users.noreply.github.com> Date: Wed, 10 Jul 2024 14:00:33 -0400 Subject: [PATCH 040/111] fix: lint --- src/plugins/NativeTokenLimitPlugin.sol | 28 +++++++++++++++----------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/plugins/NativeTokenLimitPlugin.sol b/src/plugins/NativeTokenLimitPlugin.sol index cb84a4ee..58d10a9b 100644 --- a/src/plugins/NativeTokenLimitPlugin.sol +++ b/src/plugins/NativeTokenLimitPlugin.sol @@ -22,9 +22,9 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { using UserOperationLib for PackedUserOperation; using EnumerableSet for EnumerableSet.Bytes32Set; - string internal constant NAME = "Native Token Limit"; - string internal constant VERSION = "1.0.0"; - string internal constant AUTHOR = "ERC-6900 Authors"; + string internal constant _NAME = "Native Token Limit"; + string internal constant _VERSION = "1.0.0"; + string internal constant _AUTHOR = "ERC-6900 Authors"; mapping(uint256 funcIds => mapping(address account => uint256 limit)) public limits; // Accounts should add paymasters that still use the accounts tokens here @@ -54,7 +54,13 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { ) { uint256 vgl = UserOperationLib.unpackVerificationGasLimit(userOp); uint256 cgl = UserOperationLib.unpackCallGasLimit(userOp); - uint256 totalGas = userOp.preVerificationGas + vgl + cgl; + uint256 pvgl; + uint256 ppogl; + if (userOp.paymasterAndData.length > 0) { + // Can skip the EP length check here since it would have reverted there if it was invalid + (, pvgl, ppogl) = UserOperationLib.unpackPaymasterStaticFields(userOp.paymasterAndData); + } + uint256 totalGas = userOp.preVerificationGas + vgl + cgl + pvgl + ppogl; uint256 usage = totalGas * UserOperationLib.unpackMaxFeePerGas(userOp); uint256 limit = limits[functionId][msg.sender]; @@ -104,21 +110,19 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { // No implementation, no revert // Runtime spends no account gas, and we check native token spend limits in exec hooks + // solhint-disable-next-line no-empty-blocks function preRuntimeValidationHook(uint8, address, uint256, bytes calldata) external pure override {} /// @inheritdoc IPlugin - function pluginManifest() external pure override returns (PluginManifest memory) { - // silence warnings - PluginManifest memory manifest; - return manifest; - } + // solhint-disable-next-line no-empty-blocks + function pluginManifest() external pure override returns (PluginManifest memory) {} /// @inheritdoc IPlugin function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { PluginMetadata memory metadata; - metadata.name = NAME; - metadata.version = VERSION; - metadata.author = AUTHOR; + metadata.name = _NAME; + metadata.version = _VERSION; + metadata.author = _AUTHOR; metadata.permissionRequest = new string[](2); metadata.permissionRequest[0] = "native-token-limit"; From 1d38111853d3a86aa78624d4b5681df335a2f67b Mon Sep 17 00:00:00 2001 From: howydev <132113803+howydev@users.noreply.github.com> Date: Wed, 10 Jul 2024 19:55:09 -0400 Subject: [PATCH 041/111] fix: lint, test --- src/plugins/NativeTokenLimitPlugin.sol | 7 +++++-- test/plugin/NativeTokenLimitPlugin.t.sol | 14 +++++--------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/plugins/NativeTokenLimitPlugin.sol b/src/plugins/NativeTokenLimitPlugin.sol index 58d10a9b..2b512c22 100644 --- a/src/plugins/NativeTokenLimitPlugin.sol +++ b/src/plugins/NativeTokenLimitPlugin.sol @@ -110,8 +110,11 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { // No implementation, no revert // Runtime spends no account gas, and we check native token spend limits in exec hooks - // solhint-disable-next-line no-empty-blocks - function preRuntimeValidationHook(uint8, address, uint256, bytes calldata) external pure override {} + function preRuntimeValidationHook(uint8, address, uint256, bytes calldata, bytes calldata) + external + pure + override + {} // solhint-disable-line no-empty-blocks /// @inheritdoc IPlugin // solhint-disable-next-line no-empty-blocks diff --git a/test/plugin/NativeTokenLimitPlugin.t.sol b/test/plugin/NativeTokenLimitPlugin.t.sol index b8072238..bfc16b33 100644 --- a/test/plugin/NativeTokenLimitPlugin.t.sol +++ b/test/plugin/NativeTokenLimitPlugin.t.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.sol"; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; @@ -14,10 +13,9 @@ import {IStandardExecutor, Call} from "../../src/interfaces/IStandardExecutor.so import {PluginManifest} from "../../src/interfaces/IPlugin.sol"; import {MSCAFactoryFixture} from "../mocks/MSCAFactoryFixture.sol"; -import {OptimizedTest} from "../utils/OptimizedTest.sol"; +import {AccountTestBase} from "../utils/AccountTestBase.sol"; -contract NativeTokenLimitPluginTest is OptimizedTest { - EntryPoint public entryPoint = new EntryPoint(); +contract NativeTokenLimitPluginTest is AccountTestBase { address public recipient = address(1); address payable public bundler = payable(address(2)); PluginManifest internal _m; @@ -87,7 +85,7 @@ contract NativeTokenLimitPluginTest is OptimizedTest { preVerificationGas: gas3, gasFees: bytes32(uint256(uint128(gasPrice))), paymasterAndData: "", - signature: abi.encodePacked(FunctionReferenceLib.pack(address(validationPlugin), 0), uint8(1)) + signature: _encodeSignature(FunctionReferenceLib.pack(address(validationPlugin), 0), 1, "") }); } @@ -184,9 +182,7 @@ contract NativeTokenLimitPluginTest is OptimizedTest { function test_runtime_executeLimit() public { assertEq(plugin.limits(0, address(acct)), 10 ether); - acct.executeWithAuthorization( - _getExecuteWithValue(5 ether), abi.encodePacked(validationFunction, uint8(1)) - ); + acct.executeWithAuthorization(_getExecuteWithValue(5 ether), _encodeSignature(validationFunction, 1, "")); assertEq(plugin.limits(0, address(acct)), 5 ether); } @@ -198,7 +194,7 @@ contract NativeTokenLimitPluginTest is OptimizedTest { assertEq(plugin.limits(0, address(acct)), 10 ether); acct.executeWithAuthorization( - abi.encodeCall(IStandardExecutor.executeBatch, (calls)), abi.encodePacked(validationFunction, uint8(1)) + abi.encodeCall(IStandardExecutor.executeBatch, (calls)), _encodeSignature(validationFunction, 1, "") ); assertEq(plugin.limits(0, address(acct)), 4 ether - 100001); } From 3faa350989fb2dec34fb4633b1949a0e72eb25cb Mon Sep 17 00:00:00 2001 From: howydev <132113803+howydev@users.noreply.github.com> Date: Mon, 15 Jul 2024 22:52:26 -0400 Subject: [PATCH 042/111] fix: test --- test/plugin/NativeTokenLimitPlugin.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/plugin/NativeTokenLimitPlugin.t.sol b/test/plugin/NativeTokenLimitPlugin.t.sol index bfc16b33..5fbf2a3d 100644 --- a/test/plugin/NativeTokenLimitPlugin.t.sol +++ b/test/plugin/NativeTokenLimitPlugin.t.sol @@ -11,6 +11,7 @@ import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; import {FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; import {IStandardExecutor, Call} from "../../src/interfaces/IStandardExecutor.sol"; import {PluginManifest} from "../../src/interfaces/IPlugin.sol"; +import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import {MSCAFactoryFixture} from "../mocks/MSCAFactoryFixture.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; @@ -56,8 +57,7 @@ contract NativeTokenLimitPluginTest is AccountTestBase { vm.prank(address(acct)); acct.installValidation( - FunctionReferenceLib.pack(address(validationPlugin), 0), - true, + ValidationConfigLib.pack(address(validationPlugin), 0, true, true), new bytes4[](0), new bytes(0), abi.encode(preValidationHooks, preValHooksInitDatas), From 1685e91df1edd01ab13618183331cb477ab1e560 Mon Sep 17 00:00:00 2001 From: howy <132113803+howydev@users.noreply.github.com> Date: Tue, 16 Jul 2024 16:59:00 -0700 Subject: [PATCH 043/111] [5/n permissions] feat: add erc20 token spend limit plugin (#80) Co-authored-by: fangting-alchemy <119372438+fangting-alchemy@users.noreply.github.com> --- .gitmodules | 3 + lib/modular-account-libs | 1 + remappings.txt | 1 + src/plugins/ERC20TokenLimitPlugin.sol | 154 ++++++++++++++++++++ test/mocks/MockERC20.sol | 12 ++ test/plugin/ERC20TokenLimitPlugin.t.sol | 184 ++++++++++++++++++++++++ 6 files changed, 355 insertions(+) create mode 160000 lib/modular-account-libs create mode 100644 src/plugins/ERC20TokenLimitPlugin.sol create mode 100644 test/mocks/MockERC20.sol create mode 100644 test/plugin/ERC20TokenLimitPlugin.t.sol diff --git a/.gitmodules b/.gitmodules index 813d955e..05bd137f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "lib/modular-account-libs"] + path = lib/modular-account-libs + url = https://github.com/erc6900/modular-account-libs diff --git a/lib/modular-account-libs b/lib/modular-account-libs new file mode 160000 index 00000000..5d9d0e40 --- /dev/null +++ b/lib/modular-account-libs @@ -0,0 +1 @@ +Subproject commit 5d9d0e403332251045eee2954c2a8b7ea0bae953 diff --git a/remappings.txt b/remappings.txt index 3d0ee0df..bc2ce0be 100644 --- a/remappings.txt +++ b/remappings.txt @@ -2,3 +2,4 @@ ds-test/=lib/forge-std/lib/ds-test/src/ forge-std/=lib/forge-std/src/ @eth-infinitism/account-abstraction/=lib/account-abstraction/contracts/ @openzeppelin/=lib/openzeppelin-contracts/ +@modular-account-libs/=lib/modular-account-libs/src/ \ No newline at end of file diff --git a/src/plugins/ERC20TokenLimitPlugin.sol b/src/plugins/ERC20TokenLimitPlugin.sol new file mode 100644 index 00000000..1df5bcfd --- /dev/null +++ b/src/plugins/ERC20TokenLimitPlugin.sol @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.25; + +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; +import {UserOperationLib} from "@eth-infinitism/account-abstraction/core/UserOperationLib.sol"; +import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { + SetValue, + AssociatedLinkedListSet, + AssociatedLinkedListSetLib +} from "@modular-account-libs/libraries/AssociatedLinkedListSetLib.sol"; + +import {PluginManifest, PluginMetadata} from "../interfaces/IPlugin.sol"; +import {IStandardExecutor, Call} from "../interfaces/IStandardExecutor.sol"; +import {IPlugin} from "../interfaces/IPlugin.sol"; +import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; +import {BasePlugin, IERC165} from "./BasePlugin.sol"; + +/// @title ERC20 Token Limit Plugin +/// @author ERC-6900 Authors +/// @notice This plugin supports an ERC20 token spend limit. This should be combined with a contract whitelist +/// plugin to make sure that token transfers not tracked by the plugin don't happen. +/// Note: this plugin is opinionated on what selectors can be called for token contracts to guard against weird +/// edge cases like DAI. You wouldn't be able to use uni v2 pairs directly as the pair contract is also the LP +/// token contract +contract ERC20TokenLimitPlugin is BasePlugin, IExecutionHook { + using UserOperationLib for PackedUserOperation; + using EnumerableSet for EnumerableSet.AddressSet; + using AssociatedLinkedListSetLib for AssociatedLinkedListSet; + + struct ERC20SpendLimit { + address token; + uint256[] limits; + } + + string internal constant _NAME = "ERC20 Token Limit Plugin"; + string internal constant _VERSION = "1.0.0"; + string internal constant _AUTHOR = "ERC-6900 Authors"; + + mapping(uint8 functionId => mapping(address token => mapping(address account => uint256 limit))) public limits; + AssociatedLinkedListSet internal _tokenList; + + error ExceededTokenLimit(); + error ExceededNumberOfEntities(); + error SelectorNotAllowed(); + + function updateLimits(uint8 functionId, address token, uint256 newLimit) external { + _tokenList.tryAdd(msg.sender, SetValue.wrap(bytes30(bytes20(token)))); + limits[functionId][token][msg.sender] = newLimit; + } + + /// @inheritdoc IExecutionHook + function preExecutionHook(uint8 functionId, address, uint256, bytes calldata data) + external + override + returns (bytes memory) + { + (bytes4 selector, bytes memory callData) = _getSelectorAndCalldata(data); + + if (selector == IStandardExecutor.execute.selector) { + (address token,, bytes memory innerCalldata) = abi.decode(callData, (address, uint256, bytes)); + if (_tokenList.contains(msg.sender, SetValue.wrap(bytes30(bytes20(token))))) { + _decrementLimit(functionId, token, innerCalldata); + } + } else if (selector == IStandardExecutor.executeBatch.selector) { + Call[] memory calls = abi.decode(callData, (Call[])); + for (uint256 i = 0; i < calls.length; i++) { + if (_tokenList.contains(msg.sender, SetValue.wrap(bytes30(bytes20(calls[i].target))))) { + _decrementLimit(functionId, calls[i].target, calls[i].data); + } + } + } + + return ""; + } + + /// @inheritdoc IPlugin + function onInstall(bytes calldata data) external override { + (uint8 startFunctionId, ERC20SpendLimit[] memory spendLimits) = + abi.decode(data, (uint8, ERC20SpendLimit[])); + + if (startFunctionId + spendLimits.length > type(uint8).max) { + revert ExceededNumberOfEntities(); + } + + for (uint8 i = 0; i < spendLimits.length; i++) { + _tokenList.tryAdd(msg.sender, SetValue.wrap(bytes30(bytes20(spendLimits[i].token)))); + for (uint256 j = 0; j < spendLimits[i].limits.length; j++) { + limits[i + startFunctionId][spendLimits[i].token][msg.sender] = spendLimits[i].limits[j]; + } + } + } + + /// @inheritdoc IPlugin + function onUninstall(bytes calldata data) external override { + (address token, uint8 functionId) = abi.decode(data, (address, uint8)); + delete limits[functionId][token][msg.sender]; + } + + function getTokensForAccount(address account) external view returns (address[] memory tokens) { + SetValue[] memory set = _tokenList.getAll(account); + tokens = new address[](set.length); + for (uint256 i = 0; i < tokens.length; i++) { + tokens[i] = address(bytes20(bytes32(SetValue.unwrap(set[i])))); + } + return tokens; + } + + /// @inheritdoc IExecutionHook + function postExecutionHook(uint8, bytes calldata) external pure override { + revert NotImplemented(); + } + + /// @inheritdoc IPlugin + // solhint-disable-next-line no-empty-blocks + function pluginManifest() external pure override returns (PluginManifest memory) {} + + /// @inheritdoc IPlugin + function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { + PluginMetadata memory metadata; + metadata.name = _NAME; + metadata.version = _VERSION; + metadata.author = _AUTHOR; + + metadata.permissionRequest = new string[](1); + metadata.permissionRequest[0] = "erc20-token-limit"; + return metadata; + } + + /// @inheritdoc BasePlugin + function supportsInterface(bytes4 interfaceId) public view override(BasePlugin, IERC165) returns (bool) { + return super.supportsInterface(interfaceId); + } + + function _decrementLimit(uint8 functionId, address token, bytes memory innerCalldata) internal { + bytes4 selector; + uint256 spend; + assembly { + selector := mload(add(innerCalldata, 32)) // 0:32 is arr len, 32:36 is selector + spend := mload(add(innerCalldata, 68)) // 36:68 is recipient, 68:100 is spend + } + if (selector == IERC20.transfer.selector || selector == IERC20.approve.selector) { + uint256 limit = limits[functionId][token][msg.sender]; + if (spend > limit) { + revert ExceededTokenLimit(); + } + // solhint-disable-next-line reentrancy + limits[functionId][token][msg.sender] = limit - spend; + } else { + revert SelectorNotAllowed(); + } + } +} diff --git a/test/mocks/MockERC20.sol b/test/mocks/MockERC20.sol new file mode 100644 index 00000000..131e0d1a --- /dev/null +++ b/test/mocks/MockERC20.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract MockERC20 is ERC20 { + constructor() ERC20("MockERC20", "MERC") {} + + function mint(address to, uint256 amount) external { + _mint(to, amount); + } +} diff --git a/test/plugin/ERC20TokenLimitPlugin.t.sol b/test/plugin/ERC20TokenLimitPlugin.t.sol new file mode 100644 index 00000000..96a18c20 --- /dev/null +++ b/test/plugin/ERC20TokenLimitPlugin.t.sol @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; +import {MockERC20} from "../mocks/MockERC20.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; +import {FunctionReference} from "../../src/helpers/FunctionReferenceLib.sol"; +import {ERC20TokenLimitPlugin} from "../../src/plugins/ERC20TokenLimitPlugin.sol"; +import {MockPlugin} from "../mocks/MockPlugin.sol"; +import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; +import {FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; +import {IStandardExecutor, Call} from "../../src/interfaces/IStandardExecutor.sol"; +import {PluginManifest} from "../../src/interfaces/IPlugin.sol"; +import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; + +import {MSCAFactoryFixture} from "../mocks/MSCAFactoryFixture.sol"; +import {AccountTestBase} from "../utils/AccountTestBase.sol"; + +contract ERC20TokenLimitPluginTest is AccountTestBase { + address public recipient = address(1); + MockERC20 public erc20; + address payable public bundler = payable(address(2)); + PluginManifest internal _m; + MockPlugin public validationPlugin = new MockPlugin(_m); + FunctionReference public validationFunction; + + UpgradeableModularAccount public acct; + ERC20TokenLimitPlugin public plugin = new ERC20TokenLimitPlugin(); + uint256 public spendLimit = 10 ether; + + function setUp() public { + // Set up a validator with hooks from the erc20 spend limit plugin attached + MSCAFactoryFixture factory = new MSCAFactoryFixture(entryPoint, _deploySingleOwnerPlugin()); + + acct = factory.createAccount(address(this), 0); + + erc20 = new MockERC20(); + erc20.mint(address(acct), 10 ether); + + ExecutionHook[] memory permissionHooks = new ExecutionHook[](1); + permissionHooks[0] = ExecutionHook({ + hookFunction: FunctionReferenceLib.pack(address(plugin), 0), + isPreHook: true, + isPostHook: false + }); + + // arr idx 0 => functionId of 0 has that spend + uint256[] memory limits = new uint256[](1); + limits[0] = spendLimit; + + ERC20TokenLimitPlugin.ERC20SpendLimit[] memory limit = new ERC20TokenLimitPlugin.ERC20SpendLimit[](1); + limit[0] = ERC20TokenLimitPlugin.ERC20SpendLimit({token: address(erc20), limits: limits}); + + bytes[] memory permissionInitDatas = new bytes[](1); + permissionInitDatas[0] = abi.encode(uint8(0), limit); + + vm.prank(address(acct)); + acct.installValidation( + ValidationConfigLib.pack(address(validationPlugin), 0, true, true), + new bytes4[](0), + new bytes(0), + new bytes(0), + abi.encode(permissionHooks, permissionInitDatas) + ); + + validationFunction = FunctionReferenceLib.pack(address(validationPlugin), 0); + } + + function _getPackedUO(bytes memory callData) internal view returns (PackedUserOperation memory uo) { + uo = PackedUserOperation({ + sender: address(acct), + nonce: 0, + initCode: "", + callData: abi.encodePacked(UpgradeableModularAccount.executeUserOp.selector, callData), + accountGasLimits: bytes32(bytes16(uint128(200000))) | bytes32(uint256(200000)), + preVerificationGas: 200000, + gasFees: bytes32(uint256(uint128(0))), + paymasterAndData: "", + signature: _encodeSignature(FunctionReferenceLib.pack(address(validationPlugin), 0), 1, "") + }); + } + + function _getExecuteWithSpend(uint256 value) internal view returns (bytes memory) { + return abi.encodeCall( + UpgradeableModularAccount.execute, + (address(erc20), 0, abi.encodeCall(IERC20.transfer, (recipient, value))) + ); + } + + function test_userOp_executeLimit() public { + vm.startPrank(address(entryPoint)); + assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether); + acct.executeUserOp(_getPackedUO(_getExecuteWithSpend(5 ether)), bytes32(0)); + assertEq(plugin.limits(0, address(erc20), address(acct)), 5 ether); + } + + function test_userOp_executeBatchLimit() public { + Call[] memory calls = new Call[](3); + calls[0] = + Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.transfer, (recipient, 1 wei))}); + calls[1] = + Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.transfer, (recipient, 1 ether))}); + calls[2] = Call({ + target: address(erc20), + value: 0, + data: abi.encodeCall(IERC20.transfer, (recipient, 5 ether + 100000)) + }); + + vm.startPrank(address(entryPoint)); + assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether); + acct.executeUserOp(_getPackedUO(abi.encodeCall(IStandardExecutor.executeBatch, (calls))), bytes32(0)); + assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether - 6 ether - 100001); + } + + function test_userOp_executeBatch_approveAndTransferLimit() public { + Call[] memory calls = new Call[](3); + calls[0] = + Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.approve, (recipient, 1 wei))}); + calls[1] = + Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.transfer, (recipient, 1 ether))}); + calls[2] = Call({ + target: address(erc20), + value: 0, + data: abi.encodeCall(IERC20.approve, (recipient, 5 ether + 100000)) + }); + + vm.startPrank(address(entryPoint)); + assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether); + acct.executeUserOp(_getPackedUO(abi.encodeCall(IStandardExecutor.executeBatch, (calls))), bytes32(0)); + assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether - 6 ether - 100001); + } + + function test_userOp_executeBatch_approveAndTransferLimit_fail() public { + Call[] memory calls = new Call[](3); + calls[0] = + Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.approve, (recipient, 1 wei))}); + calls[1] = + Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.transfer, (recipient, 1 ether))}); + calls[2] = Call({ + target: address(erc20), + value: 0, + data: abi.encodeCall(IERC20.approve, (recipient, 9 ether + 100000)) + }); + + vm.startPrank(address(entryPoint)); + assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether); + PackedUserOperation[] memory uos = new PackedUserOperation[](1); + uos[0] = _getPackedUO(abi.encodeCall(IStandardExecutor.executeBatch, (calls))); + entryPoint.handleOps(uos, bundler); + // no spend consumed + assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether); + } + + function test_runtime_executeLimit() public { + assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether); + acct.executeWithAuthorization( + _getExecuteWithSpend(5 ether), + _encodeSignature(FunctionReferenceLib.pack(address(validationPlugin), 0), 1, "") + ); + assertEq(plugin.limits(0, address(erc20), address(acct)), 5 ether); + } + + function test_runtime_executeBatchLimit() public { + Call[] memory calls = new Call[](3); + calls[0] = + Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.approve, (recipient, 1 wei))}); + calls[1] = + Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.transfer, (recipient, 1 ether))}); + calls[2] = Call({ + target: address(erc20), + value: 0, + data: abi.encodeCall(IERC20.approve, (recipient, 5 ether + 100000)) + }); + + assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether); + acct.executeWithAuthorization( + abi.encodeCall(IStandardExecutor.executeBatch, (calls)), + _encodeSignature(FunctionReferenceLib.pack(address(validationPlugin), 0), 1, "") + ); + assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether - 6 ether - 100001); + } +} From d2e3c3a9e4455c4b6b7c8067676c7352a3dc3e6c Mon Sep 17 00:00:00 2001 From: fangting-alchemy <119372438+fangting-alchemy@users.noreply.github.com> Date: Tue, 16 Jul 2024 17:49:57 -0700 Subject: [PATCH 044/111] feat: Validation revamp - Introduce validation composability and allow multiple validation installation on account (#93) --- README.md | 2 +- src/account/AccountLoupe.sol | 10 +- src/account/AccountStorage.sol | 43 ++-- src/account/PluginManager2.sol | 38 ++-- src/account/PluginManagerInternals.sol | 22 +- src/account/UpgradeableModularAccount.sol | 123 ++++++------ src/helpers/FunctionReferenceLib.sol | 37 ---- src/helpers/PluginEntityLib.sol | 35 ++++ src/helpers/ValidationConfigLib.sol | 74 +++---- ...taHelpers.sol => ValidationResHelpers.sol} | 20 +- src/interfaces/IAccountLoupe.sol | 15 +- src/interfaces/IExecutionHook.sol | 16 +- src/interfaces/IPlugin.sol | 4 +- src/interfaces/IPluginManager.sol | 6 +- src/interfaces/IValidation.sol | 34 ++-- src/interfaces/IValidationHook.sol | 24 +-- src/plugins/ERC20TokenLimitPlugin.sol | 32 +-- src/plugins/NativeTokenLimitPlugin.sol | 36 ++-- src/plugins/owner/SingleOwnerPlugin.sol | 188 ------------------ .../validation/ISingleSignerValidation.sol | 28 +++ .../validation/SingleSignerValidation.sol | 145 ++++++++++++++ .../permissionhooks/AllowlistPlugin.sol | 10 +- standard/ERCs/erc-6900.md | 58 +++--- test/account/AccountExecHooks.t.sol | 12 +- test/account/AccountLoupe.t.sol | 54 +++-- test/account/AccountReturnData.t.sol | 8 +- test/account/GlobalValidationTest.t.sol | 9 +- test/account/MultiValidation.t.sol | 36 ++-- test/account/PerHookData.t.sol | 59 +++--- test/account/SelfCallAuthorization.t.sol | 11 +- test/account/UpgradeableModularAccount.t.sol | 32 +-- test/account/ValidationIntersection.t.sol | 58 +++--- test/libraries/FunctionReferenceLib.t.sol | 40 ---- ...ure.sol => SingleSignerFactoryFixture.sol} | 28 +-- test/mocks/plugins/ComprehensivePlugin.sol | 46 ++--- .../plugins/MockAccessControlHookPlugin.sol | 10 +- test/mocks/plugins/ReturnDataPluginMocks.sol | 13 +- test/mocks/plugins/ValidationPluginMocks.sol | 33 +-- test/plugin/ERC20TokenLimitPlugin.t.sol | 19 +- test/plugin/NativeTokenLimitPlugin.t.sol | 19 +- test/plugin/SingleOwnerPlugin.t.sol | 186 ----------------- test/plugin/TokenReceiverPlugin.t.sol | 5 +- test/samples/AllowlistPlugin.t.sol | 19 +- test/utils/AccountTestBase.sol | 51 ++--- test/utils/CustomValidationTestBase.sol | 6 +- test/utils/OptimizedTest.sol | 16 +- test/utils/TestConstants.sol | 2 +- test/validation/SingleSignerValidation.t.sol | 142 +++++++++++++ 48 files changed, 912 insertions(+), 1002 deletions(-) delete mode 100644 src/helpers/FunctionReferenceLib.sol create mode 100644 src/helpers/PluginEntityLib.sol rename src/helpers/{ValidationDataHelpers.sol => ValidationResHelpers.sol} (72%) delete mode 100644 src/plugins/owner/SingleOwnerPlugin.sol create mode 100644 src/plugins/validation/ISingleSignerValidation.sol create mode 100644 src/plugins/validation/SingleSignerValidation.sol delete mode 100644 test/libraries/FunctionReferenceLib.t.sol rename test/mocks/{MSCAFactoryFixture.sol => SingleSignerFactoryFixture.sol} (79%) delete mode 100644 test/plugin/SingleOwnerPlugin.t.sol create mode 100644 test/validation/SingleSignerValidation.t.sol diff --git a/README.md b/README.md index fb3e9dbc..1c90c710 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Reference implementation for [ERC-6900](https://eips.ethereum.org/EIPS/eip-6900). It is an early draft implementation. -The implementation includes an upgradable modular account with two plugins (`SingleOwnerPlugin` and `TokenReceiverPlugin`). It is compliant with ERC-6900 with the latest updates. +The implementation includes an upgradable modular account with two plugins (`SingleSignerValidation` and `TokenReceiverPlugin`). It is compliant with ERC-6900 with the latest updates. ## Important Callouts diff --git a/src/account/AccountLoupe.sol b/src/account/AccountLoupe.sol index d652f45c..cca17149 100644 --- a/src/account/AccountLoupe.sol +++ b/src/account/AccountLoupe.sol @@ -6,7 +6,7 @@ import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import {IAccountLoupe, ExecutionHook} from "../interfaces/IAccountLoupe.sol"; -import {FunctionReference, IPluginManager} from "../interfaces/IPluginManager.sol"; +import {PluginEntity, IPluginManager} from "../interfaces/IPluginManager.sol"; import {IStandardExecutor} from "../interfaces/IStandardExecutor.sol"; import {getAccountStorage, toExecutionHook, toSelector} from "./AccountStorage.sol"; @@ -29,7 +29,7 @@ abstract contract AccountLoupe is IAccountLoupe { } /// @inheritdoc IAccountLoupe - function getSelectors(FunctionReference validationFunction) external view returns (bytes4[] memory) { + function getSelectors(PluginEntity validationFunction) external view returns (bytes4[] memory) { uint256 length = getAccountStorage().validationData[validationFunction].selectors.length(); bytes4[] memory selectors = new bytes4[](length); @@ -61,7 +61,7 @@ abstract contract AccountLoupe is IAccountLoupe { } /// @inheritdoc IAccountLoupe - function getPermissionHooks(FunctionReference validationFunction) + function getPermissionHooks(PluginEntity validationFunction) external view override @@ -79,11 +79,11 @@ abstract contract AccountLoupe is IAccountLoupe { } /// @inheritdoc IAccountLoupe - function getPreValidationHooks(FunctionReference validationFunction) + function getPreValidationHooks(PluginEntity validationFunction) external view override - returns (FunctionReference[] memory preValidationHooks) + returns (PluginEntity[] memory preValidationHooks) { preValidationHooks = getAccountStorage().validationData[validationFunction].preValidationHooks; } diff --git a/src/account/AccountStorage.sol b/src/account/AccountStorage.sol index 242ddff8..df081bd0 100644 --- a/src/account/AccountStorage.sol +++ b/src/account/AccountStorage.sol @@ -5,7 +5,7 @@ import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; -import {FunctionReference} from "../interfaces/IPluginManager.sol"; +import {PluginEntity} from "../interfaces/IPluginManager.sol"; // bytes = keccak256("ERC6900.UpgradeableModularAccount.Storage") bytes32 constant _ACCOUNT_STORAGE_SLOT = 0x9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e40; @@ -31,7 +31,7 @@ struct ValidationData { // Whether or not this validation is a signature validator. bool isSignatureValidation; // The pre validation hooks for this function selector. - FunctionReference[] preValidationHooks; + PluginEntity[] preValidationHooks; // Permission hooks for this validation function. EnumerableSet.Bytes32Set permissionHooks; // The set of selectors that may be validated by this validation function. @@ -46,7 +46,7 @@ struct AccountStorage { EnumerableMap.AddressToUintMap pluginManifestHashes; // Execution functions and their associated functions mapping(bytes4 => SelectorData) selectorData; - mapping(FunctionReference validationFunction => ValidationData) validationData; + mapping(PluginEntity validationFunction => ValidationData) validationData; // For ERC165 introspection mapping(bytes4 => uint256) supportedIfaces; } @@ -59,32 +59,32 @@ function getAccountStorage() pure returns (AccountStorage storage _storage) { using EnumerableSet for EnumerableSet.Bytes32Set; -function toSetValue(FunctionReference functionReference) pure returns (bytes32) { - return bytes32(FunctionReference.unwrap(functionReference)); +function toSetValue(PluginEntity functionReference) pure returns (bytes32) { + return bytes32(PluginEntity.unwrap(functionReference)); } -function toFunctionReference(bytes32 setValue) pure returns (FunctionReference) { - return FunctionReference.wrap(bytes21(setValue)); +function toPluginEntity(bytes32 setValue) pure returns (PluginEntity) { + return PluginEntity.wrap(bytes24(setValue)); } // ExecutionHook layout: -// 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF______________________ Hook Function Reference -// 0x__________________________________________AA____________________ is pre hook -// 0x____________________________________________BB__________________ is post hook +// 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF______________________ Hook Plugin Entity +// 0x________________________________________________AA____________________ is pre hook +// 0x__________________________________________________BB__________________ is post hook function toSetValue(ExecutionHook memory executionHook) pure returns (bytes32) { - return bytes32(FunctionReference.unwrap(executionHook.hookFunction)) - | bytes32(executionHook.isPreHook ? uint256(1) << 80 : 0) - | bytes32(executionHook.isPostHook ? uint256(1) << 72 : 0); + return bytes32(PluginEntity.unwrap(executionHook.hookFunction)) + | bytes32(executionHook.isPreHook ? uint256(1) << 56 : 0) + | bytes32(executionHook.isPostHook ? uint256(1) << 48 : 0); } function toExecutionHook(bytes32 setValue) pure - returns (FunctionReference hookFunction, bool isPreHook, bool isPostHook) + returns (PluginEntity hookFunction, bool isPreHook, bool isPostHook) { - hookFunction = FunctionReference.wrap(bytes21(setValue)); - isPreHook = (uint256(setValue) >> 80) & 0xFF == 1; - isPostHook = (uint256(setValue) >> 72) & 0xFF == 1; + hookFunction = PluginEntity.wrap(bytes24(setValue)); + isPreHook = (uint256(setValue) >> 56) & 0xFF == 1; + isPostHook = (uint256(setValue) >> 48) & 0xFF == 1; } function toSetValue(bytes4 selector) pure returns (bytes32) { @@ -96,15 +96,12 @@ function toSelector(bytes32 setValue) pure returns (bytes4) { } /// @dev Helper function to get all elements of a set into memory. -function toFunctionReferenceArray(EnumerableSet.Bytes32Set storage set) - view - returns (FunctionReference[] memory) -{ +function toPluginEntityArray(EnumerableSet.Bytes32Set storage set) view returns (PluginEntity[] memory) { uint256 length = set.length(); - FunctionReference[] memory result = new FunctionReference[](length); + PluginEntity[] memory result = new PluginEntity[](length); for (uint256 i = 0; i < length; ++i) { bytes32 key = set.at(i); - result[i] = FunctionReference.wrap(bytes21(key)); + result[i] = PluginEntity.wrap(bytes24(key)); } return result; } diff --git a/src/account/PluginManager2.sol b/src/account/PluginManager2.sol index 28eb0ecf..1f121880 100644 --- a/src/account/PluginManager2.sol +++ b/src/account/PluginManager2.sol @@ -4,10 +4,10 @@ pragma solidity ^0.8.25; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {IPlugin} from "../interfaces/IPlugin.sol"; -import {FunctionReference, ValidationConfig} from "../interfaces/IPluginManager.sol"; -import {FunctionReferenceLib} from "../helpers/FunctionReferenceLib.sol"; +import {PluginEntity, ValidationConfig} from "../interfaces/IPluginManager.sol"; +import {PluginEntityLib} from "../helpers/PluginEntityLib.sol"; import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; -import {ValidationData, getAccountStorage, toSetValue, toFunctionReference} from "./AccountStorage.sol"; +import {ValidationData, getAccountStorage, toSetValue, toPluginEntity} from "./AccountStorage.sol"; import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; // Temporary additional functions for a user-controlled install flow for validation functions. @@ -18,10 +18,10 @@ abstract contract PluginManager2 { // Index marking the start of the data for the validation function. uint8 internal constant _RESERVED_VALIDATION_DATA_INDEX = 255; - error PreValidationAlreadySet(FunctionReference validationFunction, FunctionReference preValidationFunction); - error ValidationAlreadySet(bytes4 selector, FunctionReference validationFunction); - error ValidationNotSet(bytes4 selector, FunctionReference validationFunction); - error PermissionAlreadySet(FunctionReference validationFunction, ExecutionHook hook); + error PreValidationAlreadySet(PluginEntity validationFunction, PluginEntity preValidationFunction); + error ValidationAlreadySet(bytes4 selector, PluginEntity validationFunction); + error ValidationNotSet(bytes4 selector, PluginEntity validationFunction); + error PermissionAlreadySet(PluginEntity validationFunction, ExecutionHook hook); error PreValidationHookLimitExceeded(); function _installValidation( @@ -35,16 +35,16 @@ abstract contract PluginManager2 { getAccountStorage().validationData[validationConfig.functionReference()]; if (preValidationHooks.length > 0) { - (FunctionReference[] memory preValidationFunctions, bytes[] memory initDatas) = - abi.decode(preValidationHooks, (FunctionReference[], bytes[])); + (PluginEntity[] memory preValidationFunctions, bytes[] memory initDatas) = + abi.decode(preValidationHooks, (PluginEntity[], bytes[])); for (uint256 i = 0; i < preValidationFunctions.length; ++i) { - FunctionReference preValidationFunction = preValidationFunctions[i]; + PluginEntity preValidationFunction = preValidationFunctions[i]; _validationData.preValidationHooks.push(preValidationFunction); if (initDatas[i].length > 0) { - (address preValidationPlugin,) = FunctionReferenceLib.unpack(preValidationFunction); + (address preValidationPlugin,) = PluginEntityLib.unpack(preValidationFunction); IPlugin(preValidationPlugin).onInstall(initDatas[i]); } } @@ -67,7 +67,7 @@ abstract contract PluginManager2 { } if (initDatas[i].length > 0) { - (address executionPlugin,) = FunctionReferenceLib.unpack(permissionFunction.hookFunction); + (address executionPlugin,) = PluginEntityLib.unpack(permissionFunction.hookFunction); IPlugin(executionPlugin).onInstall(initDatas[i]); } } @@ -90,7 +90,7 @@ abstract contract PluginManager2 { } function _uninstallValidation( - FunctionReference validationFunction, + PluginEntity validationFunction, bytes calldata uninstallData, bytes calldata preValidationHookUninstallData, bytes calldata permissionHookUninstallData @@ -104,11 +104,11 @@ abstract contract PluginManager2 { bytes[] memory preValidationHookUninstallDatas = abi.decode(preValidationHookUninstallData, (bytes[])); // Clear pre validation hooks - FunctionReference[] storage preValidationHooks = _validationData.preValidationHooks; + PluginEntity[] storage preValidationHooks = _validationData.preValidationHooks; for (uint256 i = 0; i < preValidationHooks.length; ++i) { - FunctionReference preValidationFunction = preValidationHooks[i]; + PluginEntity preValidationFunction = preValidationHooks[i]; if (preValidationHookUninstallDatas[0].length > 0) { - (address preValidationPlugin,) = FunctionReferenceLib.unpack(preValidationFunction); + (address preValidationPlugin,) = PluginEntityLib.unpack(preValidationFunction); IPlugin(preValidationPlugin).onUninstall(preValidationHookUninstallDatas[0]); } } @@ -122,9 +122,9 @@ abstract contract PluginManager2 { EnumerableSet.Bytes32Set storage permissionHooks = _validationData.permissionHooks; uint256 i = 0; while (permissionHooks.length() > 0) { - FunctionReference permissionHook = toFunctionReference(permissionHooks.at(0)); + PluginEntity permissionHook = toPluginEntity(permissionHooks.at(0)); permissionHooks.remove(toSetValue(permissionHook)); - (address permissionHookPlugin,) = FunctionReferenceLib.unpack(permissionHook); + (address permissionHookPlugin,) = PluginEntityLib.unpack(permissionHook); IPlugin(permissionHookPlugin).onUninstall(permissionHookUninstallDatas[i++]); } } @@ -137,7 +137,7 @@ abstract contract PluginManager2 { } if (uninstallData.length > 0) { - (address plugin,) = FunctionReferenceLib.unpack(validationFunction); + (address plugin,) = PluginEntityLib.unpack(validationFunction); IPlugin(plugin).onUninstall(uninstallData); } } diff --git a/src/account/PluginManagerInternals.sol b/src/account/PluginManagerInternals.sol index 4df90e1f..ccc4c68b 100644 --- a/src/account/PluginManagerInternals.sol +++ b/src/account/PluginManagerInternals.sol @@ -5,17 +5,17 @@ import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165C import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; -import {FunctionReferenceLib} from "../helpers/FunctionReferenceLib.sol"; +import {PluginEntityLib} from "../helpers/PluginEntityLib.sol"; import {IPlugin, ManifestExecutionHook, ManifestValidation, PluginManifest} from "../interfaces/IPlugin.sol"; import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; -import {FunctionReference, IPluginManager} from "../interfaces/IPluginManager.sol"; +import {PluginEntity, IPluginManager} from "../interfaces/IPluginManager.sol"; import {KnownSelectors} from "../helpers/KnownSelectors.sol"; import {AccountStorage, getAccountStorage, SelectorData, toSetValue} from "./AccountStorage.sol"; abstract contract PluginManagerInternals is IPluginManager { using EnumerableSet for EnumerableSet.Bytes32Set; using EnumerableMap for EnumerableMap.AddressToUintMap; - using FunctionReferenceLib for FunctionReference; + using PluginEntityLib for PluginEntity; error ArrayLengthMismatch(); error Erc4337FunctionNotAllowed(bytes4 selector); @@ -23,13 +23,13 @@ abstract contract PluginManagerInternals is IPluginManager { error InvalidPluginManifest(); error IPluginFunctionNotAllowed(bytes4 selector); error NativeFunctionNotAllowed(bytes4 selector); - error NullFunctionReference(); + error NullPluginEntity(); error NullPlugin(); error PluginAlreadyInstalled(address plugin); error PluginInstallCallbackFailed(address plugin, bytes revertReason); error PluginInterfaceNotSupported(address plugin); error PluginNotInstalled(address plugin); - error ValidationFunctionAlreadySet(bytes4 selector, FunctionReference validationFunction); + error ValidationFunctionAlreadySet(bytes4 selector, PluginEntity validationFunction); // Storage update operations @@ -78,7 +78,7 @@ abstract contract PluginManagerInternals is IPluginManager { function _addValidationFunction(address plugin, ManifestValidation memory mv) internal { AccountStorage storage _storage = getAccountStorage(); - FunctionReference validationFunction = FunctionReferenceLib.pack(plugin, mv.functionId); + PluginEntity validationFunction = PluginEntityLib.pack(plugin, mv.entityId); if (mv.isDefault) { _storage.validationData[validationFunction].isGlobal = true; @@ -99,7 +99,7 @@ abstract contract PluginManagerInternals is IPluginManager { function _removeValidationFunction(address plugin, ManifestValidation memory mv) internal { AccountStorage storage _storage = getAccountStorage(); - FunctionReference validationFunction = FunctionReferenceLib.pack(plugin, mv.functionId); + PluginEntity validationFunction = PluginEntityLib.pack(plugin, mv.entityId); _storage.validationData[validationFunction].isGlobal = false; _storage.validationData[validationFunction].isSignatureValidation = false; @@ -113,7 +113,7 @@ abstract contract PluginManagerInternals is IPluginManager { function _addExecHooks( EnumerableSet.Bytes32Set storage hooks, - FunctionReference hookFunction, + PluginEntity hookFunction, bool isPreExecHook, bool isPostExecHook ) internal { @@ -126,7 +126,7 @@ abstract contract PluginManagerInternals is IPluginManager { function _removeExecHooks( EnumerableSet.Bytes32Set storage hooks, - FunctionReference hookFunction, + PluginEntity hookFunction, bool isPreExecHook, bool isPostExecHook ) internal { @@ -183,7 +183,7 @@ abstract contract PluginManagerInternals is IPluginManager { for (uint256 i = 0; i < length; ++i) { ManifestExecutionHook memory mh = manifest.executionHooks[i]; EnumerableSet.Bytes32Set storage execHooks = _storage.selectorData[mh.executionSelector].executionHooks; - FunctionReference hookFunction = FunctionReferenceLib.pack(plugin, mh.functionId); + PluginEntity hookFunction = PluginEntityLib.pack(plugin, mh.entityId); _addExecHooks(execHooks, hookFunction, mh.isPreHook, mh.isPostHook); } @@ -223,7 +223,7 @@ abstract contract PluginManagerInternals is IPluginManager { uint256 length = manifest.executionHooks.length; for (uint256 i = 0; i < length; ++i) { ManifestExecutionHook memory mh = manifest.executionHooks[i]; - FunctionReference hookFunction = FunctionReferenceLib.pack(plugin, mh.functionId); + PluginEntity hookFunction = PluginEntityLib.pack(plugin, mh.entityId); EnumerableSet.Bytes32Set storage execHooks = _storage.selectorData[mh.executionSelector].executionHooks; _removeExecHooks(execHooks, hookFunction, mh.isPreHook, mh.isPostHook); } diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index d2d1b2cc..acbd6de5 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -10,15 +10,15 @@ import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import {FunctionReferenceLib} from "../helpers/FunctionReferenceLib.sol"; +import {PluginEntityLib} from "../helpers/PluginEntityLib.sol"; import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; import {SparseCalldataSegmentLib} from "../helpers/SparseCalldataSegmentLib.sol"; -import {_coalescePreValidation, _coalesceValidation} from "../helpers/ValidationDataHelpers.sol"; +import {_coalescePreValidation, _coalesceValidation} from "../helpers/ValidationResHelpers.sol"; import {IPlugin, PluginManifest} from "../interfaces/IPlugin.sol"; import {IValidation} from "../interfaces/IValidation.sol"; import {IValidationHook} from "../interfaces/IValidationHook.sol"; import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; -import {FunctionReference, IPluginManager, ValidationConfig} from "../interfaces/IPluginManager.sol"; +import {PluginEntity, IPluginManager, ValidationConfig} from "../interfaces/IPluginManager.sol"; import {IStandardExecutor, Call} from "../interfaces/IStandardExecutor.sol"; import {AccountExecutor} from "./AccountExecutor.sol"; import {AccountLoupe} from "./AccountLoupe.sol"; @@ -41,13 +41,13 @@ contract UpgradeableModularAccount is UUPSUpgradeable { using EnumerableSet for EnumerableSet.Bytes32Set; - using FunctionReferenceLib for FunctionReference; + using PluginEntityLib for PluginEntity; using ValidationConfigLib for ValidationConfig; using SparseCalldataSegmentLib for bytes; struct PostExecToRun { bytes preExecHookReturnData; - FunctionReference postExecHook; + PluginEntity postExecHook; } IEntryPoint private immutable _ENTRY_POINT; @@ -68,18 +68,18 @@ contract UpgradeableModularAccount is error NativeTokenSpendingNotPermitted(address plugin); error NonCanonicalEncoding(); error NotEntryPoint(); - error PostExecHookReverted(address plugin, uint8 functionId, bytes revertReason); - error PreExecHookReverted(address plugin, uint8 functionId, bytes revertReason); - error PreRuntimeValidationHookFailed(address plugin, uint8 functionId, bytes revertReason); + error PostExecHookReverted(address plugin, uint32 entityId, bytes revertReason); + error PreExecHookReverted(address plugin, uint32 entityId, bytes revertReason); + error PreRuntimeValidationHookFailed(address plugin, uint32 entityId, bytes revertReason); error RequireUserOperationContext(); error RuntimeValidationFunctionMissing(bytes4 selector); - error RuntimeValidationFunctionReverted(address plugin, uint8 functionId, bytes revertReason); + error RuntimeValidationFunctionReverted(address plugin, uint32 entityId, bytes revertReason); error SelfCallRecursionDepthExceeded(); - error SignatureValidationInvalid(address plugin, uint8 functionId); - error UnexpectedAggregator(address plugin, uint8 functionId, address aggregator); + error SignatureValidationInvalid(address plugin, uint32 entityId); + error UnexpectedAggregator(address plugin, uint32 entityId, address aggregator); error UnrecognizedFunction(bytes4 selector); error UserOpValidationFunctionMissing(bytes4 selector); - error ValidationDoesNotApply(bytes4 selector, address plugin, uint8 functionId, bool isGlobal); + error ValidationDoesNotApply(bytes4 selector, address plugin, uint32 entityId, bool isGlobal); error ValidationSignatureSegmentMissing(); error SignatureSegmentOutOfOrder(); @@ -164,7 +164,7 @@ contract UpgradeableModularAccount is revert NotEntryPoint(); } - FunctionReference userOpValidationFunction = FunctionReference.wrap(bytes21(userOp.signature[:21])); + PluginEntity userOpValidationFunction = PluginEntity.wrap(bytes24(userOp.signature[:24])); PostExecToRun[] memory postPermissionHooks = _doPreHooks(getAccountStorage().validationData[userOpValidationFunction].permissionHooks, msg.data); @@ -217,13 +217,13 @@ contract UpgradeableModularAccount is returns (bytes memory) { // Revert if the provided `authorization` less than 21 bytes long, rather than right-padding. - FunctionReference runtimeValidationFunction = FunctionReference.wrap(bytes21(authorization[:21])); + PluginEntity runtimeValidationFunction = PluginEntity.wrap(bytes24(authorization[:24])); // Check if the runtime validation function is allowed to be called - bool isGlobalValidation = uint8(authorization[21]) == 1; + bool isGlobalValidation = uint8(authorization[24]) == 1; _checkIfValidationAppliesCallData(data, runtimeValidationFunction, isGlobalValidation); - _doRuntimeValidation(runtimeValidationFunction, data, authorization[22:]); + _doRuntimeValidation(runtimeValidationFunction, data, authorization[25:]); // If runtime validation passes, do runtime permission checks PostExecToRun[] memory postPermissionHooks = @@ -301,7 +301,7 @@ contract UpgradeableModularAccount is /// @inheritdoc IPluginManager /// @notice May be validated by a global validation. function uninstallValidation( - FunctionReference validationFunction, + PluginEntity validationFunction, bytes calldata uninstallData, bytes calldata preValidationHookUninstallData, bytes calldata permissionHookUninstallData @@ -341,15 +341,15 @@ contract UpgradeableModularAccount is function isValidSignature(bytes32 hash, bytes calldata signature) public view override returns (bytes4) { AccountStorage storage _storage = getAccountStorage(); - FunctionReference sigValidation = FunctionReference.wrap(bytes21(signature)); + PluginEntity sigValidation = PluginEntity.wrap(bytes24(signature)); - (address plugin, uint8 functionId) = sigValidation.unpack(); + (address plugin, uint32 entityId) = sigValidation.unpack(); if (!_storage.validationData[sigValidation].isSignatureValidation) { - revert SignatureValidationInvalid(plugin, functionId); + revert SignatureValidationInvalid(plugin, entityId); } if ( - IValidation(plugin).validateSignature(functionId, msg.sender, hash, signature[21:]) + IValidation(plugin).validateSignature(address(this), entityId, msg.sender, hash, signature[24:]) == _1271_MAGIC_VALUE ) { return _1271_MAGIC_VALUE; @@ -377,8 +377,8 @@ contract UpgradeableModularAccount is } // Revert if the provided `authorization` less than 21 bytes long, rather than right-padding. - FunctionReference userOpValidationFunction = FunctionReference.wrap(bytes21(userOp.signature[:21])); - bool isGlobalValidation = uint8(userOp.signature[21]) == 1; + PluginEntity userOpValidationFunction = PluginEntity.wrap(bytes24(userOp.signature[:24])); + bool isGlobalValidation = uint8(userOp.signature[24]) == 1; _checkIfValidationAppliesCallData(userOp.callData, userOpValidationFunction, isGlobalValidation); @@ -393,12 +393,12 @@ contract UpgradeableModularAccount is revert RequireUserOperationContext(); } - validationData = _doUserOpValidation(userOpValidationFunction, userOp, userOp.signature[22:], userOpHash); + validationData = _doUserOpValidation(userOpValidationFunction, userOp, userOp.signature[25:], userOpHash); } // To support gas estimation, we don't fail early when the failure is caused by a signature failure function _doUserOpValidation( - FunctionReference userOpValidationFunction, + PluginEntity userOpValidationFunction, PackedUserOperation memory userOp, bytes calldata signature, bytes32 userOpHash @@ -407,10 +407,10 @@ contract UpgradeableModularAccount is bytes calldata signatureSegment; (signatureSegment, signature) = signature.getNextSegment(); - uint256 validationData; + uint256 validationRes; // Do preUserOpValidation hooks - FunctionReference[] memory preUserOpValidationHooks = + PluginEntity[] memory preUserOpValidationHooks = getAccountStorage().validationData[userOpValidationFunction].preValidationHooks; for (uint256 i = 0; i < preUserOpValidationHooks.length; ++i) { @@ -434,15 +434,15 @@ contract UpgradeableModularAccount is userOp.signature = ""; } - (address plugin, uint8 functionId) = preUserOpValidationHooks[i].unpack(); - uint256 currentValidationData = - IValidationHook(plugin).preUserOpValidationHook(functionId, userOp, userOpHash); + (address plugin, uint32 entityId) = preUserOpValidationHooks[i].unpack(); + uint256 currentValidationRes = + IValidationHook(plugin).preUserOpValidationHook(entityId, userOp, userOpHash); - if (uint160(currentValidationData) > 1) { + if (uint160(currentValidationRes) > 1) { // If the aggregator is not 0 or 1, it is an unexpected value - revert UnexpectedAggregator(plugin, functionId, address(uint160(currentValidationData))); + revert UnexpectedAggregator(plugin, entityId, address(uint160(currentValidationRes))); } - validationData = _coalescePreValidation(validationData, currentValidationData); + validationRes = _coalescePreValidation(validationRes, currentValidationRes); } // Run the user op validationFunction @@ -453,22 +453,22 @@ contract UpgradeableModularAccount is userOp.signature = signatureSegment.getBody(); - (address plugin, uint8 functionId) = userOpValidationFunction.unpack(); - uint256 currentValidationData = IValidation(plugin).validateUserOp(functionId, userOp, userOpHash); + (address plugin, uint32 entityId) = userOpValidationFunction.unpack(); + uint256 currentValidationRes = IValidation(plugin).validateUserOp(entityId, userOp, userOpHash); if (preUserOpValidationHooks.length != 0) { // If we have other validation data we need to coalesce with - validationData = _coalesceValidation(validationData, currentValidationData); + validationRes = _coalesceValidation(validationRes, currentValidationRes); } else { - validationData = currentValidationData; + validationRes = currentValidationRes; } } - return validationData; + return validationRes; } function _doRuntimeValidation( - FunctionReference runtimeValidationFunction, + PluginEntity runtimeValidationFunction, bytes calldata callData, bytes calldata authorizationData ) internal { @@ -477,7 +477,7 @@ contract UpgradeableModularAccount is (authSegment, authorizationData) = authorizationData.getNextSegment(); // run all preRuntimeValidation hooks - FunctionReference[] memory preRuntimeValidationHooks = + PluginEntity[] memory preRuntimeValidationHooks = getAccountStorage().validationData[runtimeValidationFunction].preValidationHooks; for (uint256 i = 0; i < preRuntimeValidationHooks.length; ++i) { @@ -501,15 +501,15 @@ contract UpgradeableModularAccount is currentAuthData = ""; } - (address hookPlugin, uint8 hookFunctionId) = preRuntimeValidationHooks[i].unpack(); + (address hookPlugin, uint32 hookEntityId) = preRuntimeValidationHooks[i].unpack(); try IValidationHook(hookPlugin).preRuntimeValidationHook( - hookFunctionId, msg.sender, msg.value, callData, currentAuthData + hookEntityId, msg.sender, msg.value, callData, currentAuthData ) // forgefmt: disable-start // solhint-disable-next-line no-empty-blocks {} catch (bytes memory revertReason) { // forgefmt: disable-end - revert PreRuntimeValidationHookFailed(hookPlugin, hookFunctionId, revertReason); + revert PreRuntimeValidationHookFailed(hookPlugin, hookEntityId, revertReason); } } @@ -517,14 +517,16 @@ contract UpgradeableModularAccount is revert ValidationSignatureSegmentMissing(); } - (address plugin, uint8 functionId) = runtimeValidationFunction.unpack(); + (address plugin, uint32 entityId) = runtimeValidationFunction.unpack(); - try IValidation(plugin).validateRuntime(functionId, msg.sender, msg.value, callData, authSegment.getBody()) + try IValidation(plugin).validateRuntime( + address(this), entityId, msg.sender, msg.value, callData, authSegment.getBody() + ) // forgefmt: disable-start // solhint-disable-next-line no-empty-blocks {} catch (bytes memory revertReason) { // forgefmt: disable-end - revert RuntimeValidationFunctionReverted(plugin, functionId, revertReason); + revert RuntimeValidationFunctionReverted(plugin, entityId, revertReason); } } @@ -540,7 +542,7 @@ contract UpgradeableModularAccount is // be sure that the set of hooks to run will not be affected by state changes mid-execution. for (uint256 i = 0; i < hooksLength; ++i) { bytes32 key = executionHooks.at(i); - (FunctionReference hookFunction,, bool isPostHook) = toExecutionHook(key); + (PluginEntity hookFunction,, bool isPostHook) = toExecutionHook(key); if (isPostHook) { postHooksToRun[i].postExecHook = hookFunction; } @@ -550,7 +552,7 @@ contract UpgradeableModularAccount is // exists. for (uint256 i = 0; i < hooksLength; ++i) { bytes32 key = executionHooks.at(i); - (FunctionReference hookFunction, bool isPreHook, bool isPostHook) = toExecutionHook(key); + (PluginEntity hookFunction, bool isPreHook, bool isPostHook) = toExecutionHook(key); if (isPreHook) { bytes memory preExecHookReturnData; @@ -565,18 +567,18 @@ contract UpgradeableModularAccount is } } - function _runPreExecHook(FunctionReference preExecHook, bytes memory data) + function _runPreExecHook(PluginEntity preExecHook, bytes memory data) internal returns (bytes memory preExecHookReturnData) { - (address plugin, uint8 functionId) = preExecHook.unpack(); - try IExecutionHook(plugin).preExecutionHook(functionId, msg.sender, msg.value, data) returns ( + (address plugin, uint32 entityId) = preExecHook.unpack(); + try IExecutionHook(plugin).preExecutionHook(entityId, msg.sender, msg.value, data) returns ( bytes memory returnData ) { preExecHookReturnData = returnData; } catch (bytes memory revertReason) { // TODO: same issue with EP0.6 - we can't do bytes4 error codes in plugins - revert PreExecHookReverted(plugin, functionId, revertReason); + revert PreExecHookReverted(plugin, entityId, revertReason); } } @@ -594,11 +596,11 @@ contract UpgradeableModularAccount is continue; } - (address plugin, uint8 functionId) = postHookToRun.postExecHook.unpack(); + (address plugin, uint32 entityId) = postHookToRun.postExecHook.unpack(); // solhint-disable-next-line no-empty-blocks - try IExecutionHook(plugin).postExecutionHook(functionId, postHookToRun.preExecHookReturnData) {} + try IExecutionHook(plugin).postExecutionHook(entityId, postHookToRun.preExecHookReturnData) {} catch (bytes memory revertReason) { - revert PostExecHookReverted(plugin, functionId, revertReason); + revert PostExecHookReverted(plugin, entityId, revertReason); } } } @@ -608,7 +610,7 @@ contract UpgradeableModularAccount is function _checkIfValidationAppliesCallData( bytes calldata callData, - FunctionReference validationFunction, + PluginEntity validationFunction, bool isGlobal ) internal view { bytes4 outerSelector = bytes4(callData[:4]); @@ -659,11 +661,10 @@ contract UpgradeableModularAccount is } } - function _checkIfValidationAppliesSelector( - bytes4 selector, - FunctionReference validationFunction, - bool isGlobal - ) internal view { + function _checkIfValidationAppliesSelector(bytes4 selector, PluginEntity validationFunction, bool isGlobal) + internal + view + { AccountStorage storage _storage = getAccountStorage(); // Check that the provided validation function is applicable to the selector diff --git a/src/helpers/FunctionReferenceLib.sol b/src/helpers/FunctionReferenceLib.sol deleted file mode 100644 index 7bddd94b..00000000 --- a/src/helpers/FunctionReferenceLib.sol +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.25; - -import {FunctionReference} from "../interfaces/IPluginManager.sol"; - -library FunctionReferenceLib { - // Empty or unset function reference. - FunctionReference internal constant _EMPTY_FUNCTION_REFERENCE = FunctionReference.wrap(bytes21(0)); - // Magic value for hooks that should always revert. - FunctionReference internal constant _PRE_HOOK_ALWAYS_DENY = FunctionReference.wrap(bytes21(uint168(2))); - - function pack(address addr, uint8 functionId) internal pure returns (FunctionReference) { - return FunctionReference.wrap(bytes21(bytes20(addr)) | bytes21(uint168(functionId))); - } - - function unpack(FunctionReference fr) internal pure returns (address addr, uint8 functionId) { - bytes21 underlying = FunctionReference.unwrap(fr); - addr = address(bytes20(underlying)); - functionId = uint8(bytes1(underlying << 160)); - } - - function isEmpty(FunctionReference fr) internal pure returns (bool) { - return FunctionReference.unwrap(fr) == bytes21(0); - } - - function notEmpty(FunctionReference fr) internal pure returns (bool) { - return FunctionReference.unwrap(fr) != bytes21(0); - } - - function eq(FunctionReference a, FunctionReference b) internal pure returns (bool) { - return FunctionReference.unwrap(a) == FunctionReference.unwrap(b); - } - - function notEq(FunctionReference a, FunctionReference b) internal pure returns (bool) { - return FunctionReference.unwrap(a) != FunctionReference.unwrap(b); - } -} diff --git a/src/helpers/PluginEntityLib.sol b/src/helpers/PluginEntityLib.sol new file mode 100644 index 00000000..423b7f70 --- /dev/null +++ b/src/helpers/PluginEntityLib.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.25; + +import {PluginEntity} from "../interfaces/IPluginManager.sol"; + +library PluginEntityLib { + // Magic value for hooks that should always revert. + PluginEntity internal constant _PRE_HOOK_ALWAYS_DENY = PluginEntity.wrap(bytes24(uint192(2))); + + function pack(address addr, uint32 entityId) internal pure returns (PluginEntity) { + return PluginEntity.wrap(bytes24(bytes20(addr)) | bytes24(uint192(entityId))); + } + + function unpack(PluginEntity fr) internal pure returns (address addr, uint32 entityId) { + bytes24 underlying = PluginEntity.unwrap(fr); + addr = address(bytes20(underlying)); + entityId = uint32(bytes4(underlying << 160)); + } + + function isEmpty(PluginEntity fr) internal pure returns (bool) { + return PluginEntity.unwrap(fr) == bytes24(0); + } + + function notEmpty(PluginEntity fr) internal pure returns (bool) { + return PluginEntity.unwrap(fr) != bytes24(0); + } + + function eq(PluginEntity a, PluginEntity b) internal pure returns (bool) { + return PluginEntity.unwrap(a) == PluginEntity.unwrap(b); + } + + function notEq(PluginEntity a, PluginEntity b) internal pure returns (bool) { + return PluginEntity.unwrap(a) != PluginEntity.unwrap(b); + } +} diff --git a/src/helpers/ValidationConfigLib.sol b/src/helpers/ValidationConfigLib.sol index 71639f80..95e8ea90 100644 --- a/src/helpers/ValidationConfigLib.sol +++ b/src/helpers/ValidationConfigLib.sol @@ -1,48 +1,48 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.25; -import {FunctionReference, ValidationConfig} from "../interfaces/IPluginManager.sol"; +import {PluginEntity, ValidationConfig} from "../interfaces/IPluginManager.sol"; // Validation config is a packed representation of a validation function and flags for its configuration. // Layout: // 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA________________________ // Address -// 0x________________________________________BB______________________ // Function ID -// 0x__________________________________________CC____________________ // isGlobal -// 0x____________________________________________DD__________________ // isSignatureValidation -// 0x______________________________________________000000000000000000 // unused +// 0x________________________________________BBBBBBBB________________ // Entity ID +// 0x________________________________________________CC______________ // isGlobal +// 0x__________________________________________________DD____________ // isSignatureValidation +// 0x____________________________________________________000000000000 // unused library ValidationConfigLib { - function pack(FunctionReference _validationFunction, bool _isGlobal, bool _isSignatureValidation) + function pack(PluginEntity _validationFunction, bool _isGlobal, bool _isSignatureValidation) internal pure returns (ValidationConfig) { return ValidationConfig.wrap( - bytes23( - bytes23(FunctionReference.unwrap(_validationFunction)) - // isGlobal flag stored in the 22nd byte - | bytes23(bytes32(_isGlobal ? uint256(1) << 80 : 0)) - // isSignatureValidation flag stored in the 23rd byte - | bytes23(bytes32(_isSignatureValidation ? uint256(1) << 72 : 0)) + bytes26( + bytes26(PluginEntity.unwrap(_validationFunction)) + // isGlobal flag stored in the 25th byte + | bytes26(bytes32(_isGlobal ? uint256(1) << 56 : 0)) + // isSignatureValidation flag stored in the 26th byte + | bytes26(bytes32(_isSignatureValidation ? uint256(1) << 48 : 0)) ) ); } - function pack(address _plugin, uint8 _functionId, bool _isGlobal, bool _isSignatureValidation) + function pack(address _plugin, uint32 _entityId, bool _isGlobal, bool _isSignatureValidation) internal pure returns (ValidationConfig) { return ValidationConfig.wrap( - bytes23( + bytes26( // plugin address stored in the first 20 bytes - bytes23(bytes20(_plugin)) - // functionId stored in the 21st byte - | bytes23(bytes32(uint256(_functionId) << 168)) - // isGlobal flag stored in the 22nd byte - | bytes23(bytes32(_isGlobal ? uint256(1) << 80 : 0)) - // isSignatureValidation flag stored in the 23rd byte - | bytes23(bytes32(_isSignatureValidation ? uint256(1) << 72 : 0)) + bytes26(bytes20(_plugin)) + // entityId stored in the 21st - 24th byte + | bytes26(bytes24(uint192(_entityId))) + // isGlobal flag stored in the 25th byte + | bytes26(bytes32(_isGlobal ? uint256(1) << 56 : 0)) + // isSignatureValidation flag stored in the 26th byte + | bytes26(bytes32(_isSignatureValidation ? uint256(1) << 48 : 0)) ) ); } @@ -50,43 +50,43 @@ library ValidationConfigLib { function unpackUnderlying(ValidationConfig config) internal pure - returns (address _plugin, uint8 _functionId, bool _isGlobal, bool _isSignatureValidation) + returns (address _plugin, uint32 _entityId, bool _isGlobal, bool _isSignatureValidation) { - bytes23 configBytes = ValidationConfig.unwrap(config); + bytes26 configBytes = ValidationConfig.unwrap(config); _plugin = address(bytes20(configBytes)); - _functionId = uint8(configBytes[20]); - _isGlobal = uint8(configBytes[21]) == 1; - _isSignatureValidation = uint8(configBytes[22]) == 1; + _entityId = uint32(bytes4(configBytes << 160)); + _isGlobal = uint8(configBytes[24]) == 1; + _isSignatureValidation = uint8(configBytes[25]) == 1; } function unpack(ValidationConfig config) internal pure - returns (FunctionReference _validationFunction, bool _isGlobal, bool _isSignatureValidation) + returns (PluginEntity _validationFunction, bool _isGlobal, bool _isSignatureValidation) { - bytes23 configBytes = ValidationConfig.unwrap(config); - _validationFunction = FunctionReference.wrap(bytes21(configBytes)); - _isGlobal = uint8(configBytes[21]) == 1; - _isSignatureValidation = uint8(configBytes[22]) == 1; + bytes26 configBytes = ValidationConfig.unwrap(config); + _validationFunction = PluginEntity.wrap(bytes24(configBytes)); + _isGlobal = uint8(configBytes[24]) == 1; + _isSignatureValidation = uint8(configBytes[25]) == 1; } function plugin(ValidationConfig config) internal pure returns (address) { return address(bytes20(ValidationConfig.unwrap(config))); } - function functionId(ValidationConfig config) internal pure returns (uint8) { - return uint8(ValidationConfig.unwrap(config)[20]); + function entityId(ValidationConfig config) internal pure returns (uint32) { + return uint32(bytes4(ValidationConfig.unwrap(config) << 160)); } - function functionReference(ValidationConfig config) internal pure returns (FunctionReference) { - return FunctionReference.wrap(bytes21(ValidationConfig.unwrap(config))); + function functionReference(ValidationConfig config) internal pure returns (PluginEntity) { + return PluginEntity.wrap(bytes24(ValidationConfig.unwrap(config))); } function isGlobal(ValidationConfig config) internal pure returns (bool) { - return uint8(ValidationConfig.unwrap(config)[21]) == 1; + return uint8(ValidationConfig.unwrap(config)[24]) == 1; } function isSignatureValidation(ValidationConfig config) internal pure returns (bool) { - return uint8(ValidationConfig.unwrap(config)[22]) == 1; + return uint8(ValidationConfig.unwrap(config)[25]) == 1; } } diff --git a/src/helpers/ValidationDataHelpers.sol b/src/helpers/ValidationResHelpers.sol similarity index 72% rename from src/helpers/ValidationDataHelpers.sol rename to src/helpers/ValidationResHelpers.sol index 3f61b19c..854d442c 100644 --- a/src/helpers/ValidationDataHelpers.sol +++ b/src/helpers/ValidationResHelpers.sol @@ -2,31 +2,31 @@ pragma solidity ^0.8.25; // solhint-disable-next-line private-vars-leading-underscore -function _coalescePreValidation(uint256 validationData1, uint256 validationData2) +function _coalescePreValidation(uint256 validationRes1, uint256 validationRes2) pure returns (uint256 resValidationData) { - uint48 validUntil1 = uint48(validationData1 >> 160); + uint48 validUntil1 = uint48(validationRes1 >> 160); if (validUntil1 == 0) { validUntil1 = type(uint48).max; } - uint48 validUntil2 = uint48(validationData2 >> 160); + uint48 validUntil2 = uint48(validationRes2 >> 160); if (validUntil2 == 0) { validUntil2 = type(uint48).max; } resValidationData = ((validUntil1 > validUntil2) ? uint256(validUntil2) << 160 : uint256(validUntil1) << 160); - uint48 validAfter1 = uint48(validationData1 >> 208); - uint48 validAfter2 = uint48(validationData2 >> 208); + uint48 validAfter1 = uint48(validationRes1 >> 208); + uint48 validAfter2 = uint48(validationRes2 >> 208); resValidationData |= ((validAfter1 < validAfter2) ? uint256(validAfter2) << 208 : uint256(validAfter1) << 208); // Once we know that the authorizer field is 0 or 1, we can safely bubble up SIG_FAIL with bitwise OR - resValidationData |= uint160(validationData1) | uint160(validationData2); + resValidationData |= uint160(validationRes1) | uint160(validationRes2); } // solhint-disable-next-line private-vars-leading-underscore -function _coalesceValidation(uint256 preValidationData, uint256 validationData) +function _coalesceValidation(uint256 preValidationData, uint256 validationRes) pure returns (uint256 resValidationData) { @@ -34,17 +34,17 @@ function _coalesceValidation(uint256 preValidationData, uint256 validationData) if (validUntil1 == 0) { validUntil1 = type(uint48).max; } - uint48 validUntil2 = uint48(validationData >> 160); + uint48 validUntil2 = uint48(validationRes >> 160); if (validUntil2 == 0) { validUntil2 = type(uint48).max; } resValidationData = ((validUntil1 > validUntil2) ? uint256(validUntil2) << 160 : uint256(validUntil1) << 160); uint48 validAfter1 = uint48(preValidationData >> 208); - uint48 validAfter2 = uint48(validationData >> 208); + uint48 validAfter2 = uint48(validationRes >> 208); resValidationData |= ((validAfter1 < validAfter2) ? uint256(validAfter2) << 208 : uint256(validAfter1) << 208); // If prevalidation failed, bubble up failure - resValidationData |= uint160(preValidationData) == 1 ? 1 : uint160(validationData); + resValidationData |= uint160(preValidationData) == 1 ? 1 : uint160(validationRes); } diff --git a/src/interfaces/IAccountLoupe.sol b/src/interfaces/IAccountLoupe.sol index d74c5940..3e7d9f11 100644 --- a/src/interfaces/IAccountLoupe.sol +++ b/src/interfaces/IAccountLoupe.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: CC0-1.0 pragma solidity ^0.8.25; -import {FunctionReference} from "../interfaces/IPluginManager.sol"; +import {PluginEntity} from "../interfaces/IPluginManager.sol"; /// @notice Pre and post hooks for a given selector. /// @dev It's possible for one of either `preExecHook` or `postExecHook` to be empty. struct ExecutionHook { - FunctionReference hookFunction; + PluginEntity hookFunction; bool isPreHook; bool isPostHook; } @@ -21,7 +21,7 @@ interface IAccountLoupe { /// @notice Get the selectors for a validation function. /// @param validationFunction The validation function to get the selectors for. /// @return The allowed selectors for this validation function. - function getSelectors(FunctionReference validationFunction) external view returns (bytes4[] memory); + function getSelectors(PluginEntity validationFunction) external view returns (bytes4[] memory); /// @notice Get the pre and post execution hooks for a selector. /// @param selector The selector to get the hooks for. @@ -31,18 +31,15 @@ interface IAccountLoupe { /// @notice Get the pre and post execution hooks for a validation function. /// @param validationFunction The validation function to get the hooks for. /// @return The pre and post execution hooks for this validation function. - function getPermissionHooks(FunctionReference validationFunction) - external - view - returns (ExecutionHook[] memory); + function getPermissionHooks(PluginEntity validationFunction) external view returns (ExecutionHook[] memory); /// @notice Get the pre user op and runtime validation hooks associated with a selector. /// @param validationFunction The validation function to get the hooks for. /// @return preValidationHooks The pre validation hooks for this selector. - function getPreValidationHooks(FunctionReference validationFunction) + function getPreValidationHooks(PluginEntity validationFunction) external view - returns (FunctionReference[] memory preValidationHooks); + returns (PluginEntity[] memory preValidationHooks); /// @notice Get an array of all installed plugins. /// @return The addresses of all installed plugins. diff --git a/src/interfaces/IExecutionHook.sol b/src/interfaces/IExecutionHook.sol index 3240c489..9cb16482 100644 --- a/src/interfaces/IExecutionHook.sol +++ b/src/interfaces/IExecutionHook.sol @@ -4,22 +4,22 @@ pragma solidity ^0.8.25; import {IPlugin} from "./IPlugin.sol"; interface IExecutionHook is IPlugin { - /// @notice Run the pre execution hook specified by the `functionId`. + /// @notice Run the pre execution hook specified by the `entityId`. /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param functionId An identifier that routes the call to different internal implementations, should there be - /// more than one. + /// @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. /// @return Context to pass to a post execution hook, if present. An empty bytes array MAY be returned. - function preExecutionHook(uint8 functionId, address sender, uint256 value, bytes calldata data) + function preExecutionHook(uint32 entityId, address sender, uint256 value, bytes calldata data) external returns (bytes memory); - /// @notice Run the post execution hook specified by the `functionId`. + /// @notice Run the post execution hook specified by the `entityId`. /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param functionId An identifier that routes the call to different internal implementations, should there be - /// more than one. + /// @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(uint8 functionId, bytes calldata preExecHookData) external; + function postExecutionHook(uint32 entityId, bytes calldata preExecHookData) external; } diff --git a/src/interfaces/IPlugin.sol b/src/interfaces/IPlugin.sol index eb10e96b..824a1ddf 100644 --- a/src/interfaces/IPlugin.sol +++ b/src/interfaces/IPlugin.sol @@ -15,7 +15,7 @@ struct ManifestExecutionFunction { // todo: do we need these at all? Or do we fully switch to `installValidation`? struct ManifestValidation { - uint8 functionId; + uint32 entityId; bool isDefault; bool isSignatureValidation; bytes4[] selectors; @@ -24,7 +24,7 @@ struct ManifestValidation { struct ManifestExecutionHook { // TODO(erc6900 spec): These fields can be packed into a single word bytes4 executionSelector; - uint8 functionId; + uint32 entityId; bool isPreHook; bool isPostHook; } diff --git a/src/interfaces/IPluginManager.sol b/src/interfaces/IPluginManager.sol index bf1296e1..d2df72b5 100644 --- a/src/interfaces/IPluginManager.sol +++ b/src/interfaces/IPluginManager.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: CC0-1.0 pragma solidity ^0.8.25; -type FunctionReference is bytes21; +type PluginEntity is bytes24; -type ValidationConfig is bytes23; +type ValidationConfig is bytes26; interface IPluginManager { event PluginInstalled(address indexed plugin, bytes32 manifestHash); @@ -45,7 +45,7 @@ interface IPluginManager { /// data /// @param permissionHookUninstallData Optional data to be decoded and used by the plugin to clear account data function uninstallValidation( - FunctionReference validationFunction, + PluginEntity validationFunction, bytes calldata uninstallData, bytes calldata preValidationHookUninstallData, bytes calldata permissionHookUninstallData diff --git a/src/interfaces/IValidation.sol b/src/interfaces/IValidation.sol index 38c8a139..471cd2c1 100644 --- a/src/interfaces/IValidation.sol +++ b/src/interfaces/IValidation.sol @@ -6,26 +6,28 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import {IPlugin} from "./IPlugin.sol"; interface IValidation is IPlugin { - /// @notice Run the user operation validationFunction specified by the `functionId`. - /// @param functionId An identifier that routes the call to different internal implementations, should there be - /// more than one. + /// @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(uint8 functionId, PackedUserOperation calldata userOp, bytes32 userOpHash) + function validateUserOp(uint32 entityId, PackedUserOperation calldata userOp, bytes32 userOpHash) external returns (uint256); - /// @notice Run the runtime validationFunction specified by the `functionId`. + /// @notice Run the runtime validationFunction specified by the `entityId`. /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param functionId An identifier that routes the call to different internal implementations, should there be - /// more than one. + /// @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( - uint8 functionId, + address account, + uint32 entityId, address sender, uint256 value, bytes calldata data, @@ -34,14 +36,18 @@ interface IValidation is IPlugin { /// @notice Validates a signature using ERC-1271. /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param functionId An identifier that routes the call to different internal implementations, should there be - /// more than one. + /// @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(uint8 functionId, address sender, bytes32 hash, bytes calldata signature) - external - view - returns (bytes4); + function validateSignature( + address account, + uint32 entityId, + address sender, + bytes32 hash, + bytes calldata signature + ) external view returns (bytes4); } diff --git a/src/interfaces/IValidationHook.sol b/src/interfaces/IValidationHook.sol index 8300bbb8..dd7e2500 100644 --- a/src/interfaces/IValidationHook.sol +++ b/src/interfaces/IValidationHook.sol @@ -6,26 +6,26 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import {IPlugin} from "./IPlugin.sol"; interface IValidationHook is IPlugin { - /// @notice Run the pre user operation validation hook specified by the `functionId`. + /// @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 functionId An identifier that routes the call to different internal implementations, should there be - /// more than one. + /// @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(uint8 functionId, PackedUserOperation calldata userOp, bytes32 userOpHash) + function preUserOpValidationHook(uint32 entityId, PackedUserOperation calldata userOp, bytes32 userOpHash) external returns (uint256); - /// @notice Run the pre runtime validation hook specified by the `functionId`. + /// @notice Run the pre runtime validation hook specified by the `entityId`. /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param functionId An identifier that routes the call to different internal implementations, should there be - /// more than one. + /// @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( - uint8 functionId, + uint32 entityId, address sender, uint256 value, bytes calldata data, @@ -34,14 +34,14 @@ interface IValidationHook is IPlugin { // TODO: support this hook type within the account & in the manifest - /// @notice Run the pre signature validation hook specified by the `functionId`. + /// @notice Run the pre signature validation hook specified by the `entityId`. /// @dev To indicate the call should revert, the function MUST revert. - /// @param functionId An identifier that routes the call to different internal implementations, should there be - /// more than one. + /// @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(uint8 functionId, address sender, bytes32 hash, bytes calldata + // function preSignatureValidationHook(uint32 entityId, address sender, bytes32 hash, bytes calldata // signature) // external // view diff --git a/src/plugins/ERC20TokenLimitPlugin.sol b/src/plugins/ERC20TokenLimitPlugin.sol index 1df5bcfd..4e9c17ac 100644 --- a/src/plugins/ERC20TokenLimitPlugin.sol +++ b/src/plugins/ERC20TokenLimitPlugin.sol @@ -38,20 +38,20 @@ contract ERC20TokenLimitPlugin is BasePlugin, IExecutionHook { string internal constant _VERSION = "1.0.0"; string internal constant _AUTHOR = "ERC-6900 Authors"; - mapping(uint8 functionId => mapping(address token => mapping(address account => uint256 limit))) public limits; + mapping(uint32 entityId => mapping(address token => mapping(address account => uint256 limit))) public limits; AssociatedLinkedListSet internal _tokenList; error ExceededTokenLimit(); error ExceededNumberOfEntities(); error SelectorNotAllowed(); - function updateLimits(uint8 functionId, address token, uint256 newLimit) external { + function updateLimits(uint32 entityId, address token, uint256 newLimit) external { _tokenList.tryAdd(msg.sender, SetValue.wrap(bytes30(bytes20(token)))); - limits[functionId][token][msg.sender] = newLimit; + limits[entityId][token][msg.sender] = newLimit; } /// @inheritdoc IExecutionHook - function preExecutionHook(uint8 functionId, address, uint256, bytes calldata data) + function preExecutionHook(uint32 entityId, address, uint256, bytes calldata data) external override returns (bytes memory) @@ -61,13 +61,13 @@ contract ERC20TokenLimitPlugin is BasePlugin, IExecutionHook { if (selector == IStandardExecutor.execute.selector) { (address token,, bytes memory innerCalldata) = abi.decode(callData, (address, uint256, bytes)); if (_tokenList.contains(msg.sender, SetValue.wrap(bytes30(bytes20(token))))) { - _decrementLimit(functionId, token, innerCalldata); + _decrementLimit(entityId, token, innerCalldata); } } else if (selector == IStandardExecutor.executeBatch.selector) { Call[] memory calls = abi.decode(callData, (Call[])); for (uint256 i = 0; i < calls.length; i++) { if (_tokenList.contains(msg.sender, SetValue.wrap(bytes30(bytes20(calls[i].target))))) { - _decrementLimit(functionId, calls[i].target, calls[i].data); + _decrementLimit(entityId, calls[i].target, calls[i].data); } } } @@ -77,25 +77,25 @@ contract ERC20TokenLimitPlugin is BasePlugin, IExecutionHook { /// @inheritdoc IPlugin function onInstall(bytes calldata data) external override { - (uint8 startFunctionId, ERC20SpendLimit[] memory spendLimits) = - abi.decode(data, (uint8, ERC20SpendLimit[])); + (uint32 startEntityId, ERC20SpendLimit[] memory spendLimits) = + abi.decode(data, (uint32, ERC20SpendLimit[])); - if (startFunctionId + spendLimits.length > type(uint8).max) { + if (startEntityId + spendLimits.length > type(uint32).max) { revert ExceededNumberOfEntities(); } for (uint8 i = 0; i < spendLimits.length; i++) { _tokenList.tryAdd(msg.sender, SetValue.wrap(bytes30(bytes20(spendLimits[i].token)))); for (uint256 j = 0; j < spendLimits[i].limits.length; j++) { - limits[i + startFunctionId][spendLimits[i].token][msg.sender] = spendLimits[i].limits[j]; + limits[i + startEntityId][spendLimits[i].token][msg.sender] = spendLimits[i].limits[j]; } } } /// @inheritdoc IPlugin function onUninstall(bytes calldata data) external override { - (address token, uint8 functionId) = abi.decode(data, (address, uint8)); - delete limits[functionId][token][msg.sender]; + (address token, uint32 entityId) = abi.decode(data, (address, uint32)); + delete limits[entityId][token][msg.sender]; } function getTokensForAccount(address account) external view returns (address[] memory tokens) { @@ -108,7 +108,7 @@ contract ERC20TokenLimitPlugin is BasePlugin, IExecutionHook { } /// @inheritdoc IExecutionHook - function postExecutionHook(uint8, bytes calldata) external pure override { + function postExecutionHook(uint32, bytes calldata) external pure override { revert NotImplemented(); } @@ -133,7 +133,7 @@ contract ERC20TokenLimitPlugin is BasePlugin, IExecutionHook { return super.supportsInterface(interfaceId); } - function _decrementLimit(uint8 functionId, address token, bytes memory innerCalldata) internal { + function _decrementLimit(uint32 entityId, address token, bytes memory innerCalldata) internal { bytes4 selector; uint256 spend; assembly { @@ -141,12 +141,12 @@ contract ERC20TokenLimitPlugin is BasePlugin, IExecutionHook { spend := mload(add(innerCalldata, 68)) // 36:68 is recipient, 68:100 is spend } if (selector == IERC20.transfer.selector || selector == IERC20.approve.selector) { - uint256 limit = limits[functionId][token][msg.sender]; + uint256 limit = limits[entityId][token][msg.sender]; if (spend > limit) { revert ExceededTokenLimit(); } // solhint-disable-next-line reentrancy - limits[functionId][token][msg.sender] = limit - spend; + limits[entityId][token][msg.sender] = limit - spend; } else { revert SelectorNotAllowed(); } diff --git a/src/plugins/NativeTokenLimitPlugin.sol b/src/plugins/NativeTokenLimitPlugin.sol index 2b512c22..de0d9f3d 100644 --- a/src/plugins/NativeTokenLimitPlugin.sol +++ b/src/plugins/NativeTokenLimitPlugin.sol @@ -34,8 +34,8 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { error ExceededNativeTokenLimit(); error ExceededNumberOfEntities(); - function updateLimits(uint8 functionId, uint256 newLimit) external { - limits[functionId][msg.sender] = newLimit; + function updateLimits(uint32 entityId, uint256 newLimit) external { + limits[entityId][msg.sender] = newLimit; } function updateSpecialPaymaster(address paymaster, bool allowed) external { @@ -43,7 +43,7 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { } /// @inheritdoc IValidationHook - function preUserOpValidationHook(uint8 functionId, PackedUserOperation calldata userOp, bytes32) + function preUserOpValidationHook(uint32 entityId, PackedUserOperation calldata userOp, bytes32) external returns (uint256) { @@ -63,54 +63,54 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { uint256 totalGas = userOp.preVerificationGas + vgl + cgl + pvgl + ppogl; uint256 usage = totalGas * UserOperationLib.unpackMaxFeePerGas(userOp); - uint256 limit = limits[functionId][msg.sender]; + uint256 limit = limits[entityId][msg.sender]; if (usage > limit) { revert ExceededNativeTokenLimit(); } - limits[functionId][msg.sender] = limit - usage; + limits[entityId][msg.sender] = limit - usage; } return 0; } /// @inheritdoc IExecutionHook - function preExecutionHook(uint8 functionId, address, uint256, bytes calldata data) + function preExecutionHook(uint32 entityId, address, uint256, bytes calldata data) external override returns (bytes memory) { - return _checkAndDecrementLimit(functionId, data); + return _checkAndDecrementLimit(entityId, data); } /// @inheritdoc IPlugin function onInstall(bytes calldata data) external override { - (uint8 startFunctionId, uint256[] memory spendLimits) = abi.decode(data, (uint8, uint256[])); + (uint32 startEntityId, uint256[] memory spendLimits) = abi.decode(data, (uint32, uint256[])); - if (startFunctionId + spendLimits.length > type(uint8).max) { + if (startEntityId + spendLimits.length > type(uint32).max) { revert ExceededNumberOfEntities(); } for (uint256 i = 0; i < spendLimits.length; i++) { - limits[i + startFunctionId][msg.sender] = spendLimits[i]; + limits[i + startEntityId][msg.sender] = spendLimits[i]; } } /// @inheritdoc IPlugin function onUninstall(bytes calldata data) external override { - // This is the highest functionId that's being used by the account - uint8 functionId = abi.decode(data, (uint8)); - for (uint256 i = 0; i < functionId; i++) { + // This is the highest entityId that's being used by the account + uint32 entityId = abi.decode(data, (uint32)); + for (uint256 i = 0; i < entityId; i++) { delete limits[i][msg.sender]; } } /// @inheritdoc IExecutionHook - function postExecutionHook(uint8, bytes calldata) external pure override { + function postExecutionHook(uint32, bytes calldata) external pure override { revert NotImplemented(); } // No implementation, no revert // Runtime spends no account gas, and we check native token spend limits in exec hooks - function preRuntimeValidationHook(uint8, address, uint256, bytes calldata, bytes calldata) + function preRuntimeValidationHook(uint32, address, uint256, bytes calldata, bytes calldata) external pure override @@ -142,7 +142,7 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { return interfaceId == type(IExecutionHook).interfaceId || super.supportsInterface(interfaceId); } - function _checkAndDecrementLimit(uint8 functionId, bytes calldata data) internal returns (bytes memory) { + function _checkAndDecrementLimit(uint32 entityId, bytes calldata data) internal returns (bytes memory) { (bytes4 selector, bytes memory callData) = _getSelectorAndCalldata(data); uint256 value; @@ -156,11 +156,11 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { } } - uint256 limit = limits[functionId][msg.sender]; + uint256 limit = limits[entityId][msg.sender]; if (value > limit) { revert ExceededNativeTokenLimit(); } - limits[functionId][msg.sender] = limit - value; + limits[entityId][msg.sender] = limit - value; return ""; } diff --git a/src/plugins/owner/SingleOwnerPlugin.sol b/src/plugins/owner/SingleOwnerPlugin.sol deleted file mode 100644 index dbb5d56a..00000000 --- a/src/plugins/owner/SingleOwnerPlugin.sol +++ /dev/null @@ -1,188 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.25; - -import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; -import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; -import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; - -import {PluginManifest, PluginMetadata, SelectorPermission} from "../../interfaces/IPlugin.sol"; -import {IPlugin} from "../../interfaces/IPlugin.sol"; -import {IValidation} from "../../interfaces/IValidation.sol"; -import {BasePlugin, IERC165} from "../BasePlugin.sol"; - -/// @title Single Owner Plugin -/// @author ERC-6900 Authors -/// @notice This plugin allows an EOA or smart contract to own a modular account. -/// It also supports [ERC-1271](https://eips.ethereum.org/EIPS/eip-1271) signature -/// validation for both validating the signature on user operations and in -/// exposing its own `isValidSignature` method. This only works when the owner of -/// modular account also support ERC-1271. -/// -/// ERC-4337's bundler validation rules limit the types of contracts that can be -/// used as owners to validate user operation signatures. For example, the -/// contract's `isValidSignature` function may not use any forbidden opcodes -/// such as `TIMESTAMP` or `NUMBER`, and the contract may not be an ERC-1967 -/// proxy as it accesses a constant implementation slot not associated with -/// the account, violating storage access rules. This also means that the -/// owner of a modular account may not be another modular account if you want to -/// send user operations through a bundler. -contract SingleOwnerPlugin is IValidation, BasePlugin { - using ECDSA for bytes32; - using MessageHashUtils for bytes32; - - string internal constant _NAME = "Single Owner Plugin"; - string internal constant _VERSION = "1.0.0"; - string internal constant _AUTHOR = "ERC-6900 Authors"; - - uint256 internal constant _SIG_VALIDATION_PASSED = 0; - uint256 internal constant _SIG_VALIDATION_FAILED = 1; - - // bytes4(keccak256("isValidSignature(bytes32,bytes)")) - bytes4 internal constant _1271_MAGIC_VALUE = 0x1626ba7e; - bytes4 internal constant _1271_INVALID = 0xffffffff; - - mapping(uint8 id => mapping(address account => address)) public owners; - - /// @notice This event is emitted when ownership of the account changes. - /// @param account The account whose ownership changed. - /// @param previousOwner The address of the previous owner. - /// @param newOwner The address of the new owner. - event OwnershipTransferred(address indexed account, address indexed previousOwner, address indexed newOwner); - - error AlreadyInitialized(); - error NotAuthorized(); - error NotInitialized(); - - // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Execution functions ┃ - // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - - /// @notice Transfer ownership of an account and ID to `newOwner`. The caller address (`msg.sender`) is user to - /// identify the account. - /// @param newOwner The address of the new owner. - function transferOwnership(uint8 id, address newOwner) external { - _transferOwnership(id, newOwner); - } - - // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Plugin interface functions ┃ - // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - - /// @inheritdoc IPlugin - function onInstall(bytes calldata data) external override { - (uint8 id, address owner) = abi.decode(data, (uint8, address)); - _transferOwnership(id, owner); - } - - /// @inheritdoc IPlugin - function onUninstall(bytes calldata data) external override { - uint8 id = abi.decode(data, (uint8)); - _transferOwnership(id, address(0)); - } - - /// @inheritdoc IValidation - function validateRuntime(uint8 functionId, address sender, uint256, bytes calldata, bytes calldata) - external - view - override - { - // Validate that the sender is the owner of the account or self. - if (sender != owners[functionId][msg.sender]) { - revert NotAuthorized(); - } - return; - } - - /// @inheritdoc IValidation - function validateUserOp(uint8 functionId, PackedUserOperation calldata userOp, bytes32 userOpHash) - external - view - override - returns (uint256) - { - // Validate the user op signature against the owner. - if ( - SignatureChecker.isValidSignatureNow( - owners[functionId][msg.sender], userOpHash.toEthSignedMessageHash(), userOp.signature - ) - ) { - return _SIG_VALIDATION_PASSED; - } - return _SIG_VALIDATION_FAILED; - } - - // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Execution view functions ┃ - // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - - /// @inheritdoc IValidation - /// @dev The signature is valid if it is signed by the owner's private key - /// (if the owner is an EOA) or if it is a valid ERC-1271 signature from the - /// owner (if the owner is a contract). Note that unlike the signature - /// validation used in `validateUserOp`, this does///*not** wrap the digest in - /// an "Ethereum Signed Message" envelope before checking the signature in - /// the EOA-owner case. - function validateSignature(uint8 functionId, address, bytes32 digest, bytes calldata signature) - external - view - override - returns (bytes4) - { - if (SignatureChecker.isValidSignatureNow(owners[functionId][msg.sender], digest, signature)) { - return _1271_MAGIC_VALUE; - } - return _1271_INVALID; - } - - // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Plugin view functions ┃ - // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - - /// @inheritdoc IPlugin - function pluginManifest() external pure override returns (PluginManifest memory) { - PluginManifest memory manifest; - return manifest; - } - - /// @inheritdoc IPlugin - function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { - PluginMetadata memory metadata; - metadata.name = _NAME; - metadata.version = _VERSION; - metadata.author = _AUTHOR; - - // Permission strings - string memory modifyOwnershipPermission = "Modify Ownership"; - - // Permission descriptions - metadata.permissionDescriptors = new SelectorPermission[](1); - metadata.permissionDescriptors[0] = SelectorPermission({ - functionSelector: this.transferOwnership.selector, - permissionDescription: modifyOwnershipPermission - }); - - return metadata; - } - - // ┏━━━━━━━━━━━━━━━┓ - // ┃ EIP-165 ┃ - // ┗━━━━━━━━━━━━━━━┛ - - /// @inheritdoc BasePlugin - function supportsInterface(bytes4 interfaceId) public view override(BasePlugin, IERC165) returns (bool) { - return interfaceId == type(IValidation).interfaceId || super.supportsInterface(interfaceId); - } - - // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Internal / Private functions ┃ - // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - - // Transfers ownership and emits and event. - function _transferOwnership(uint8 id, address newOwner) internal { - address previousOwner = owners[id][msg.sender]; - owners[id][msg.sender] = newOwner; - // Todo: include id in event - emit OwnershipTransferred(msg.sender, previousOwner, newOwner); - } -} diff --git a/src/plugins/validation/ISingleSignerValidation.sol b/src/plugins/validation/ISingleSignerValidation.sol new file mode 100644 index 00000000..2653b752 --- /dev/null +++ b/src/plugins/validation/ISingleSignerValidation.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.25; + +import {IValidation} from "../../interfaces/IValidation.sol"; + +interface ISingleSignerValidation is IValidation { + /// @notice This event is emitted when Signer of the account's validation changes. + /// @param account The account whose validation Signer changed. + /// @param entityId The entityId for the account and the signer. + /// @param previousSigner The address of the previous signer. + /// @param newSigner The address of the new signer. + event SignerTransferred( + address indexed account, uint32 indexed entityId, address previousSigner, address newSigner + ); + + error NotAuthorized(); + + /// @notice Transfer Signer of the account's validation to `newSigner`. + /// @param entityId The entityId for the account and the signer. + /// @param newSigner The address of the new signer. + function transferSigner(uint32 entityId, address newSigner) external; + + /// @notice Get the signer of the `account`'s validation. + /// @param entityId The entityId for the account and the signer. + /// @param account The account to get the signer of. + /// @return The address of the signer. + function signerOf(uint32 entityId, address account) external view returns (address); +} diff --git a/src/plugins/validation/SingleSignerValidation.sol b/src/plugins/validation/SingleSignerValidation.sol new file mode 100644 index 00000000..5ae4517d --- /dev/null +++ b/src/plugins/validation/SingleSignerValidation.sol @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.25; + +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; +import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; + +import {IPlugin, PluginManifest, PluginMetadata} from "../../interfaces/IPlugin.sol"; +import {IValidation} from "../../interfaces/IValidation.sol"; +import {BasePlugin} from "../BasePlugin.sol"; +import {ISingleSignerValidation} from "./ISingleSignerValidation.sol"; + +/// @title ECSDA Validation +/// @author ERC-6900 Authors +/// @notice This validation enables any ECDSA (secp256k1 curve) signature validation. It handles installation by +/// each entity (entityId). +/// Note: Uninstallation will NOT disable all installed validation entities. None of the functions are installed on +/// the account. Account states are to be retrieved from this global singleton directly. +/// +/// - This validation supports ERC-1271. The signature is valid if it is signed by the owner's private key +/// (if the owner is an EOA) or if it is a valid ERC-1271 signature from the +/// owner (if the owner is a contract). +/// +/// - This validation supports composition that other validation can relay on entities in this validation +/// to validate partially or fully. +contract SingleSignerValidation is ISingleSignerValidation, BasePlugin { + using ECDSA for bytes32; + using MessageHashUtils for bytes32; + + string public constant NAME = "SingleSigner Validation"; + string public constant VERSION = "1.0.0"; + string public constant AUTHOR = "ERC-6900 Authors"; + + uint256 internal constant _SIG_VALIDATION_PASSED = 0; + uint256 internal constant _SIG_VALIDATION_FAILED = 1; + + // bytes4(keccak256("isValidSignature(bytes32,bytes)")) + bytes4 internal constant _1271_MAGIC_VALUE = 0x1626ba7e; + bytes4 internal constant _1271_INVALID = 0xffffffff; + + mapping(uint32 entityId => mapping(address account => address)) public signer; + + /// @inheritdoc ISingleSignerValidation + function signerOf(uint32 entityId, address account) external view returns (address) { + return signer[entityId][account]; + } + + /// @inheritdoc ISingleSignerValidation + function transferSigner(uint32 entityId, address newSigner) external { + _transferSigner(entityId, newSigner); + } + + // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ + // ┃ Plugin interface functions ┃ + // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + + /// @inheritdoc IPlugin + function pluginManifest() external pure override returns (PluginManifest memory) { + PluginManifest memory manifest; + return manifest; + } + + /// @inheritdoc IPlugin + function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { + PluginMetadata memory metadata; + metadata.name = NAME; + metadata.version = VERSION; + metadata.author = AUTHOR; + return metadata; + } + + /// @inheritdoc IPlugin + function onInstall(bytes calldata data) external override { + (uint32 entityId, address newSigner) = abi.decode(data, (uint32, address)); + _transferSigner(entityId, newSigner); + } + + /// @inheritdoc IPlugin + function onUninstall(bytes calldata data) external override { + // ToDo: what does it mean in the world of composable validation world to uninstall one type of validation + // We can either get rid of all SingleSigner signers. What about the nested ones? + _transferSigner(abi.decode(data, (uint32)), address(0)); + } + + /// @inheritdoc IValidation + function validateUserOp(uint32 entityId, PackedUserOperation calldata userOp, bytes32 userOpHash) + external + view + override + returns (uint256) + { + // Validate the user op signature against the owner. + (address sigSigner,,) = (userOpHash.toEthSignedMessageHash()).tryRecover(userOp.signature); + if (sigSigner == address(0) || sigSigner != signer[entityId][userOp.sender]) { + return _SIG_VALIDATION_FAILED; + } + return _SIG_VALIDATION_PASSED; + } + + /// @inheritdoc IValidation + function validateRuntime( + address account, + uint32 entityId, + address sender, + uint256, + bytes calldata, + bytes calldata + ) external view override { + // Validate that the sender is the owner of the account or self. + if (sender != signer[entityId][account]) { + revert NotAuthorized(); + } + return; + } + + /// @inheritdoc IValidation + /// @dev The signature is valid if it is signed by the owner's private key + /// (if the owner is an EOA) or if it is a valid ERC-1271 signature from the + /// owner (if the owner is a contract). Note that unlike the signature + /// validation used in `validateUserOp`, this does///*not** wrap the digest in + /// an "Ethereum Signed Message" envelope before checking the signature in + /// the EOA-owner case. + function validateSignature(address account, uint32 entityId, address, bytes32 digest, bytes calldata signature) + external + view + override + returns (bytes4) + { + if (SignatureChecker.isValidSignatureNow(signer[entityId][account], digest, signature)) { + return _1271_MAGIC_VALUE; + } + return _1271_INVALID; + } + + // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ + // ┃ Internal / Private functions ┃ + // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + + function _transferSigner(uint32 entityId, address newSigner) internal { + address previousSigner = signer[entityId][msg.sender]; + signer[entityId][msg.sender] = newSigner; + emit SignerTransferred(msg.sender, entityId, previousSigner, newSigner); + } +} diff --git a/src/samples/permissionhooks/AllowlistPlugin.sol b/src/samples/permissionhooks/AllowlistPlugin.sol index 209d8370..2d86ca18 100644 --- a/src/samples/permissionhooks/AllowlistPlugin.sol +++ b/src/samples/permissionhooks/AllowlistPlugin.sol @@ -9,7 +9,7 @@ import {IStandardExecutor, Call} from "../../interfaces/IStandardExecutor.sol"; import {BasePlugin} from "../../plugins/BasePlugin.sol"; contract AllowlistPlugin is IValidationHook, BasePlugin { - enum FunctionId { + enum EntityId { PRE_VALIDATION_HOOK } @@ -68,25 +68,25 @@ contract AllowlistPlugin is IValidationHook, BasePlugin { selectorAllowlist[target][selector][msg.sender] = allowed; } - function preUserOpValidationHook(uint8 functionId, PackedUserOperation calldata userOp, bytes32) + function preUserOpValidationHook(uint32 entityId, PackedUserOperation calldata userOp, bytes32) external view override returns (uint256) { - if (functionId == uint8(FunctionId.PRE_VALIDATION_HOOK)) { + if (entityId == uint32(EntityId.PRE_VALIDATION_HOOK)) { _checkAllowlistCalldata(userOp.callData); return 0; } revert NotImplemented(); } - function preRuntimeValidationHook(uint8 functionId, address, uint256, bytes calldata data, bytes calldata) + function preRuntimeValidationHook(uint32 entityId, address, uint256, bytes calldata data, bytes calldata) external view override { - if (functionId == uint8(FunctionId.PRE_VALIDATION_HOOK)) { + if (entityId == uint32(EntityId.PRE_VALIDATION_HOOK)) { _checkAllowlistCalldata(data); return; } diff --git a/standard/ERCs/erc-6900.md b/standard/ERCs/erc-6900.md index 06b51e4d..fed6a625 100644 --- a/standard/ERCs/erc-6900.md +++ b/standard/ERCs/erc-6900.md @@ -106,10 +106,10 @@ Plugin manager interface. Modular Smart Contract Accounts **MUST** implement thi ```solidity // Treats the first 20 bytes as an address, and the last byte as a function identifier. -type FunctionReference is bytes21; +type PluginEntity is bytes21; interface IPluginManager { - event PluginInstalled(address indexed plugin, bytes32 manifestHash, FunctionReference[] dependencies); + event PluginInstalled(address indexed plugin, bytes32 manifestHash, PluginEntity[] dependencies); event PluginUninstalled(address indexed plugin, bool indexed onUninstallSucceeded); @@ -118,13 +118,13 @@ interface IPluginManager { /// @param manifestHash The hash of the plugin manifest. /// @param pluginInstallData Optional data to be decoded and used by the plugin to setup initial plugin data /// for the modular account. - /// @param dependencies The dependencies of the plugin, as described in the manifest. Each FunctionReference + /// @param dependencies The dependencies of the plugin, as described in the manifest. Each PluginEntity /// MUST be composed of an installed plugin's address and a function ID of its validation function. function installPlugin( address plugin, bytes32 manifestHash, bytes calldata pluginInstallData, - FunctionReference[] calldata dependencies + PluginEntity[] calldata dependencies ) external; /// @notice Uninstall a plugin from the modular account. @@ -215,13 +215,13 @@ interface IAccountLoupe { /// @notice Config for an execution function, given a selector. struct ExecutionFunctionConfig { address plugin; - FunctionReference validationFunction; + PluginEntity validationFunction; } /// @notice Pre and post hooks for a given selector. /// @dev It's possible for one of either `preExecHook` or `postExecHook` to be empty. struct ExecutionHooks { - FunctionReference hookFunction; + PluginEntity hookFunction; bool isPreHook; bool isPostHook; } @@ -243,7 +243,7 @@ interface IAccountLoupe { function getPreValidationHooks(bytes4 selector) external view - returns (FunctionReference[] memory preValidationHooks); + returns (PluginEntity[] memory preValidationHooks); /// @notice Get an array of all installed plugins. /// @return The addresses of all installed plugins. @@ -267,56 +267,56 @@ interface IPlugin { /// @param data Optional bytes array to be decoded and used by the plugin to clear plugin data for the modular account. function onUninstall(bytes calldata data) external; - /// @notice Run the pre user operation validation hook specified by the `functionId`. + /// @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 functionId An identifier that routes the call to different internal implementations, should there be more than one. + /// @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(uint8 functionId, PackedUserOperation memory userOp, bytes32 userOpHash) external returns (uint256); + function preUserOpValidationHook(uint8 entityId, PackedUserOperation memory userOp, bytes32 userOpHash) external returns (uint256); - /// @notice Run the user operation validationFunction specified by the `functionId`. - /// @param functionId An identifier that routes the call to different internal implementations, should there be + /// @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 userOpValidationFunction(uint8 functionId, PackedUserOperation calldata userOp, bytes32 userOpHash) + function userOpValidationFunction(uint8 entityId, PackedUserOperation calldata userOp, bytes32 userOpHash) external returns (uint256); - /// @notice Run the pre runtime validation hook specified by the `functionId`. + /// @notice Run the pre runtime validation hook specified by the `entityId`. /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param functionId An identifier that routes the call to different internal implementations, should there be more than one. + /// @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(uint8 functionId, address sender, uint256 value, bytes calldata data) external; + function preRuntimeValidationHook(uint8 entityId, address sender, uint256 value, bytes calldata data) external; - /// @notice Run the runtime validationFunction specified by the `functionId`. + /// @notice Run the runtime validationFunction specified by the `entityId`. /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param functionId An identifier that routes the call to different internal implementations, should there be + /// @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 runtimeValidationFunction(uint8 functionId, address sender, uint256 value, bytes calldata data) + function runtimeValidationFunction(uint8 entityId, address sender, uint256 value, bytes calldata data) external; - /// @notice Run the pre execution hook specified by the `functionId`. + /// @notice Run the pre execution hook specified by the `entityId`. /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param functionId An identifier that routes the call to different internal implementations, should there be more than one. + /// @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. /// @return Context to pass to a post execution hook, if present. An empty bytes array MAY be returned. - function preExecutionHook(uint8 functionId, address sender, uint256 value, bytes calldata data) external returns (bytes memory); + function preExecutionHook(uint32 entityId, address sender, uint256 value, bytes calldata data) external returns (bytes memory); - /// @notice Run the post execution hook specified by the `functionId`. + /// @notice Run the post execution hook specified by the `entityId`. /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param functionId An identifier that routes the call to different internal implementations, should there be more than one. + /// @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(uint8 functionId, bytes calldata preExecHookData) external; + function postExecutionHook(uint32 entityId, bytes calldata preExecHookData) external; /// @notice Describe the contents and intended configuration of the plugin. /// @dev This manifest MUST stay constant over time. @@ -359,7 +359,7 @@ enum ManifestAssociatedFunctionType { /// of the function at `dependencies[dependencyIndex]` during the call to `installPlugin(config)`. struct ManifestFunction { ManifestAssociatedFunctionType functionType; - uint8 functionId; + uint8 entityId; uint256 dependencyIndex; } @@ -371,7 +371,7 @@ struct ManifestAssociatedFunction { struct ManifestExecutionHook { // TODO(erc6900 spec): These fields can be packed into a single word bytes4 executionSelector; - uint8 functionId; + uint8 entityId; bool isPreHook; bool isPostHook; } @@ -443,7 +443,7 @@ The following behavior MUST be followed: #### Calls to `installPlugin` -The function `installPlugin` accepts 4 parameters: the address of the plugin to install, the Keccak-256 hash of the plugin's manifest, ABI-encoded data to pass to the plugin's `onInstall` callback, and an array of function references that represent the plugin's install dependencies. +The function `installPlugin` accepts 3 parameters: the address of the plugin to install, the Keccak-256 hash of the plugin's manifest, ABI-encoded data to pass to the plugin's `onInstall` callback. The function MUST retrieve the plugin's manifest by calling `pluginManifest()` using `staticcall`. @@ -513,7 +513,7 @@ Additionally, when the modular account natively implements functions in `IPlugin The steps to perform are: - If the call is not from the `EntryPoint`, then find an associated runtime validation function. If one does not exist, execution MUST revert. The modular account MUST execute all pre runtime validation hooks, then the runtime validation function, with the `call` opcode. All of these functions MUST receive the caller, value, and execution function's calldata as parameters. If any of these functions revert, execution MUST revert. If any pre execution hooks are set to `PRE_HOOK_ALWAYS_DENY`, execution MUST revert. If the validation function is set to `RUNTIME_VALIDATION_ALWAYS_ALLOW`, the runtime validation function MUST be bypassed. -- If there are pre execution hooks defined for the execution function, execute those hooks with the caller, value, and execution function's calldata as parameters. If any of these hooks returns data, it MUST be preserved until the call to the post execution hook. The operation MUST be done with the `call` opcode. If there are duplicate pre execution hooks (i.e., hooks with identical `FunctionReference`s), run the hook only once. If any of these functions revert, execution MUST revert. +- If there are pre execution hooks defined for the execution function, execute those hooks with the caller, value, and execution function's calldata as parameters. If any of these hooks returns data, it MUST be preserved until the call to the post execution hook. The operation MUST be done with the `call` opcode. If there are duplicate pre execution hooks (i.e., hooks with identical `PluginEntity`s), run the hook only once. If any of these functions revert, execution MUST revert. - Run the execution function. - If any post execution hooks are defined, run the functions. If a pre execution hook returned data to the account, that data MUST be passed as a parameter to the associated post execution hook. The operation MUST be done with the `call` opcode. If there are duplicate post execution hooks, run them once for each unique associated pre execution hook. For post execution hooks without an associated pre execution hook, run the hook only once. If any of these functions revert, execution MUST revert. diff --git a/test/account/AccountExecHooks.t.sol b/test/account/AccountExecHooks.t.sol index 14ad57fc..98d16dcf 100644 --- a/test/account/AccountExecHooks.t.sol +++ b/test/account/AccountExecHooks.t.sol @@ -18,9 +18,9 @@ contract AccountExecHooksTest is AccountTestBase { bytes32 public manifestHash2; bytes4 internal constant _EXEC_SELECTOR = bytes4(uint32(1)); - uint8 internal constant _PRE_HOOK_FUNCTION_ID_1 = 1; - uint8 internal constant _POST_HOOK_FUNCTION_ID_2 = 2; - uint8 internal constant _BOTH_HOOKS_FUNCTION_ID_3 = 3; + uint32 internal constant _PRE_HOOK_FUNCTION_ID_1 = 1; + uint32 internal constant _POST_HOOK_FUNCTION_ID_2 = 2; + uint32 internal constant _BOTH_HOOKS_FUNCTION_ID_3 = 3; PluginManifest internal _m1; @@ -45,7 +45,7 @@ contract AccountExecHooksTest is AccountTestBase { _installPlugin1WithHooks( ManifestExecutionHook({ executionSelector: _EXEC_SELECTOR, - functionId: _PRE_HOOK_FUNCTION_ID_1, + entityId: _PRE_HOOK_FUNCTION_ID_1, isPreHook: true, isPostHook: false }) @@ -83,7 +83,7 @@ contract AccountExecHooksTest is AccountTestBase { _installPlugin1WithHooks( ManifestExecutionHook({ executionSelector: _EXEC_SELECTOR, - functionId: _BOTH_HOOKS_FUNCTION_ID_3, + entityId: _BOTH_HOOKS_FUNCTION_ID_3, isPreHook: true, isPostHook: true }) @@ -131,7 +131,7 @@ contract AccountExecHooksTest is AccountTestBase { _installPlugin1WithHooks( ManifestExecutionHook({ executionSelector: _EXEC_SELECTOR, - functionId: _POST_HOOK_FUNCTION_ID_2, + entityId: _POST_HOOK_FUNCTION_ID_2, isPreHook: false, isPostHook: true }) diff --git a/test/account/AccountLoupe.t.sol b/test/account/AccountLoupe.t.sol index 69563b51..f0670848 100644 --- a/test/account/AccountLoupe.t.sol +++ b/test/account/AccountLoupe.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; -import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; +import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; import {IPluginManager} from "../../src/interfaces/IPluginManager.sol"; import {IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; @@ -69,9 +69,8 @@ contract AccountLoupeTest is CustomValidationTestBase { } function test_pluginLoupe_getSelectors() public { - FunctionReference comprehensivePluginValidation = FunctionReferenceLib.pack( - address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.VALIDATION) - ); + PluginEntity comprehensivePluginValidation = + PluginEntityLib.pack(address(comprehensivePlugin), uint32(ComprehensivePlugin.EntityId.VALIDATION)); bytes4[] memory selectors = account1.getSelectors(comprehensivePluginValidation); @@ -83,22 +82,22 @@ contract AccountLoupeTest is CustomValidationTestBase { ExecutionHook[] memory hooks = account1.getExecutionHooks(comprehensivePlugin.foo.selector); ExecutionHook[3] memory expectedHooks = [ ExecutionHook({ - hookFunction: FunctionReferenceLib.pack( - address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.BOTH_EXECUTION_HOOKS) + hookFunction: PluginEntityLib.pack( + address(comprehensivePlugin), uint32(ComprehensivePlugin.EntityId.BOTH_EXECUTION_HOOKS) ), isPreHook: true, isPostHook: true }), ExecutionHook({ - hookFunction: FunctionReferenceLib.pack( - address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.PRE_EXECUTION_HOOK) + hookFunction: PluginEntityLib.pack( + address(comprehensivePlugin), uint32(ComprehensivePlugin.EntityId.PRE_EXECUTION_HOOK) ), isPreHook: true, isPostHook: false }), ExecutionHook({ - hookFunction: FunctionReferenceLib.pack( - address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.POST_EXECUTION_HOOK) + hookFunction: PluginEntityLib.pack( + address(comprehensivePlugin), uint32(ComprehensivePlugin.EntityId.POST_EXECUTION_HOOK) ), isPreHook: false, isPostHook: true @@ -108,8 +107,7 @@ contract AccountLoupeTest is CustomValidationTestBase { assertEq(hooks.length, 3); for (uint256 i = 0; i < hooks.length; i++) { assertEq( - FunctionReference.unwrap(hooks[i].hookFunction), - FunctionReference.unwrap(expectedHooks[i].hookFunction) + PluginEntity.unwrap(hooks[i].hookFunction), PluginEntity.unwrap(expectedHooks[i].hookFunction) ); assertEq(hooks[i].isPreHook, expectedHooks[i].isPreHook); assertEq(hooks[i].isPostHook, expectedHooks[i].isPostHook); @@ -117,22 +115,22 @@ contract AccountLoupeTest is CustomValidationTestBase { } function test_pluginLoupe_getValidationHooks() public { - FunctionReference[] memory hooks = account1.getPreValidationHooks(_ownerValidation); + PluginEntity[] memory hooks = account1.getPreValidationHooks(_signerValidation); assertEq(hooks.length, 2); assertEq( - FunctionReference.unwrap(hooks[0]), - FunctionReference.unwrap( - FunctionReferenceLib.pack( - address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.PRE_VALIDATION_HOOK_1) + PluginEntity.unwrap(hooks[0]), + PluginEntity.unwrap( + PluginEntityLib.pack( + address(comprehensivePlugin), uint32(ComprehensivePlugin.EntityId.PRE_VALIDATION_HOOK_1) ) ) ); assertEq( - FunctionReference.unwrap(hooks[1]), - FunctionReference.unwrap( - FunctionReferenceLib.pack( - address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.PRE_VALIDATION_HOOK_2) + PluginEntity.unwrap(hooks[1]), + PluginEntity.unwrap( + PluginEntityLib.pack( + address(comprehensivePlugin), uint32(ComprehensivePlugin.EntityId.PRE_VALIDATION_HOOK_2) ) ) ); @@ -144,19 +142,19 @@ contract AccountLoupeTest is CustomValidationTestBase { internal virtual override - returns (FunctionReference, bool, bool, bytes4[] memory, bytes memory, bytes memory, bytes memory) + returns (PluginEntity, bool, bool, bytes4[] memory, bytes memory, bytes memory, bytes memory) { - FunctionReference[] memory preValidationHooks = new FunctionReference[](2); - preValidationHooks[0] = FunctionReferenceLib.pack( - address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.PRE_VALIDATION_HOOK_1) + PluginEntity[] memory preValidationHooks = new PluginEntity[](2); + preValidationHooks[0] = PluginEntityLib.pack( + address(comprehensivePlugin), uint32(ComprehensivePlugin.EntityId.PRE_VALIDATION_HOOK_1) ); - preValidationHooks[1] = FunctionReferenceLib.pack( - address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.PRE_VALIDATION_HOOK_2) + preValidationHooks[1] = PluginEntityLib.pack( + address(comprehensivePlugin), uint32(ComprehensivePlugin.EntityId.PRE_VALIDATION_HOOK_2) ); bytes[] memory installDatas = new bytes[](2); return ( - _ownerValidation, + _signerValidation, true, true, new bytes4[](0), diff --git a/test/account/AccountReturnData.t.sol b/test/account/AccountReturnData.t.sol index 1738c722..bb081325 100644 --- a/test/account/AccountReturnData.t.sol +++ b/test/account/AccountReturnData.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; +import {PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; import {Call} from "../../src/interfaces/IStandardExecutor.sol"; import { @@ -10,7 +10,7 @@ import { ResultConsumerPlugin } from "../mocks/plugins/ReturnDataPluginMocks.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; -import {TEST_DEFAULT_OWNER_FUNCTION_ID} from "../utils/TestConstants.sol"; +import {TEST_DEFAULT_VALIDATION_ENTITY_ID} from "../utils/TestConstants.sol"; // Tests all the different ways that return data can be read from plugins through an account contract AccountReturnDataTest is AccountTestBase { @@ -58,7 +58,7 @@ contract AccountReturnDataTest is AccountTestBase { (address(regularResultContract), 0, abi.encodeCall(RegularResultContract.foo, ())) ), _encodeSignature( - FunctionReferenceLib.pack(address(singleOwnerPlugin), TEST_DEFAULT_OWNER_FUNCTION_ID), + PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, "" ) @@ -86,7 +86,7 @@ contract AccountReturnDataTest is AccountTestBase { bytes memory retData = account1.executeWithAuthorization( abi.encodeCall(account1.executeBatch, (calls)), _encodeSignature( - FunctionReferenceLib.pack(address(singleOwnerPlugin), TEST_DEFAULT_OWNER_FUNCTION_ID), + PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, "" ) diff --git a/test/account/GlobalValidationTest.t.sol b/test/account/GlobalValidationTest.t.sol index 9f40f806..7ef7fdc5 100644 --- a/test/account/GlobalValidationTest.t.sol +++ b/test/account/GlobalValidationTest.t.sol @@ -5,7 +5,7 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; +import {PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; @@ -26,7 +26,8 @@ contract GlobalValidationTest is AccountTestBase { account2 = UpgradeableModularAccount(payable(factory.getAddress(owner2, 0))); vm.deal(address(account2), 100 ether); - _ownerValidation = FunctionReferenceLib.pack(address(singleOwnerPlugin), TEST_DEFAULT_OWNER_FUNCTION_ID); + _signerValidation = + PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID); ethRecipient = makeAddr("ethRecipient"); vm.deal(ethRecipient, 1 wei); @@ -48,7 +49,7 @@ contract GlobalValidationTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature(_signerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -65,7 +66,7 @@ contract GlobalValidationTest is AccountTestBase { vm.prank(owner2); account2.executeWithAuthorization( abi.encodeCall(UpgradeableModularAccount.execute, (ethRecipient, 1 wei, "")), - _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, "") + _encodeSignature(_signerValidation, GLOBAL_VALIDATION, "") ); assertEq(ethRecipient.balance, 2 wei); diff --git a/test/account/MultiValidation.t.sol b/test/account/MultiValidation.t.sol index 5254c50d..641fcefb 100644 --- a/test/account/MultiValidation.t.sol +++ b/test/account/MultiValidation.t.sol @@ -8,26 +8,26 @@ import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/Messa import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {FunctionReference} from "../../src/interfaces/IPluginManager.sol"; +import {PluginEntity} from "../../src/interfaces/IPluginManager.sol"; import {IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; -import {FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; +import {PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; -import {SingleOwnerPlugin} from "../../src/plugins/owner/SingleOwnerPlugin.sol"; +import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; -import {TEST_DEFAULT_OWNER_FUNCTION_ID} from "../utils/TestConstants.sol"; +import {TEST_DEFAULT_VALIDATION_ENTITY_ID} from "../utils/TestConstants.sol"; contract MultiValidationTest is AccountTestBase { using ECDSA for bytes32; using MessageHashUtils for bytes32; - SingleOwnerPlugin public validator2; + SingleSignerValidation public validator2; address public owner2; uint256 public owner2Key; function setUp() public { - validator2 = new SingleOwnerPlugin(); + validator2 = new SingleSignerValidation(); (owner2, owner2Key) = makeAddrAndKey("owner2"); } @@ -35,16 +35,16 @@ contract MultiValidationTest is AccountTestBase { function test_overlappingValidationInstall() public { vm.prank(address(entryPoint)); account1.installValidation( - ValidationConfigLib.pack(address(validator2), TEST_DEFAULT_OWNER_FUNCTION_ID, true, true), + ValidationConfigLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID, true, true), new bytes4[](0), - abi.encode(TEST_DEFAULT_OWNER_FUNCTION_ID, owner2), + abi.encode(TEST_DEFAULT_VALIDATION_ENTITY_ID, owner2), "", "" ); - FunctionReference[] memory validations = new FunctionReference[](2); - validations[0] = FunctionReferenceLib.pack(address(singleOwnerPlugin), TEST_DEFAULT_OWNER_FUNCTION_ID); - validations[1] = FunctionReferenceLib.pack(address(validator2), TEST_DEFAULT_OWNER_FUNCTION_ID); + PluginEntity[] memory validations = new PluginEntity[](2); + validations[0] = PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID); + validations[1] = PluginEntityLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID); bytes4[] memory selectors0 = account1.getSelectors(validations[0]); bytes4[] memory selectors1 = account1.getSelectors(validations[1]); @@ -64,16 +64,14 @@ contract MultiValidationTest is AccountTestBase { abi.encodeWithSelector( UpgradeableModularAccount.RuntimeValidationFunctionReverted.selector, address(validator2), - 0, + 1, abi.encodeWithSignature("NotAuthorized()") ) ); account1.executeWithAuthorization( abi.encodeCall(IStandardExecutor.execute, (address(0), 0, "")), _encodeSignature( - FunctionReferenceLib.pack(address(validator2), TEST_DEFAULT_OWNER_FUNCTION_ID), - GLOBAL_VALIDATION, - "" + PluginEntityLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, "" ) ); @@ -81,9 +79,7 @@ contract MultiValidationTest is AccountTestBase { account1.executeWithAuthorization( abi.encodeCall(IStandardExecutor.execute, (address(0), 0, "")), _encodeSignature( - FunctionReferenceLib.pack(address(validator2), TEST_DEFAULT_OWNER_FUNCTION_ID), - GLOBAL_VALIDATION, - "" + PluginEntityLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, "" ) ); } @@ -109,7 +105,7 @@ contract MultiValidationTest is AccountTestBase { bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); userOp.signature = _encodeSignature( - FunctionReferenceLib.pack(address(validator2), TEST_DEFAULT_OWNER_FUNCTION_ID), + PluginEntityLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, abi.encodePacked(r, s, v) ); @@ -124,7 +120,7 @@ contract MultiValidationTest is AccountTestBase { userOp.nonce = 1; (v, r, s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); userOp.signature = _encodeSignature( - FunctionReferenceLib.pack(address(validator2), TEST_DEFAULT_OWNER_FUNCTION_ID), + PluginEntityLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, abi.encodePacked(r, s, v) ); diff --git a/test/account/PerHookData.t.sol b/test/account/PerHookData.t.sol index 9c90cf5c..bcb53171 100644 --- a/test/account/PerHookData.t.sol +++ b/test/account/PerHookData.t.sol @@ -6,7 +6,7 @@ import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntry import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; +import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; import {MockAccessControlHookPlugin} from "../mocks/plugins/MockAccessControlHookPlugin.sol"; import {Counter} from "../mocks/Counter.sol"; @@ -37,8 +37,9 @@ contract PerHookDataTest is CustomValidationTestBase { PreValidationHookData[] memory preValidationHookData = new PreValidationHookData[](1); preValidationHookData[0] = PreValidationHookData({index: 0, validationData: abi.encodePacked(_counter)}); - userOp.signature = - _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature( + _signerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v) + ); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -59,8 +60,9 @@ contract PerHookDataTest is CustomValidationTestBase { validationData: abi.encodePacked(address(0x1234123412341234123412341234123412341234)) }); - userOp.signature = - _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature( + _signerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v) + ); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -80,7 +82,7 @@ contract PerHookDataTest is CustomValidationTestBase { (PackedUserOperation memory userOp, bytes32 userOpHash) = _getCounterUserOP(); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature(_signerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -104,8 +106,9 @@ contract PerHookDataTest is CustomValidationTestBase { preValidationHookData[0] = PreValidationHookData({index: 0, validationData: abi.encodePacked(_counter)}); preValidationHookData[1] = PreValidationHookData({index: 1, validationData: abi.encodePacked(_counter)}); - userOp.signature = - _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature( + _signerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v) + ); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -142,8 +145,9 @@ contract PerHookDataTest is CustomValidationTestBase { PreValidationHookData[] memory preValidationHookData = new PreValidationHookData[](1); preValidationHookData[0] = PreValidationHookData({index: 0, validationData: abi.encodePacked(beneficiary)}); - userOp.signature = - _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature( + _signerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v) + ); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -166,8 +170,9 @@ contract PerHookDataTest is CustomValidationTestBase { PreValidationHookData[] memory preValidationHookData = new PreValidationHookData[](1); preValidationHookData[0] = PreValidationHookData({index: 0, validationData: ""}); - userOp.signature = - _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature( + _signerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v) + ); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -195,7 +200,7 @@ contract PerHookDataTest is CustomValidationTestBase { UpgradeableModularAccount.execute, (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) ), - _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, preValidationHookData, "") + _encodeSignature(_signerValidation, GLOBAL_VALIDATION, preValidationHookData, "") ); assertEq(_counter.number(), 1); @@ -213,7 +218,7 @@ contract PerHookDataTest is CustomValidationTestBase { abi.encodeWithSelector( UpgradeableModularAccount.PreRuntimeValidationHookFailed.selector, _accessControlHookPlugin, - uint8(MockAccessControlHookPlugin.FunctionId.PRE_VALIDATION_HOOK), + uint32(MockAccessControlHookPlugin.EntityId.PRE_VALIDATION_HOOK), abi.encodeWithSignature("Error(string)", "Proof doesn't match target") ) ); @@ -222,7 +227,7 @@ contract PerHookDataTest is CustomValidationTestBase { UpgradeableModularAccount.execute, (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) ), - _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, preValidationHookData, "") + _encodeSignature(_signerValidation, GLOBAL_VALIDATION, preValidationHookData, "") ); } @@ -232,7 +237,7 @@ contract PerHookDataTest is CustomValidationTestBase { abi.encodeWithSelector( UpgradeableModularAccount.PreRuntimeValidationHookFailed.selector, _accessControlHookPlugin, - uint8(MockAccessControlHookPlugin.FunctionId.PRE_VALIDATION_HOOK), + uint32(MockAccessControlHookPlugin.EntityId.PRE_VALIDATION_HOOK), abi.encodeWithSignature("Error(string)", "Proof doesn't match target") ) ); @@ -241,7 +246,7 @@ contract PerHookDataTest is CustomValidationTestBase { UpgradeableModularAccount.execute, (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) ), - _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, "") + _encodeSignature(_signerValidation, GLOBAL_VALIDATION, "") ); } @@ -259,7 +264,7 @@ contract PerHookDataTest is CustomValidationTestBase { UpgradeableModularAccount.execute, (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) ), - _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, preValidationHookData, "") + _encodeSignature(_signerValidation, GLOBAL_VALIDATION, preValidationHookData, "") ); } @@ -274,13 +279,13 @@ contract PerHookDataTest is CustomValidationTestBase { abi.encodeWithSelector( UpgradeableModularAccount.PreRuntimeValidationHookFailed.selector, _accessControlHookPlugin, - uint8(MockAccessControlHookPlugin.FunctionId.PRE_VALIDATION_HOOK), + uint32(MockAccessControlHookPlugin.EntityId.PRE_VALIDATION_HOOK), abi.encodeWithSignature("Error(string)", "Target not allowed") ) ); account1.executeWithAuthorization( abi.encodeCall(UpgradeableModularAccount.execute, (beneficiary, 1 wei, "")), - _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, preValidationHookData, "") + _encodeSignature(_signerValidation, GLOBAL_VALIDATION, preValidationHookData, "") ); } @@ -295,7 +300,7 @@ contract PerHookDataTest is CustomValidationTestBase { UpgradeableModularAccount.execute, (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) ), - _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, preValidationHookData, "") + _encodeSignature(_signerValidation, GLOBAL_VALIDATION, preValidationHookData, "") ); } @@ -325,13 +330,13 @@ contract PerHookDataTest is CustomValidationTestBase { internal virtual override - returns (FunctionReference, bool, bool, bytes4[] memory, bytes memory, bytes memory, bytes memory) + returns (PluginEntity, bool, bool, bytes4[] memory, bytes memory, bytes memory, bytes memory) { - FunctionReference accessControlHook = FunctionReferenceLib.pack( - address(_accessControlHookPlugin), uint8(MockAccessControlHookPlugin.FunctionId.PRE_VALIDATION_HOOK) + PluginEntity accessControlHook = PluginEntityLib.pack( + address(_accessControlHookPlugin), uint32(MockAccessControlHookPlugin.EntityId.PRE_VALIDATION_HOOK) ); - FunctionReference[] memory preValidationHooks = new FunctionReference[](1); + PluginEntity[] memory preValidationHooks = new PluginEntity[](1); preValidationHooks[0] = accessControlHook; bytes[] memory preValidationHookData = new bytes[](1); @@ -341,11 +346,11 @@ contract PerHookDataTest is CustomValidationTestBase { bytes memory packedPreValidationHooks = abi.encode(preValidationHooks, preValidationHookData); return ( - _ownerValidation, + _signerValidation, true, true, new bytes4[](0), - abi.encode(TEST_DEFAULT_OWNER_FUNCTION_ID, owner1), + abi.encode(TEST_DEFAULT_VALIDATION_ENTITY_ID, owner1), packedPreValidationHooks, "" ); diff --git a/test/account/SelfCallAuthorization.t.sol b/test/account/SelfCallAuthorization.t.sol index 5bcf64b3..c490eea4 100644 --- a/test/account/SelfCallAuthorization.t.sol +++ b/test/account/SelfCallAuthorization.t.sol @@ -7,7 +7,7 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {IStandardExecutor, Call} from "../../src/interfaces/IStandardExecutor.sol"; -import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; +import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; @@ -16,7 +16,7 @@ import {ComprehensivePlugin} from "../mocks/plugins/ComprehensivePlugin.sol"; contract SelfCallAuthorizationTest is AccountTestBase { ComprehensivePlugin public comprehensivePlugin; - FunctionReference public comprehensivePluginValidation; + PluginEntity public comprehensivePluginValidation; function setUp() public { // install the comprehensive plugin to get new exec functions with different validations configured. @@ -27,9 +27,8 @@ contract SelfCallAuthorizationTest is AccountTestBase { vm.prank(address(entryPoint)); account1.installPlugin(address(comprehensivePlugin), manifestHash, ""); - comprehensivePluginValidation = FunctionReferenceLib.pack( - address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.VALIDATION) - ); + comprehensivePluginValidation = + PluginEntityLib.pack(address(comprehensivePlugin), uint32(ComprehensivePlugin.EntityId.VALIDATION)); } function test_selfCallFails_userOp() public { @@ -304,7 +303,7 @@ contract SelfCallAuthorizationTest is AccountTestBase { UpgradeableModularAccount.installValidation, (ValidationConfigLib.pack(comprehensivePluginValidation, false, false), selectors, "", "", "") ), - _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, "") + _encodeSignature(_signerValidation, GLOBAL_VALIDATION, "") ); } diff --git a/test/account/UpgradeableModularAccount.t.sol b/test/account/UpgradeableModularAccount.t.sol index dda78c66..54af25c6 100644 --- a/test/account/UpgradeableModularAccount.t.sol +++ b/test/account/UpgradeableModularAccount.t.sol @@ -14,14 +14,14 @@ import {PluginManifest} from "../../src/interfaces/IPlugin.sol"; import {IAccountLoupe} from "../../src/interfaces/IAccountLoupe.sol"; import {IPluginManager} from "../../src/interfaces/IPluginManager.sol"; import {Call} from "../../src/interfaces/IStandardExecutor.sol"; -import {SingleOwnerPlugin} from "../../src/plugins/owner/SingleOwnerPlugin.sol"; +import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; import {TokenReceiverPlugin} from "../../src/plugins/TokenReceiverPlugin.sol"; import {Counter} from "../mocks/Counter.sol"; import {ComprehensivePlugin} from "../mocks/plugins/ComprehensivePlugin.sol"; import {MockPlugin} from "../mocks/MockPlugin.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; -import {TEST_DEFAULT_OWNER_FUNCTION_ID} from "../utils/TestConstants.sol"; +import {TEST_DEFAULT_VALIDATION_ENTITY_ID} from "../utils/TestConstants.sol"; contract UpgradeableModularAccountTest is AccountTestBase { using ECDSA for bytes32; @@ -77,7 +77,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature(_signerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -95,9 +95,9 @@ contract UpgradeableModularAccountTest is AccountTestBase { callData: abi.encodeCall( UpgradeableModularAccount.execute, ( - address(singleOwnerPlugin), + address(singleSignerValidation), 0, - abi.encodeCall(SingleOwnerPlugin.transferOwnership, (TEST_DEFAULT_OWNER_FUNCTION_ID, owner2)) + abi.encodeCall(SingleSignerValidation.transferSigner, (TEST_DEFAULT_VALIDATION_ENTITY_ID, owner2)) ) ), accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), @@ -110,7 +110,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature(_signerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -136,7 +136,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature(_signerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -162,7 +162,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature(_signerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -190,7 +190,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature(_signerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -221,7 +221,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature(_signerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -409,16 +409,16 @@ contract UpgradeableModularAccountTest is AccountTestBase { } function test_transferOwnership() public { - assertEq(singleOwnerPlugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, address(account1)), owner1); + assertEq(singleSignerValidation.signerOf(TEST_DEFAULT_VALIDATION_ENTITY_ID, address(account1)), owner1); vm.prank(address(entryPoint)); account1.execute( - address(singleOwnerPlugin), + address(singleSignerValidation), 0, - abi.encodeCall(SingleOwnerPlugin.transferOwnership, (TEST_DEFAULT_OWNER_FUNCTION_ID, owner2)) + abi.encodeCall(SingleSignerValidation.transferSigner, (TEST_DEFAULT_VALIDATION_ENTITY_ID, owner2)) ); - assertEq(singleOwnerPlugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, address(account1)), owner2); + assertEq(singleSignerValidation.signerOf(TEST_DEFAULT_VALIDATION_ENTITY_ID, address(account1)), owner2); } function test_isValidSignature() public { @@ -426,10 +426,10 @@ contract UpgradeableModularAccountTest is AccountTestBase { (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, message); - // singleOwnerPlugin.ownerOf(address(account1)); + // singleSignerValidation.ownerOf(address(account1)); bytes memory signature = - abi.encodePacked(address(singleOwnerPlugin), TEST_DEFAULT_OWNER_FUNCTION_ID, r, s, v); + abi.encodePacked(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID, r, s, v); bytes4 validationResult = IERC1271(address(account1)).isValidSignature(message, signature); diff --git a/test/account/ValidationIntersection.t.sol b/test/account/ValidationIntersection.t.sol index 7f245031..3bdaa1b6 100644 --- a/test/account/ValidationIntersection.t.sol +++ b/test/account/ValidationIntersection.t.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.19; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; +import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import { @@ -22,28 +22,28 @@ contract ValidationIntersectionTest is AccountTestBase { MockUserOpValidation1HookPlugin public oneHookPlugin; MockUserOpValidation2HookPlugin public twoHookPlugin; - FunctionReference public noHookValidation; - FunctionReference public oneHookValidation; - FunctionReference public twoHookValidation; + PluginEntity public noHookValidation; + PluginEntity public oneHookValidation; + PluginEntity public twoHookValidation; function setUp() public { noHookPlugin = new MockUserOpValidationPlugin(); oneHookPlugin = new MockUserOpValidation1HookPlugin(); twoHookPlugin = new MockUserOpValidation2HookPlugin(); - noHookValidation = FunctionReferenceLib.pack({ + noHookValidation = PluginEntityLib.pack({ addr: address(noHookPlugin), - functionId: uint8(MockBaseUserOpValidationPlugin.FunctionId.USER_OP_VALIDATION) + entityId: uint32(MockBaseUserOpValidationPlugin.EntityId.USER_OP_VALIDATION) }); - oneHookValidation = FunctionReferenceLib.pack({ + oneHookValidation = PluginEntityLib.pack({ addr: address(oneHookPlugin), - functionId: uint8(MockBaseUserOpValidationPlugin.FunctionId.USER_OP_VALIDATION) + entityId: uint32(MockBaseUserOpValidationPlugin.EntityId.USER_OP_VALIDATION) }); - twoHookValidation = FunctionReferenceLib.pack({ + twoHookValidation = PluginEntityLib.pack({ addr: address(twoHookPlugin), - functionId: uint8(MockBaseUserOpValidationPlugin.FunctionId.USER_OP_VALIDATION) + entityId: uint32(MockBaseUserOpValidationPlugin.EntityId.USER_OP_VALIDATION) }); vm.startPrank(address(entryPoint)); @@ -59,10 +59,10 @@ contract ValidationIntersectionTest is AccountTestBase { }); // TODO: change with new install flow // temporary fix to add the pre-validation hook - FunctionReference[] memory preValidationHooks = new FunctionReference[](1); - preValidationHooks[0] = FunctionReferenceLib.pack({ + PluginEntity[] memory preValidationHooks = new PluginEntity[](1); + preValidationHooks[0] = PluginEntityLib.pack({ addr: address(oneHookPlugin), - functionId: uint8(MockBaseUserOpValidationPlugin.FunctionId.PRE_VALIDATION_HOOK_1) + entityId: uint32(MockBaseUserOpValidationPlugin.EntityId.PRE_VALIDATION_HOOK_1) }); bytes[] memory installDatas = new bytes[](1); account1.installValidation( @@ -78,14 +78,14 @@ contract ValidationIntersectionTest is AccountTestBase { pluginInstallData: "" }); // temporary fix to add the pre-validation hook - preValidationHooks = new FunctionReference[](2); - preValidationHooks[0] = FunctionReferenceLib.pack({ + preValidationHooks = new PluginEntity[](2); + preValidationHooks[0] = PluginEntityLib.pack({ addr: address(twoHookPlugin), - functionId: uint8(MockBaseUserOpValidationPlugin.FunctionId.PRE_VALIDATION_HOOK_1) + entityId: uint32(MockBaseUserOpValidationPlugin.EntityId.PRE_VALIDATION_HOOK_1) }); - preValidationHooks[1] = FunctionReferenceLib.pack({ + preValidationHooks[1] = PluginEntityLib.pack({ addr: address(twoHookPlugin), - functionId: uint8(MockBaseUserOpValidationPlugin.FunctionId.PRE_VALIDATION_HOOK_2) + entityId: uint32(MockBaseUserOpValidationPlugin.EntityId.PRE_VALIDATION_HOOK_2) }); installDatas = new bytes[](2); account1.installValidation( @@ -156,7 +156,7 @@ contract ValidationIntersectionTest is AccountTestBase { uint48 end2 = uint48(25); oneHookPlugin.setValidationData( - _packValidationData(address(0), start1, end1), _packValidationData(address(0), start2, end2) + _packValidationRes(address(0), start1, end1), _packValidationRes(address(0), start2, end2) ); PackedUserOperation memory userOp; @@ -167,7 +167,7 @@ contract ValidationIntersectionTest is AccountTestBase { vm.prank(address(entryPoint)); uint256 returnedValidationData = account1.validateUserOp(userOp, uoHash, 1 wei); - assertEq(returnedValidationData, _packValidationData(address(0), start2, end1)); + assertEq(returnedValidationData, _packValidationRes(address(0), start2, end1)); } function test_validationIntersect_timeBounds_intersect_2() public { @@ -178,7 +178,7 @@ contract ValidationIntersectionTest is AccountTestBase { uint48 end2 = uint48(25); oneHookPlugin.setValidationData( - _packValidationData(address(0), start2, end2), _packValidationData(address(0), start1, end1) + _packValidationRes(address(0), start2, end2), _packValidationRes(address(0), start1, end1) ); PackedUserOperation memory userOp; @@ -189,7 +189,7 @@ contract ValidationIntersectionTest is AccountTestBase { vm.prank(address(entryPoint)); uint256 returnedValidationData = account1.validateUserOp(userOp, uoHash, 1 wei); - assertEq(returnedValidationData, _packValidationData(address(0), start2, end1)); + assertEq(returnedValidationData, _packValidationRes(address(0), start2, end1)); } function test_validationIntersect_revert_unexpectedAuthorizer() public { @@ -211,7 +211,7 @@ contract ValidationIntersectionTest is AccountTestBase { abi.encodeWithSelector( UpgradeableModularAccount.UnexpectedAggregator.selector, address(oneHookPlugin), - MockBaseUserOpValidationPlugin.FunctionId.PRE_VALIDATION_HOOK_1, + MockBaseUserOpValidationPlugin.EntityId.PRE_VALIDATION_HOOK_1, badAuthorizer ) ); @@ -247,7 +247,7 @@ contract ValidationIntersectionTest is AccountTestBase { address goodAuthorizer = makeAddr("goodAuthorizer"); oneHookPlugin.setValidationData( - _packValidationData(goodAuthorizer, start1, end1), _packValidationData(address(0), start2, end2) + _packValidationRes(goodAuthorizer, start1, end1), _packValidationRes(address(0), start2, end2) ); PackedUserOperation memory userOp; @@ -258,7 +258,7 @@ contract ValidationIntersectionTest is AccountTestBase { vm.prank(address(entryPoint)); uint256 returnedValidationData = account1.validateUserOp(userOp, uoHash, 1 wei); - assertEq(returnedValidationData, _packValidationData(goodAuthorizer, start2, end1)); + assertEq(returnedValidationData, _packValidationRes(goodAuthorizer, start2, end1)); } function test_validationIntersect_multiplePreValidationHooksIntersect() public { @@ -270,8 +270,8 @@ contract ValidationIntersectionTest is AccountTestBase { twoHookPlugin.setValidationData( 0, // returns OK - _packValidationData(address(0), start1, end1), - _packValidationData(address(0), start2, end2) + _packValidationRes(address(0), start1, end1), + _packValidationRes(address(0), start2, end2) ); PackedUserOperation memory userOp; @@ -282,7 +282,7 @@ contract ValidationIntersectionTest is AccountTestBase { vm.prank(address(entryPoint)); uint256 returnedValidationData = account1.validateUserOp(userOp, uoHash, 1 wei); - assertEq(returnedValidationData, _packValidationData(address(0), start2, end1)); + assertEq(returnedValidationData, _packValidationRes(address(0), start2, end1)); } function test_validationIntersect_multiplePreValidationHooksSigFail() public { @@ -318,7 +318,7 @@ contract ValidationIntersectionTest is AccountTestBase { validAfter = uint48(validationData >> (48 + 160)); } - function _packValidationData(address authorizer, uint48 validAfter, uint48 validUntil) + function _packValidationRes(address authorizer, uint48 validAfter, uint48 validUntil) internal pure returns (uint256) diff --git a/test/libraries/FunctionReferenceLib.t.sol b/test/libraries/FunctionReferenceLib.t.sol deleted file mode 100644 index 6471fbd0..00000000 --- a/test/libraries/FunctionReferenceLib.t.sol +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; - -import {Test} from "forge-std/Test.sol"; - -import {FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; -import {FunctionReference} from "../../src/interfaces/IPluginManager.sol"; - -contract FunctionReferenceLibTest is Test { - using FunctionReferenceLib for FunctionReference; - - function testFuzz_functionReference_packing(address addr, uint8 functionId) public { - // console.log("addr: ", addr); - // console.log("functionId: ", vm.toString(functionId)); - FunctionReference fr = FunctionReferenceLib.pack(addr, functionId); - // console.log("packed: ", vm.toString(FunctionReference.unwrap(fr))); - (address addr2, uint8 functionId2) = FunctionReferenceLib.unpack(fr); - // console.log("addr2: ", addr2); - // console.log("functionId2: ", vm.toString(functionId2)); - assertEq(addr, addr2); - assertEq(functionId, functionId2); - } - - function testFuzz_functionReference_operators(FunctionReference a, FunctionReference b) public { - assertTrue(a.eq(a)); - assertTrue(b.eq(b)); - - if (FunctionReference.unwrap(a) == FunctionReference.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/mocks/MSCAFactoryFixture.sol b/test/mocks/SingleSignerFactoryFixture.sol similarity index 79% rename from test/mocks/MSCAFactoryFixture.sol rename to test/mocks/SingleSignerFactoryFixture.sol index 8ca3a51f..b3da73ec 100644 --- a/test/mocks/MSCAFactoryFixture.sol +++ b/test/mocks/SingleSignerFactoryFixture.sol @@ -5,34 +5,32 @@ import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; -import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; -import {SingleOwnerPlugin} from "../../src/plugins/owner/SingleOwnerPlugin.sol"; +import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; +import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; import {OptimizedTest} from "../utils/OptimizedTest.sol"; -import {TEST_DEFAULT_OWNER_FUNCTION_ID} from "../utils/TestConstants.sol"; +import {TEST_DEFAULT_VALIDATION_ENTITY_ID} from "../utils/TestConstants.sol"; -/** - * @title MSCAFactoryFixture - * @dev a factory that initializes UpgradeableModularAccounts with a single plugin, SingleOwnerPlugin - * intended for unit tests and local development, not for production. - */ -contract MSCAFactoryFixture is OptimizedTest { +contract SingleSignerFactoryFixture is OptimizedTest { UpgradeableModularAccount public accountImplementation; - SingleOwnerPlugin public singleOwnerPlugin; + SingleSignerValidation public singleSignerValidation; bytes32 private immutable _PROXY_BYTECODE_HASH; uint32 public constant UNSTAKE_DELAY = 1 weeks; IEntryPoint public entryPoint; - constructor(IEntryPoint _entryPoint, SingleOwnerPlugin _singleOwnerPlugin) { + address public self; + + constructor(IEntryPoint _entryPoint, SingleSignerValidation _singleSignerValidation) { entryPoint = _entryPoint; accountImplementation = _deployUpgradeableModularAccount(_entryPoint); _PROXY_BYTECODE_HASH = keccak256( abi.encodePacked(type(ERC1967Proxy).creationCode, abi.encode(address(accountImplementation), "")) ); - singleOwnerPlugin = _singleOwnerPlugin; + singleSignerValidation = _singleSignerValidation; + self = address(this); } /** @@ -47,13 +45,15 @@ contract MSCAFactoryFixture is OptimizedTest { // short circuit if exists if (addr.code.length == 0) { - bytes memory pluginInstallData = abi.encode(TEST_DEFAULT_OWNER_FUNCTION_ID, owner); + bytes memory pluginInstallData = abi.encode(TEST_DEFAULT_VALIDATION_ENTITY_ID, owner); // not necessary to check return addr since next call will fail if so new ERC1967Proxy{salt: getSalt(owner, salt)}(address(accountImplementation), ""); // point proxy to actual implementation and init plugins UpgradeableModularAccount(payable(addr)).initializeWithValidation( - ValidationConfigLib.pack(address(singleOwnerPlugin), TEST_DEFAULT_OWNER_FUNCTION_ID, true, true), + ValidationConfigLib.pack( + address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID, true, true + ), new bytes4[](0), pluginInstallData, "", diff --git a/test/mocks/plugins/ComprehensivePlugin.sol b/test/mocks/plugins/ComprehensivePlugin.sol index cd455c88..306e96a4 100644 --- a/test/mocks/plugins/ComprehensivePlugin.sol +++ b/test/mocks/plugins/ComprehensivePlugin.sol @@ -18,7 +18,7 @@ import {IExecutionHook} from "../../../src/interfaces/IExecutionHook.sol"; import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; contract ComprehensivePlugin is IValidation, IValidationHook, IExecutionHook, BasePlugin { - enum FunctionId { + enum EntityId { PRE_VALIDATION_HOOK_1, PRE_VALIDATION_HOOK_2, VALIDATION, @@ -46,85 +46,85 @@ contract ComprehensivePlugin is IValidation, IValidationHook, IExecutionHook, Ba function onUninstall(bytes calldata) external override {} - function preUserOpValidationHook(uint8 functionId, PackedUserOperation calldata, bytes32) + function preUserOpValidationHook(uint32 entityId, PackedUserOperation calldata, bytes32) external pure override returns (uint256) { - if (functionId == uint8(FunctionId.PRE_VALIDATION_HOOK_1)) { + if (entityId == uint32(EntityId.PRE_VALIDATION_HOOK_1)) { return 0; - } else if (functionId == uint8(FunctionId.PRE_VALIDATION_HOOK_2)) { + } else if (entityId == uint32(EntityId.PRE_VALIDATION_HOOK_2)) { return 0; } revert NotImplemented(); } - function validateUserOp(uint8 functionId, PackedUserOperation calldata, bytes32) + function validateUserOp(uint32 entityId, PackedUserOperation calldata, bytes32) external pure override returns (uint256) { - if (functionId == uint8(FunctionId.VALIDATION)) { + if (entityId == uint32(EntityId.VALIDATION)) { return 0; } revert NotImplemented(); } - function preRuntimeValidationHook(uint8 functionId, address, uint256, bytes calldata, bytes calldata) + function preRuntimeValidationHook(uint32 entityId, address, uint256, bytes calldata, bytes calldata) external pure override { - if (functionId == uint8(FunctionId.PRE_VALIDATION_HOOK_1)) { + if (entityId == uint32(EntityId.PRE_VALIDATION_HOOK_1)) { return; - } else if (functionId == uint8(FunctionId.PRE_VALIDATION_HOOK_2)) { + } else if (entityId == uint32(EntityId.PRE_VALIDATION_HOOK_2)) { return; } revert NotImplemented(); } - function validateRuntime(uint8 functionId, address, uint256, bytes calldata, bytes calldata) + function validateRuntime(address, uint32 entityId, address, uint256, bytes calldata, bytes calldata) external pure override { - if (functionId == uint8(FunctionId.VALIDATION)) { + if (entityId == uint32(EntityId.VALIDATION)) { return; } revert NotImplemented(); } - function validateSignature(uint8 functionId, address, bytes32, bytes calldata) + function validateSignature(address, uint32 entityId, address, bytes32, bytes calldata) external pure returns (bytes4) { - if (functionId == uint8(FunctionId.SIG_VALIDATION)) { + if (entityId == uint32(EntityId.SIG_VALIDATION)) { return 0xffffffff; } revert NotImplemented(); } - function preExecutionHook(uint8 functionId, address, uint256, bytes calldata) + function preExecutionHook(uint32 entityId, address, uint256, bytes calldata) external pure override returns (bytes memory) { - if (functionId == uint8(FunctionId.PRE_EXECUTION_HOOK)) { + if (entityId == uint32(EntityId.PRE_EXECUTION_HOOK)) { return ""; - } else if (functionId == uint8(FunctionId.BOTH_EXECUTION_HOOKS)) { + } else if (entityId == uint32(EntityId.BOTH_EXECUTION_HOOKS)) { return ""; } revert NotImplemented(); } - function postExecutionHook(uint8 functionId, bytes calldata) external pure override { - if (functionId == uint8(FunctionId.POST_EXECUTION_HOOK)) { + function postExecutionHook(uint32 entityId, bytes calldata) external pure override { + if (entityId == uint32(EntityId.POST_EXECUTION_HOOK)) { return; - } else if (functionId == uint8(FunctionId.BOTH_EXECUTION_HOOKS)) { + } else if (entityId == uint32(EntityId.BOTH_EXECUTION_HOOKS)) { return; } revert NotImplemented(); @@ -145,7 +145,7 @@ contract ComprehensivePlugin is IValidation, IValidationHook, IExecutionHook, Ba manifest.validationFunctions = new ManifestValidation[](1); manifest.validationFunctions[0] = ManifestValidation({ - functionId: uint8(FunctionId.VALIDATION), + entityId: uint32(EntityId.VALIDATION), isDefault: true, isSignatureValidation: false, selectors: validationSelectors @@ -154,19 +154,19 @@ contract ComprehensivePlugin is IValidation, IValidationHook, IExecutionHook, Ba manifest.executionHooks = new ManifestExecutionHook[](3); manifest.executionHooks[0] = ManifestExecutionHook({ executionSelector: this.foo.selector, - functionId: uint8(FunctionId.BOTH_EXECUTION_HOOKS), + entityId: uint32(EntityId.BOTH_EXECUTION_HOOKS), isPreHook: true, isPostHook: true }); manifest.executionHooks[1] = ManifestExecutionHook({ executionSelector: this.foo.selector, - functionId: uint8(FunctionId.PRE_EXECUTION_HOOK), + entityId: uint32(EntityId.PRE_EXECUTION_HOOK), isPreHook: true, isPostHook: false }); manifest.executionHooks[2] = ManifestExecutionHook({ executionSelector: this.foo.selector, - functionId: uint8(FunctionId.POST_EXECUTION_HOOK), + entityId: uint32(EntityId.POST_EXECUTION_HOOK), isPreHook: false, isPostHook: true }); diff --git a/test/mocks/plugins/MockAccessControlHookPlugin.sol b/test/mocks/plugins/MockAccessControlHookPlugin.sol index c17868a8..6bd593f2 100644 --- a/test/mocks/plugins/MockAccessControlHookPlugin.sol +++ b/test/mocks/plugins/MockAccessControlHookPlugin.sol @@ -13,7 +13,7 @@ import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; // This is just a mock - it does not enforce this over `executeBatch` and other methods of making calls, and should // not be used in production.. contract MockAccessControlHookPlugin is IValidationHook, BasePlugin { - enum FunctionId { + enum EntityId { PRE_VALIDATION_HOOK } @@ -28,13 +28,13 @@ contract MockAccessControlHookPlugin is IValidationHook, BasePlugin { delete allowedTargets[msg.sender]; } - function preUserOpValidationHook(uint8 functionId, PackedUserOperation calldata userOp, bytes32) + function preUserOpValidationHook(uint32 entityId, PackedUserOperation calldata userOp, bytes32) external view override returns (uint256) { - if (functionId == uint8(FunctionId.PRE_VALIDATION_HOOK)) { + if (entityId == uint32(EntityId.PRE_VALIDATION_HOOK)) { if (bytes4(userOp.callData[:4]) == IStandardExecutor.execute.selector) { address target = abi.decode(userOp.callData[4:36], (address)); @@ -49,13 +49,13 @@ contract MockAccessControlHookPlugin is IValidationHook, BasePlugin { } function preRuntimeValidationHook( - uint8 functionId, + uint32 entityId, address, uint256, bytes calldata data, bytes calldata authorization ) external view override { - if (functionId == uint8(FunctionId.PRE_VALIDATION_HOOK)) { + if (entityId == uint32(EntityId.PRE_VALIDATION_HOOK)) { if (bytes4(data[:4]) == IStandardExecutor.execute.selector) { address target = abi.decode(data[4:36], (address)); diff --git a/test/mocks/plugins/ReturnDataPluginMocks.sol b/test/mocks/plugins/ReturnDataPluginMocks.sol index 211f79af..3adbb6e4 100644 --- a/test/mocks/plugins/ReturnDataPluginMocks.sol +++ b/test/mocks/plugins/ReturnDataPluginMocks.sol @@ -72,17 +72,20 @@ contract ResultConsumerPlugin is BasePlugin, IValidation { // Validation function implementations. We only care about the runtime validation function, to authorize // itself. - function validateUserOp(uint8, PackedUserOperation calldata, bytes32) external pure returns (uint256) { + function validateUserOp(uint32, PackedUserOperation calldata, bytes32) external pure returns (uint256) { revert NotImplemented(); } - function validateRuntime(uint8, address sender, uint256, bytes calldata, bytes calldata) external view { + function validateRuntime(address, uint32, address sender, uint256, bytes calldata, bytes calldata) + external + view + { if (sender != address(this)) { revert NotAuthorized(); } } - function validateSignature(uint8, address, bytes32, bytes calldata) external pure returns (bytes4) { + function validateSignature(address, uint32, address, bytes32, bytes calldata) external pure returns (bytes4) { revert NotImplemented(); } @@ -99,7 +102,7 @@ contract ResultConsumerPlugin is BasePlugin, IValidation { // This result should be allowed based on the manifest permission request bytes memory returnData = IStandardExecutor(msg.sender).executeWithAuthorization( abi.encodeCall(IStandardExecutor.execute, (target, 0, abi.encodeCall(RegularResultContract.foo, ()))), - abi.encodePacked(this, uint8(0), uint8(0), uint32(1), uint8(255)) // Validation function of self, + abi.encodePacked(this, uint32(0), uint8(0), uint32(1), uint8(255)) // Validation function of self, // selector-associated, with no auth data ); @@ -121,7 +124,7 @@ contract ResultConsumerPlugin is BasePlugin, IValidation { manifest.validationFunctions = new ManifestValidation[](1); manifest.validationFunctions[0] = ManifestValidation({ - functionId: 0, + entityId: 0, isDefault: true, isSignatureValidation: false, selectors: validationSelectors diff --git a/test/mocks/plugins/ValidationPluginMocks.sol b/test/mocks/plugins/ValidationPluginMocks.sol index a59d5ee3..8d9b6ce2 100644 --- a/test/mocks/plugins/ValidationPluginMocks.sol +++ b/test/mocks/plugins/ValidationPluginMocks.sol @@ -14,7 +14,7 @@ import {IValidationHook} from "../../../src/interfaces/IValidationHook.sol"; import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; abstract contract MockBaseUserOpValidationPlugin is IValidation, IValidationHook, BasePlugin { - enum FunctionId { + enum EntityId { USER_OP_VALIDATION, PRE_VALIDATION_HOOK_1, PRE_VALIDATION_HOOK_2 @@ -32,40 +32,45 @@ abstract contract MockBaseUserOpValidationPlugin is IValidation, IValidationHook function onUninstall(bytes calldata) external override {} - function preUserOpValidationHook(uint8 functionId, PackedUserOperation calldata, bytes32) + function preUserOpValidationHook(uint32 entityId, PackedUserOperation calldata, bytes32) external view override returns (uint256) { - if (functionId == uint8(FunctionId.PRE_VALIDATION_HOOK_1)) { + if (entityId == uint32(EntityId.PRE_VALIDATION_HOOK_1)) { return _preUserOpValidationHook1Data; - } else if (functionId == uint8(FunctionId.PRE_VALIDATION_HOOK_2)) { + } else if (entityId == uint32(EntityId.PRE_VALIDATION_HOOK_2)) { return _preUserOpValidationHook2Data; } revert NotImplemented(); } - function validateUserOp(uint8 functionId, PackedUserOperation calldata, bytes32) + function validateUserOp(uint32 entityId, PackedUserOperation calldata, bytes32) external view override returns (uint256) { - if (functionId == uint8(FunctionId.USER_OP_VALIDATION)) { + if (entityId == uint32(EntityId.USER_OP_VALIDATION)) { return _userOpValidationFunctionData; } revert NotImplemented(); } - function validateSignature(uint8, address, bytes32, bytes calldata) external pure override returns (bytes4) { + function validateSignature(address, uint32, address, bytes32, bytes calldata) + external + pure + override + returns (bytes4) + { revert NotImplemented(); } // Empty stubs function pluginMetadata() external pure override returns (PluginMetadata memory) {} - function preRuntimeValidationHook(uint8, address, uint256, bytes calldata, bytes calldata) + function preRuntimeValidationHook(uint32, address, uint256, bytes calldata, bytes calldata) external pure override @@ -73,7 +78,11 @@ abstract contract MockBaseUserOpValidationPlugin is IValidation, IValidationHook revert NotImplemented(); } - function validateRuntime(uint8, address, uint256, bytes calldata, bytes calldata) external pure override { + function validateRuntime(address, uint32, address, uint256, bytes calldata, bytes calldata) + external + pure + override + { revert NotImplemented(); } } @@ -108,7 +117,7 @@ contract MockUserOpValidationPlugin is MockBaseUserOpValidationPlugin { manifest.validationFunctions = new ManifestValidation[](1); manifest.validationFunctions[0] = ManifestValidation({ - functionId: uint8(FunctionId.USER_OP_VALIDATION), + entityId: uint32(EntityId.USER_OP_VALIDATION), isDefault: false, isSignatureValidation: false, selectors: validationSelectors @@ -151,7 +160,7 @@ contract MockUserOpValidation1HookPlugin is MockBaseUserOpValidationPlugin { manifest.validationFunctions = new ManifestValidation[](2); manifest.validationFunctions[0] = ManifestValidation({ - functionId: uint8(FunctionId.USER_OP_VALIDATION), + entityId: uint32(EntityId.USER_OP_VALIDATION), isDefault: false, isSignatureValidation: false, selectors: validationSelectors @@ -197,7 +206,7 @@ contract MockUserOpValidation2HookPlugin is MockBaseUserOpValidationPlugin { manifest.validationFunctions = new ManifestValidation[](1); manifest.validationFunctions[0] = ManifestValidation({ - functionId: uint8(FunctionId.USER_OP_VALIDATION), + entityId: uint32(EntityId.USER_OP_VALIDATION), isDefault: false, isSignatureValidation: false, selectors: validationSelectors diff --git a/test/plugin/ERC20TokenLimitPlugin.t.sol b/test/plugin/ERC20TokenLimitPlugin.t.sol index 96a18c20..9693f98f 100644 --- a/test/plugin/ERC20TokenLimitPlugin.t.sol +++ b/test/plugin/ERC20TokenLimitPlugin.t.sol @@ -6,16 +6,15 @@ import {MockERC20} from "../mocks/MockERC20.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {FunctionReference} from "../../src/helpers/FunctionReferenceLib.sol"; +import {PluginEntity} from "../../src/helpers/PluginEntityLib.sol"; import {ERC20TokenLimitPlugin} from "../../src/plugins/ERC20TokenLimitPlugin.sol"; import {MockPlugin} from "../mocks/MockPlugin.sol"; import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; -import {FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; +import {PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; import {IStandardExecutor, Call} from "../../src/interfaces/IStandardExecutor.sol"; import {PluginManifest} from "../../src/interfaces/IPlugin.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; -import {MSCAFactoryFixture} from "../mocks/MSCAFactoryFixture.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; contract ERC20TokenLimitPluginTest is AccountTestBase { @@ -24,7 +23,7 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { address payable public bundler = payable(address(2)); PluginManifest internal _m; MockPlugin public validationPlugin = new MockPlugin(_m); - FunctionReference public validationFunction; + PluginEntity public validationFunction; UpgradeableModularAccount public acct; ERC20TokenLimitPlugin public plugin = new ERC20TokenLimitPlugin(); @@ -32,8 +31,6 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { function setUp() public { // Set up a validator with hooks from the erc20 spend limit plugin attached - MSCAFactoryFixture factory = new MSCAFactoryFixture(entryPoint, _deploySingleOwnerPlugin()); - acct = factory.createAccount(address(this), 0); erc20 = new MockERC20(); @@ -41,7 +38,7 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { ExecutionHook[] memory permissionHooks = new ExecutionHook[](1); permissionHooks[0] = ExecutionHook({ - hookFunction: FunctionReferenceLib.pack(address(plugin), 0), + hookFunction: PluginEntityLib.pack(address(plugin), 0), isPreHook: true, isPostHook: false }); @@ -65,7 +62,7 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { abi.encode(permissionHooks, permissionInitDatas) ); - validationFunction = FunctionReferenceLib.pack(address(validationPlugin), 0); + validationFunction = PluginEntityLib.pack(address(validationPlugin), 0); } function _getPackedUO(bytes memory callData) internal view returns (PackedUserOperation memory uo) { @@ -78,7 +75,7 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { preVerificationGas: 200000, gasFees: bytes32(uint256(uint128(0))), paymasterAndData: "", - signature: _encodeSignature(FunctionReferenceLib.pack(address(validationPlugin), 0), 1, "") + signature: _encodeSignature(PluginEntityLib.pack(address(validationPlugin), 0), 1, "") }); } @@ -157,7 +154,7 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether); acct.executeWithAuthorization( _getExecuteWithSpend(5 ether), - _encodeSignature(FunctionReferenceLib.pack(address(validationPlugin), 0), 1, "") + _encodeSignature(PluginEntityLib.pack(address(validationPlugin), 0), 1, "") ); assertEq(plugin.limits(0, address(erc20), address(acct)), 5 ether); } @@ -177,7 +174,7 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether); acct.executeWithAuthorization( abi.encodeCall(IStandardExecutor.executeBatch, (calls)), - _encodeSignature(FunctionReferenceLib.pack(address(validationPlugin), 0), 1, "") + _encodeSignature(PluginEntityLib.pack(address(validationPlugin), 0), 1, "") ); assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether - 6 ether - 100001); } diff --git a/test/plugin/NativeTokenLimitPlugin.t.sol b/test/plugin/NativeTokenLimitPlugin.t.sol index 5fbf2a3d..33635216 100644 --- a/test/plugin/NativeTokenLimitPlugin.t.sol +++ b/test/plugin/NativeTokenLimitPlugin.t.sol @@ -4,16 +4,15 @@ pragma solidity ^0.8.19; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {FunctionReference} from "../../src/helpers/FunctionReferenceLib.sol"; +import {PluginEntity} from "../../src/helpers/PluginEntityLib.sol"; import {NativeTokenLimitPlugin} from "../../src/plugins/NativeTokenLimitPlugin.sol"; import {MockPlugin} from "../mocks/MockPlugin.sol"; import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; -import {FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; +import {PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; import {IStandardExecutor, Call} from "../../src/interfaces/IStandardExecutor.sol"; import {PluginManifest} from "../../src/interfaces/IPlugin.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; -import {MSCAFactoryFixture} from "../mocks/MSCAFactoryFixture.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; contract NativeTokenLimitPluginTest is AccountTestBase { @@ -21,7 +20,7 @@ contract NativeTokenLimitPluginTest is AccountTestBase { address payable public bundler = payable(address(2)); PluginManifest internal _m; MockPlugin public validationPlugin = new MockPlugin(_m); - FunctionReference public validationFunction; + PluginEntity public validationFunction; UpgradeableModularAccount public acct; NativeTokenLimitPlugin public plugin = new NativeTokenLimitPlugin(); @@ -30,18 +29,16 @@ contract NativeTokenLimitPluginTest is AccountTestBase { function setUp() public { // Set up a validator with hooks from the gas spend limit plugin attached - MSCAFactoryFixture factory = new MSCAFactoryFixture(entryPoint, _deploySingleOwnerPlugin()); - acct = factory.createAccount(address(this), 0); vm.deal(address(acct), 10 ether); - FunctionReference[] memory preValidationHooks = new FunctionReference[](1); - preValidationHooks[0] = FunctionReferenceLib.pack(address(plugin), 0); + PluginEntity[] memory preValidationHooks = new PluginEntity[](1); + preValidationHooks[0] = PluginEntityLib.pack(address(plugin), 0); ExecutionHook[] memory permissionHooks = new ExecutionHook[](1); permissionHooks[0] = ExecutionHook({ - hookFunction: FunctionReferenceLib.pack(address(plugin), 0), + hookFunction: PluginEntityLib.pack(address(plugin), 0), isPreHook: true, isPostHook: false }); @@ -64,7 +61,7 @@ contract NativeTokenLimitPluginTest is AccountTestBase { abi.encode(permissionHooks, permissionInitDatas) ); - validationFunction = FunctionReferenceLib.pack(address(validationPlugin), 0); + validationFunction = PluginEntityLib.pack(address(validationPlugin), 0); } function _getExecuteWithValue(uint256 value) internal view returns (bytes memory) { @@ -85,7 +82,7 @@ contract NativeTokenLimitPluginTest is AccountTestBase { preVerificationGas: gas3, gasFees: bytes32(uint256(uint128(gasPrice))), paymasterAndData: "", - signature: _encodeSignature(FunctionReferenceLib.pack(address(validationPlugin), 0), 1, "") + signature: _encodeSignature(PluginEntityLib.pack(address(validationPlugin), 0), 1, "") }); } diff --git a/test/plugin/SingleOwnerPlugin.t.sol b/test/plugin/SingleOwnerPlugin.t.sol deleted file mode 100644 index ddfe4d41..00000000 --- a/test/plugin/SingleOwnerPlugin.t.sol +++ /dev/null @@ -1,186 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; - -import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.sol"; -import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; -import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; - -import {SingleOwnerPlugin} from "../../src/plugins/owner/SingleOwnerPlugin.sol"; - -import {ContractOwner} from "../mocks/ContractOwner.sol"; -import {OptimizedTest} from "../utils/OptimizedTest.sol"; -import {TEST_DEFAULT_OWNER_FUNCTION_ID} from "../utils/TestConstants.sol"; - -contract SingleOwnerPluginTest is OptimizedTest { - using ECDSA for bytes32; - using MessageHashUtils for bytes32; - - SingleOwnerPlugin public plugin; - EntryPoint public entryPoint; - - bytes4 internal constant _1271_MAGIC_VALUE = 0x1626ba7e; - address public a; - address public b; - - address public owner1; - address public owner2; - ContractOwner public contractOwner; - - // Event declarations (needed for vm.expectEmit) - event OwnershipTransferred(address indexed account, address indexed previousOwner, address indexed newOwner); - - function setUp() public { - plugin = _deploySingleOwnerPlugin(); - entryPoint = new EntryPoint(); - - a = makeAddr("a"); - b = makeAddr("b"); - owner1 = makeAddr("owner1"); - owner2 = makeAddr("owner2"); - contractOwner = new ContractOwner(); - } - - // Tests: - // - uninitialized owner is zero address - // - transferOwnership result is returned via owner afterwards - // - transferOwnership emits OwnershipTransferred event - // - owner() returns correct value after transferOwnership - // - owner() does not return a different account's owner - // - requireFromOwner succeeds when called by owner - // - requireFromOwner reverts when called by non-owner - - function test_uninitializedOwner() public { - vm.startPrank(a); - assertEq(address(0), plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); - } - - function test_ownerInitialization() public { - vm.startPrank(a); - assertEq(address(0), plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); - plugin.transferOwnership(TEST_DEFAULT_OWNER_FUNCTION_ID, owner1); - assertEq(owner1, plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); - } - - function test_ownerInitializationEvent() public { - vm.startPrank(a); - assertEq(address(0), plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); - - vm.expectEmit(true, true, true, true); - emit OwnershipTransferred(a, address(0), owner1); - - plugin.transferOwnership(TEST_DEFAULT_OWNER_FUNCTION_ID, owner1); - assertEq(owner1, plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); - } - - function test_ownerMigration() public { - vm.startPrank(a); - assertEq(address(0), plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); - plugin.transferOwnership(TEST_DEFAULT_OWNER_FUNCTION_ID, owner1); - assertEq(owner1, plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); - plugin.transferOwnership(TEST_DEFAULT_OWNER_FUNCTION_ID, owner2); - assertEq(owner2, plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); - } - - function test_ownerMigrationEvents() public { - vm.startPrank(a); - assertEq(address(0), plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); - - vm.expectEmit(true, true, true, true); - emit OwnershipTransferred(a, address(0), owner1); - - plugin.transferOwnership(TEST_DEFAULT_OWNER_FUNCTION_ID, owner1); - assertEq(owner1, plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); - - vm.expectEmit(true, true, true, true); - emit OwnershipTransferred(a, owner1, owner2); - - plugin.transferOwnership(TEST_DEFAULT_OWNER_FUNCTION_ID, owner2); - assertEq(owner2, plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); - } - - function test_ownerForSender() public { - vm.startPrank(a); - assertEq(address(0), plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); - plugin.transferOwnership(TEST_DEFAULT_OWNER_FUNCTION_ID, owner1); - assertEq(owner1, plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); - vm.startPrank(b); - assertEq(address(0), plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, b)); - plugin.transferOwnership(TEST_DEFAULT_OWNER_FUNCTION_ID, owner2); - assertEq(owner2, plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, b)); - } - - function test_requireOwner() public { - vm.startPrank(a); - assertEq(address(0), plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); - plugin.transferOwnership(TEST_DEFAULT_OWNER_FUNCTION_ID, owner1); - assertEq(owner1, plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); - plugin.validateRuntime(TEST_DEFAULT_OWNER_FUNCTION_ID, owner1, 0, "", ""); - - vm.startPrank(b); - vm.expectRevert(SingleOwnerPlugin.NotAuthorized.selector); - plugin.validateRuntime(TEST_DEFAULT_OWNER_FUNCTION_ID, owner1, 0, "", ""); - } - - function testFuzz_validateUserOpSig(string memory salt, PackedUserOperation memory userOp) public { - // range bound the possible set of priv keys - (address signer, uint256 privateKey) = makeAddrAndKey(salt); - - vm.startPrank(a); - bytes32 userOpHash = entryPoint.getUserOpHash(userOp); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, userOpHash.toEthSignedMessageHash()); - - // sig cannot cover the whole userop struct since userop struct has sig field - userOp.signature = abi.encodePacked(r, s, v); - - // sig check should fail - uint256 success = plugin.validateUserOp(TEST_DEFAULT_OWNER_FUNCTION_ID, userOp, userOpHash); - assertEq(success, 1); - - // transfer ownership to signer - plugin.transferOwnership(TEST_DEFAULT_OWNER_FUNCTION_ID, signer); - assertEq(signer, plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); - - // sig check should pass - success = plugin.validateUserOp(TEST_DEFAULT_OWNER_FUNCTION_ID, userOp, userOpHash); - assertEq(success, 0); - } - - function testFuzz_isValidSignatureForEOAOwner(string memory salt, bytes32 digest) public { - // range bound the possible set of priv keys - (address signer, uint256 privateKey) = makeAddrAndKey(salt); - - vm.startPrank(a); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest); - - // sig check should fail - assertEq( - plugin.validateSignature( - TEST_DEFAULT_OWNER_FUNCTION_ID, address(this), digest, abi.encodePacked(r, s, v) - ), - bytes4(0xFFFFFFFF) - ); - - // transfer ownership to signer - plugin.transferOwnership(TEST_DEFAULT_OWNER_FUNCTION_ID, signer); - assertEq(signer, plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); - - // sig check should pass - assertEq( - plugin.validateSignature( - TEST_DEFAULT_OWNER_FUNCTION_ID, address(this), digest, abi.encodePacked(r, s, v) - ), - _1271_MAGIC_VALUE - ); - } - - function testFuzz_isValidSignatureForContractOwner(bytes32 digest) public { - vm.startPrank(a); - plugin.transferOwnership(TEST_DEFAULT_OWNER_FUNCTION_ID, address(contractOwner)); - bytes memory signature = contractOwner.sign(digest); - assertEq( - plugin.validateSignature(TEST_DEFAULT_OWNER_FUNCTION_ID, address(this), digest, signature), - _1271_MAGIC_VALUE - ); - } -} diff --git a/test/plugin/TokenReceiverPlugin.t.sol b/test/plugin/TokenReceiverPlugin.t.sol index 2f52a988..1e198f0b 100644 --- a/test/plugin/TokenReceiverPlugin.t.sol +++ b/test/plugin/TokenReceiverPlugin.t.sol @@ -8,7 +8,7 @@ import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Re import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {TokenReceiverPlugin} from "../../src/plugins/TokenReceiverPlugin.sol"; -import {MSCAFactoryFixture} from "../mocks/MSCAFactoryFixture.sol"; +import {SingleSignerFactoryFixture} from "../mocks/SingleSignerFactoryFixture.sol"; import {MockERC721} from "../mocks/MockERC721.sol"; import {MockERC1155} from "../mocks/MockERC1155.sol"; import {OptimizedTest} from "../utils/OptimizedTest.sol"; @@ -33,7 +33,8 @@ contract TokenReceiverPluginTest is OptimizedTest, IERC1155Receiver { function setUp() public { entryPoint = new EntryPoint(); - MSCAFactoryFixture factory = new MSCAFactoryFixture(entryPoint, _deploySingleOwnerPlugin()); + SingleSignerFactoryFixture factory = + new SingleSignerFactoryFixture(entryPoint, _deploySingleSignerValidation()); acct = factory.createAccount(address(this), 0); plugin = _deployTokenReceiverPlugin(); diff --git a/test/samples/AllowlistPlugin.t.sol b/test/samples/AllowlistPlugin.t.sol index d81d5f79..441fbdcb 100644 --- a/test/samples/AllowlistPlugin.t.sol +++ b/test/samples/AllowlistPlugin.t.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.25; import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; import {Call} from "../../src/interfaces/IStandardExecutor.sol"; -import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; +import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {AllowlistPlugin} from "../../src/samples/permissionhooks/AllowlistPlugin.sol"; @@ -184,7 +184,7 @@ contract AllowlistPluginTest is CustomValidationTestBase { return abi.encodeWithSelector( UpgradeableModularAccount.PreRuntimeValidationHookFailed.selector, address(allowlistPlugin), - uint8(AllowlistPlugin.FunctionId.PRE_VALIDATION_HOOK), + uint32(AllowlistPlugin.EntityId.PRE_VALIDATION_HOOK), abi.encodeWithSelector(AllowlistPlugin.SelectorNotAllowed.selector) ); } @@ -192,7 +192,7 @@ contract AllowlistPluginTest is CustomValidationTestBase { return abi.encodeWithSelector( UpgradeableModularAccount.PreRuntimeValidationHookFailed.selector, address(allowlistPlugin), - uint8(AllowlistPlugin.FunctionId.PRE_VALIDATION_HOOK), + uint32(AllowlistPlugin.EntityId.PRE_VALIDATION_HOOK), abi.encodeWithSelector(AllowlistPlugin.TargetNotAllowed.selector) ); } @@ -290,13 +290,12 @@ contract AllowlistPluginTest is CustomValidationTestBase { internal virtual override - returns (FunctionReference, bool, bool, bytes4[] memory, bytes memory, bytes memory, bytes memory) + returns (PluginEntity, bool, bool, bytes4[] memory, bytes memory, bytes memory, bytes memory) { - FunctionReference accessControlHook = FunctionReferenceLib.pack( - address(allowlistPlugin), uint8(AllowlistPlugin.FunctionId.PRE_VALIDATION_HOOK) - ); + PluginEntity accessControlHook = + PluginEntityLib.pack(address(allowlistPlugin), uint32(AllowlistPlugin.EntityId.PRE_VALIDATION_HOOK)); - FunctionReference[] memory preValidationHooks = new FunctionReference[](1); + PluginEntity[] memory preValidationHooks = new PluginEntity[](1); preValidationHooks[0] = accessControlHook; bytes[] memory preValidationHookData = new bytes[](1); @@ -306,11 +305,11 @@ contract AllowlistPluginTest is CustomValidationTestBase { bytes memory packedPreValidationHooks = abi.encode(preValidationHooks, preValidationHookData); return ( - _ownerValidation, + _signerValidation, true, true, new bytes4[](0), - abi.encode(TEST_DEFAULT_OWNER_FUNCTION_ID, owner1), + abi.encode(TEST_DEFAULT_VALIDATION_ENTITY_ID, owner1), packedPreValidationHooks, "" ); diff --git a/test/utils/AccountTestBase.sol b/test/utils/AccountTestBase.sol index a312fbdf..526365d0 100644 --- a/test/utils/AccountTestBase.sol +++ b/test/utils/AccountTestBase.sol @@ -5,38 +5,40 @@ import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.so import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; -import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; +import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; +import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; import {IStandardExecutor, Call} from "../../src/interfaces/IStandardExecutor.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {SingleOwnerPlugin} from "../../src/plugins/owner/SingleOwnerPlugin.sol"; import {OptimizedTest} from "./OptimizedTest.sol"; -import {TEST_DEFAULT_OWNER_FUNCTION_ID as EXT_CONST_TEST_DEFAULT_OWNER_FUNCTION_ID} from "./TestConstants.sol"; +import {TEST_DEFAULT_VALIDATION_ENTITY_ID as EXT_CONST_TEST_DEFAULT_VALIDATION_ENTITY_ID} from + "./TestConstants.sol"; -import {MSCAFactoryFixture} from "../mocks/MSCAFactoryFixture.sol"; +import {SingleSignerFactoryFixture} from "../mocks/SingleSignerFactoryFixture.sol"; /// @dev This contract handles common boilerplate setup for tests using UpgradeableModularAccount with -/// SingleOwnerPlugin. +/// SingleSignerValidation. abstract contract AccountTestBase is OptimizedTest { - using FunctionReferenceLib for FunctionReference; + using PluginEntityLib for PluginEntity; using MessageHashUtils for bytes32; EntryPoint public entryPoint; address payable public beneficiary; - SingleOwnerPlugin public singleOwnerPlugin; - MSCAFactoryFixture public factory; + + SingleSignerValidation public singleSignerValidation; + SingleSignerFactoryFixture public factory; address public owner1; uint256 public owner1Key; UpgradeableModularAccount public account1; - FunctionReference internal _ownerValidation; + PluginEntity internal _signerValidation; uint8 public constant SELECTOR_ASSOCIATED_VALIDATION = 0; uint8 public constant GLOBAL_VALIDATION = 1; // Re-declare the constant to prevent derived test contracts from having to import it - uint8 public constant TEST_DEFAULT_OWNER_FUNCTION_ID = EXT_CONST_TEST_DEFAULT_OWNER_FUNCTION_ID; + uint32 public constant TEST_DEFAULT_VALIDATION_ENTITY_ID = EXT_CONST_TEST_DEFAULT_VALIDATION_ENTITY_ID; uint256 public constant CALL_GAS_LIMIT = 100000; uint256 public constant VERIFICATION_GAS_LIMIT = 1200000; @@ -51,13 +53,14 @@ abstract contract AccountTestBase is OptimizedTest { (owner1, owner1Key) = makeAddrAndKey("owner1"); beneficiary = payable(makeAddr("beneficiary")); - singleOwnerPlugin = _deploySingleOwnerPlugin(); - factory = new MSCAFactoryFixture(entryPoint, singleOwnerPlugin); + singleSignerValidation = _deploySingleSignerValidation(); + factory = new SingleSignerFactoryFixture(entryPoint, singleSignerValidation); account1 = factory.createAccount(owner1, 0); vm.deal(address(account1), 100 ether); - _ownerValidation = FunctionReferenceLib.pack(address(singleOwnerPlugin), TEST_DEFAULT_OWNER_FUNCTION_ID); + _signerValidation = + PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID); } function _runExecUserOp(address target, bytes memory callData) internal { @@ -100,7 +103,7 @@ abstract contract AccountTestBase is OptimizedTest { (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); userOp.signature = _encodeSignature( - FunctionReferenceLib.pack(address(singleOwnerPlugin), TEST_DEFAULT_OWNER_FUNCTION_ID), + PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, abi.encodePacked(r, s, v) ); @@ -153,7 +156,7 @@ abstract contract AccountTestBase is OptimizedTest { account1.executeWithAuthorization( callData, _encodeSignature( - FunctionReferenceLib.pack(address(singleOwnerPlugin), TEST_DEFAULT_OWNER_FUNCTION_ID), + PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, "" ) @@ -168,7 +171,7 @@ abstract contract AccountTestBase is OptimizedTest { account1.executeWithAuthorization( callData, _encodeSignature( - FunctionReferenceLib.pack(address(singleOwnerPlugin), TEST_DEFAULT_OWNER_FUNCTION_ID), + PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, "" ) @@ -182,15 +185,15 @@ abstract contract AccountTestBase is OptimizedTest { abi.encodeCall( account1.execute, ( - address(singleOwnerPlugin), + address(singleSignerValidation), 0, abi.encodeCall( - SingleOwnerPlugin.transferOwnership, (TEST_DEFAULT_OWNER_FUNCTION_ID, address(this)) + SingleSignerValidation.transferSigner, (TEST_DEFAULT_VALIDATION_ENTITY_ID, address(this)) ) ) ), _encodeSignature( - FunctionReferenceLib.pack(address(singleOwnerPlugin), TEST_DEFAULT_OWNER_FUNCTION_ID), + PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, "" ) @@ -204,7 +207,7 @@ abstract contract AccountTestBase is OptimizedTest { // helper function to encode a signature, according to the per-hook and per-validation data format. function _encodeSignature( - FunctionReference validationFunction, + PluginEntity validationFunction, uint8 globalOrNot, PreValidationHookData[] memory preValidationHookData, bytes memory validationData @@ -214,7 +217,7 @@ abstract contract AccountTestBase is OptimizedTest { for (uint256 i = 0; i < preValidationHookData.length; ++i) { sig = abi.encodePacked( sig, - _packValidationDataWithIndex( + _packValidationResWithIndex( preValidationHookData[i].index, preValidationHookData[i].validationData ) ); @@ -222,13 +225,13 @@ abstract contract AccountTestBase is OptimizedTest { // Index of the actual validation data is the length of the preValidationHooksRetrieved - aka // one-past-the-end - sig = abi.encodePacked(sig, _packValidationDataWithIndex(255, validationData)); + sig = abi.encodePacked(sig, _packValidationResWithIndex(255, validationData)); return sig; } // overload for the case where there are no pre-validation hooks - function _encodeSignature(FunctionReference validationFunction, uint8 globalOrNot, bytes memory validationData) + function _encodeSignature(PluginEntity validationFunction, uint8 globalOrNot, bytes memory validationData) internal pure returns (bytes memory) @@ -238,7 +241,7 @@ abstract contract AccountTestBase is OptimizedTest { } // helper function to pack validation data with an index, according to the sparse calldata segment spec. - function _packValidationDataWithIndex(uint8 index, bytes memory validationData) + function _packValidationResWithIndex(uint8 index, bytes memory validationData) internal pure returns (bytes memory) diff --git a/test/utils/CustomValidationTestBase.sol b/test/utils/CustomValidationTestBase.sol index 2244c865..bd119fa1 100644 --- a/test/utils/CustomValidationTestBase.sol +++ b/test/utils/CustomValidationTestBase.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.25; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import {FunctionReference} from "../../src/helpers/FunctionReferenceLib.sol"; +import {PluginEntity} from "../../src/helpers/PluginEntityLib.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; @@ -16,7 +16,7 @@ import {AccountTestBase} from "./AccountTestBase.sol"; abstract contract CustomValidationTestBase is AccountTestBase { function _customValidationSetup() internal { ( - FunctionReference validationFunction, + PluginEntity validationFunction, bool isGlobal, bool isSignatureValidation, bytes4[] memory selectors, @@ -44,7 +44,7 @@ abstract contract CustomValidationTestBase is AccountTestBase { internal virtual returns ( - FunctionReference validationFunction, + PluginEntity validationFunction, bool shared, bool isSignatureValidation, bytes4[] memory selectors, diff --git a/test/utils/OptimizedTest.sol b/test/utils/OptimizedTest.sol index f9431acc..d884193f 100644 --- a/test/utils/OptimizedTest.sol +++ b/test/utils/OptimizedTest.sol @@ -6,7 +6,7 @@ import {Test} from "forge-std/Test.sol"; import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {SingleOwnerPlugin} from "../../src/plugins/owner/SingleOwnerPlugin.sol"; +import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; import {TokenReceiverPlugin} from "../../src/plugins/TokenReceiverPlugin.sol"; /// @dev This contract provides functions to deploy optimized (via IR) precompiled contracts. By compiling just @@ -44,15 +44,17 @@ abstract contract OptimizedTest is Test { : new UpgradeableModularAccount(entryPoint); } - function _deploySingleOwnerPlugin() internal returns (SingleOwnerPlugin) { - return _isOptimizedTest() - ? SingleOwnerPlugin(deployCode("out-optimized/SingleOwnerPlugin.sol/SingleOwnerPlugin.json")) - : new SingleOwnerPlugin(); - } - function _deployTokenReceiverPlugin() internal returns (TokenReceiverPlugin) { return _isOptimizedTest() ? TokenReceiverPlugin(deployCode("out-optimized/TokenReceiverPlugin.sol/TokenReceiverPlugin.json")) : new TokenReceiverPlugin(); } + + function _deploySingleSignerValidation() internal returns (SingleSignerValidation) { + return _isOptimizedTest() + ? SingleSignerValidation( + deployCode("out-optimized/SingleSignerValidation.sol/SingleSignerValidation.json") + ) + : new SingleSignerValidation(); + } } diff --git a/test/utils/TestConstants.sol b/test/utils/TestConstants.sol index 923692a7..c15b2dd3 100644 --- a/test/utils/TestConstants.sol +++ b/test/utils/TestConstants.sol @@ -1,4 +1,4 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.25; -uint8 constant TEST_DEFAULT_OWNER_FUNCTION_ID = 0; +uint32 constant TEST_DEFAULT_VALIDATION_ENTITY_ID = 1; diff --git a/test/validation/SingleSignerValidation.t.sol b/test/validation/SingleSignerValidation.t.sol new file mode 100644 index 00000000..a983ea93 --- /dev/null +++ b/test/validation/SingleSignerValidation.t.sol @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; +import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; + +import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; +import {PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; +import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; + +import {AccountTestBase} from "../utils/AccountTestBase.sol"; +import {TEST_DEFAULT_VALIDATION_ENTITY_ID} from "../utils/TestConstants.sol"; +import {ContractOwner} from "../mocks/ContractOwner.sol"; + +contract SingleSignerValidationTest is AccountTestBase { + using MessageHashUtils for bytes32; + + bytes4 internal constant _1271_MAGIC_VALUE = 0x1626ba7e; + + address public ethRecipient; + address public owner2; + uint256 public owner2Key; + UpgradeableModularAccount public account; + + ContractOwner public contractOwner; + + function setUp() public { + ethRecipient = makeAddr("ethRecipient"); + (owner2, owner2Key) = makeAddrAndKey("owner2"); + account = factory.createAccount(owner1, 0); + vm.deal(address(account), 100 ether); + + contractOwner = new ContractOwner(); + } + + function test_userOpValidation() public { + PackedUserOperation memory userOp = PackedUserOperation({ + sender: address(account), + nonce: 0, + initCode: "", + callData: abi.encodeCall(UpgradeableModularAccount.execute, (ethRecipient, 1 wei, "")), + accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), + preVerificationGas: 0, + gasFees: _encodeGas(1, 1), + paymasterAndData: "", + signature: "" + }); + + // Generate signature + bytes32 userOpHash = entryPoint.getUserOpHash(userOp); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); + userOp.signature = _encodeSignature( + PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), + GLOBAL_VALIDATION, + abi.encodePacked(r, s, v) + ); + + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); + userOps[0] = userOp; + + entryPoint.handleOps(userOps, beneficiary); + + assertEq(ethRecipient.balance, 1 wei); + } + + function test_runtimeValidate() public { + vm.prank(owner1); + account.executeWithAuthorization( + abi.encodeCall(UpgradeableModularAccount.execute, (ethRecipient, 1 wei, "")), + _encodeSignature( + PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), + GLOBAL_VALIDATION, + "" + ) + ); + assertEq(ethRecipient.balance, 1 wei); + } + + function test_runtime_with2SameValidationInstalled() public { + uint32 newEntityId = TEST_DEFAULT_VALIDATION_ENTITY_ID + 1; + vm.prank(address(entryPoint)); + account.installValidation( + ValidationConfigLib.pack(address(singleSignerValidation), newEntityId, true, false), + new bytes4[](0), + abi.encode(newEntityId, owner2), + "", + "" + ); + + vm.prank(owner2); + account.executeWithAuthorization( + abi.encodeCall(UpgradeableModularAccount.execute, (ethRecipient, 1 wei, "")), + _encodeSignature( + PluginEntityLib.pack(address(singleSignerValidation), newEntityId), GLOBAL_VALIDATION, "" + ) + ); + assertEq(ethRecipient.balance, 1 wei); + } + + function testFuzz_isValidSignatureForEOAOwner(string memory salt, bytes32 digest) public { + // range bound the possible set of priv keys + (address signer, uint256 privateKey) = makeAddrAndKey(salt); + + address accountAddr = address(account); + + vm.startPrank(accountAddr); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest); + + // sig check should fail + assertEq( + singleSignerValidation.validateSignature( + accountAddr, TEST_DEFAULT_VALIDATION_ENTITY_ID, address(this), digest, abi.encodePacked(r, s, v) + ), + bytes4(0xFFFFFFFF) + ); + + // transfer ownership to signer + singleSignerValidation.transferSigner(TEST_DEFAULT_VALIDATION_ENTITY_ID, signer); + assertEq(signer, singleSignerValidation.signerOf(TEST_DEFAULT_VALIDATION_ENTITY_ID, accountAddr)); + + // sig check should pass + assertEq( + singleSignerValidation.validateSignature( + accountAddr, TEST_DEFAULT_VALIDATION_ENTITY_ID, address(this), digest, abi.encodePacked(r, s, v) + ), + _1271_MAGIC_VALUE + ); + } + + function testFuzz_isValidSignatureForContractOwner(bytes32 digest) public { + address accountAddr = address(account); + vm.startPrank(accountAddr); + singleSignerValidation.transferSigner(TEST_DEFAULT_VALIDATION_ENTITY_ID, address(contractOwner)); + bytes memory signature = contractOwner.sign(digest); + assertEq( + singleSignerValidation.validateSignature( + accountAddr, TEST_DEFAULT_VALIDATION_ENTITY_ID, address(this), digest, signature + ), + _1271_MAGIC_VALUE + ); + } +} From a598069515e63a992fcfec93a2d05ac3b8b577a7 Mon Sep 17 00:00:00 2001 From: Zer0dot Date: Wed, 17 Jul 2024 23:00:14 +0800 Subject: [PATCH 045/111] Allow direct plugin calls with validation & permission hooks (#90) --- .solhint-test.json | 36 +++--- src/account/AccountStorage.sol | 2 +- src/account/PluginManager2.sol | 35 +++--- src/account/UpgradeableModularAccount.sol | 131 ++++++++++++------- src/helpers/ValidationConfigLib.sol | 2 +- test/account/DirectCallsFromPlugin.t.sol | 133 ++++++++++++++++++++ test/account/PermittedCallPermissions.t.sol | 4 +- test/account/SelfCallAuthorization.t.sol | 18 +-- test/mocks/plugins/DirectCallPlugin.sol | 47 +++++++ 9 files changed, 315 insertions(+), 93 deletions(-) create mode 100644 test/account/DirectCallsFromPlugin.t.sol create mode 100644 test/mocks/plugins/DirectCallPlugin.sol diff --git a/.solhint-test.json b/.solhint-test.json index fd2b1007..3224b9d0 100644 --- a/.solhint-test.json +++ b/.solhint-test.json @@ -1,20 +1,20 @@ { - "extends": "solhint:recommended", - "rules": { - "func-name-mixedcase": "off", - "immutable-vars-naming": ["error"], - "no-unused-import": ["error"], - "compiler-version": ["error", ">=0.8.19"], - "custom-errors": "off", - "func-visibility": ["error", { "ignoreConstructors": true }], - "max-line-length": ["error", 120], - "max-states-count": ["warn", 30], - "modifier-name-mixedcase": ["error"], - "private-vars-leading-underscore": ["error"], - "no-inline-assembly": "off", - "avoid-low-level-calls": "off", - "one-contract-per-file": "off", - "no-empty-blocks": "off" - } + "extends": "solhint:recommended", + "rules": { + "func-name-mixedcase": "off", + "immutable-vars-naming": ["error"], + "no-unused-import": ["error"], + "compiler-version": ["error", ">=0.8.19"], + "custom-errors": "off", + "func-visibility": ["error", { "ignoreConstructors": true }], + "max-line-length": ["error", 120], + "max-states-count": ["warn", 30], + "modifier-name-mixedcase": ["error"], + "private-vars-leading-underscore": ["error"], + "no-inline-assembly": "off", + "avoid-low-level-calls": "off", + "one-contract-per-file": "off", + "no-empty-blocks": "off", + "reason-string": ["warn", { "maxLength": 64 }] } - \ No newline at end of file +} diff --git a/src/account/AccountStorage.sol b/src/account/AccountStorage.sol index df081bd0..aa56f75d 100644 --- a/src/account/AccountStorage.sol +++ b/src/account/AccountStorage.sol @@ -30,7 +30,7 @@ struct ValidationData { bool isGlobal; // Whether or not this validation is a signature validator. bool isSignatureValidation; - // The pre validation hooks for this function selector. + // The pre validation hooks for this validation function. PluginEntity[] preValidationHooks; // Permission hooks for this validation function. EnumerableSet.Bytes32Set permissionHooks; diff --git a/src/account/PluginManager2.sol b/src/account/PluginManager2.sol index 1f121880..2ff6a9d1 100644 --- a/src/account/PluginManager2.sol +++ b/src/account/PluginManager2.sol @@ -7,7 +7,7 @@ import {IPlugin} from "../interfaces/IPlugin.sol"; import {PluginEntity, ValidationConfig} from "../interfaces/IPluginManager.sol"; import {PluginEntityLib} from "../helpers/PluginEntityLib.sol"; import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; -import {ValidationData, getAccountStorage, toSetValue, toPluginEntity} from "./AccountStorage.sol"; +import {ValidationData, getAccountStorage, toSetValue} from "./AccountStorage.sol"; import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; // Temporary additional functions for a user-controlled install flow for validation functions. @@ -17,6 +17,7 @@ abstract contract PluginManager2 { // Index marking the start of the data for the validation function. uint8 internal constant _RESERVED_VALIDATION_DATA_INDEX = 255; + uint32 internal constant _SELF_PERMIT_VALIDATION_FUNCTIONID = type(uint32).max; error PreValidationAlreadySet(PluginEntity validationFunction, PluginEntity preValidationFunction); error ValidationAlreadySet(bytes4 selector, PluginEntity validationFunction); @@ -32,7 +33,7 @@ abstract contract PluginManager2 { bytes memory permissionHooks ) internal { ValidationData storage _validationData = - getAccountStorage().validationData[validationConfig.functionReference()]; + getAccountStorage().validationData[validationConfig.pluginEntity()]; if (preValidationHooks.length > 0) { (PluginEntity[] memory preValidationFunctions, bytes[] memory initDatas) = @@ -63,7 +64,7 @@ abstract contract PluginManager2 { ExecutionHook memory permissionFunction = permissionFunctions[i]; if (!_validationData.permissionHooks.add(toSetValue(permissionFunction))) { - revert PermissionAlreadySet(validationConfig.functionReference(), permissionFunction); + revert PermissionAlreadySet(validationConfig.pluginEntity(), permissionFunction); } if (initDatas[i].length > 0) { @@ -73,19 +74,21 @@ abstract contract PluginManager2 { } } - _validationData.isGlobal = validationConfig.isGlobal(); - _validationData.isSignatureValidation = validationConfig.isSignatureValidation(); - for (uint256 i = 0; i < selectors.length; ++i) { bytes4 selector = selectors[i]; if (!_validationData.selectors.add(toSetValue(selector))) { - revert ValidationAlreadySet(selector, validationConfig.functionReference()); + revert ValidationAlreadySet(selector, validationConfig.pluginEntity()); } } - if (installData.length > 0) { - address plugin = validationConfig.plugin(); - IPlugin(plugin).onInstall(installData); + if (validationConfig.entityId() != _SELF_PERMIT_VALIDATION_FUNCTIONID) { + // Only allow global validations and signature validations if they're not direct-call validations. + + _validationData.isGlobal = validationConfig.isGlobal(); + _validationData.isSignatureValidation = validationConfig.isSignatureValidation(); + if (installData.length > 0) { + IPlugin(validationConfig.plugin()).onInstall(installData); + } } } @@ -120,12 +123,12 @@ abstract contract PluginManager2 { // Clear permission hooks EnumerableSet.Bytes32Set storage permissionHooks = _validationData.permissionHooks; - uint256 i = 0; - while (permissionHooks.length() > 0) { - PluginEntity permissionHook = toPluginEntity(permissionHooks.at(0)); - permissionHooks.remove(toSetValue(permissionHook)); - (address permissionHookPlugin,) = PluginEntityLib.unpack(permissionHook); - IPlugin(permissionHookPlugin).onUninstall(permissionHookUninstallDatas[i++]); + uint256 len = permissionHooks.length(); + for (uint256 i = 0; i < len; ++i) { + bytes32 permissionHook = permissionHooks.at(0); + permissionHooks.remove(permissionHook); + address permissionHookPlugin = address(uint160(bytes20(permissionHook))); + IPlugin(permissionHookPlugin).onUninstall(permissionHookUninstallDatas[i]); } } delete _validationData.preValidationHooks; diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index acbd6de5..6c577ed2 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -78,7 +78,7 @@ contract UpgradeableModularAccount is error SignatureValidationInvalid(address plugin, uint32 entityId); error UnexpectedAggregator(address plugin, uint32 entityId, address aggregator); error UnrecognizedFunction(bytes4 selector); - error UserOpValidationFunctionMissing(bytes4 selector); + error ValidationFunctionMissing(bytes4 selector); error ValidationDoesNotApply(bytes4 selector, address plugin, uint32 entityId, bool isGlobal); error ValidationSignatureSegmentMissing(); error SignatureSegmentOutOfOrder(); @@ -86,14 +86,13 @@ contract UpgradeableModularAccount is // Wraps execution of a native function with runtime validation and hooks // Used for upgradeTo, upgradeToAndCall, execute, executeBatch, installPlugin, uninstallPlugin modifier wrapNativeFunction() { - _checkPermittedCallerIfNotFromEP(); - - PostExecToRun[] memory postExecHooks = - _doPreHooks(getAccountStorage().selectorData[msg.sig].executionHooks, msg.data); + (PostExecToRun[] memory postPermissionHooks, PostExecToRun[] memory postExecHooks) = + _checkPermittedCallerAndAssociatedHooks(); _; _doCachedPostExecHooks(postExecHooks); + _doCachedPostExecHooks(postPermissionHooks); } constructor(IEntryPoint anEntryPoint) { @@ -136,7 +135,7 @@ contract UpgradeableModularAccount is revert UnrecognizedFunction(msg.sig); } - _checkPermittedCallerIfNotFromEP(); + _checkPermittedCallerAndAssociatedHooks(); PostExecToRun[] memory postExecHooks; // Cache post-exec hooks in memory @@ -500,17 +499,7 @@ contract UpgradeableModularAccount is } else { currentAuthData = ""; } - - (address hookPlugin, uint32 hookEntityId) = preRuntimeValidationHooks[i].unpack(); - try IValidationHook(hookPlugin).preRuntimeValidationHook( - hookEntityId, msg.sender, msg.value, callData, currentAuthData - ) - // forgefmt: disable-start - // solhint-disable-next-line no-empty-blocks - {} catch (bytes memory revertReason) { - // forgefmt: disable-end - revert PreRuntimeValidationHookFailed(hookPlugin, hookEntityId, revertReason); - } + _doPreRuntimeValidationHook(preRuntimeValidationHooks[i], callData, currentAuthData); } if (authSegment.getIndex() != _RESERVED_VALIDATION_DATA_INDEX) { @@ -605,9 +594,78 @@ contract UpgradeableModularAccount is } } + function _doPreRuntimeValidationHook( + PluginEntity validationHook, + bytes memory callData, + bytes memory currentAuthData + ) internal { + (address hookPlugin, uint32 hookEntityId) = validationHook.unpack(); + try IValidationHook(hookPlugin).preRuntimeValidationHook( + hookEntityId, msg.sender, msg.value, callData, currentAuthData + ) + // forgefmt: disable-start + // solhint-disable-next-line no-empty-blocks + {} catch (bytes memory revertReason) { + // forgefmt: disable-end + revert PreRuntimeValidationHookFailed(hookPlugin, hookEntityId, revertReason); + } + } + // solhint-disable-next-line no-empty-blocks function _authorizeUpgrade(address newImplementation) internal override {} + /** + * Order of operations: + * 1. Check if the sender is the entry point, the account itself, or the selector called is public. + * - Yes: Return an empty array, there are no post-permissionHooks. + * - No: Continue + * 2. Check if the called selector (msg.sig) is included in the set of selectors the msg.sender can + * directly call. + * - Yes: Continue + * - No: Revert, the caller is not allowed to call this selector + * 3. If there are runtime validation hooks associated with this caller-sig combination, run them. + * 4. Run the pre-permissionHooks associated with this caller-sig combination, and return the + * post-permissionHooks to run later. + */ + function _checkPermittedCallerAndAssociatedHooks() + internal + returns (PostExecToRun[] memory, PostExecToRun[] memory) + { + AccountStorage storage _storage = getAccountStorage(); + + if ( + msg.sender == address(_ENTRY_POINT) || msg.sender == address(this) + || _storage.selectorData[msg.sig].isPublic + ) { + return (new PostExecToRun[](0), new PostExecToRun[](0)); + } + + PluginEntity directCallValidationKey = PluginEntityLib.pack(msg.sender, _SELF_PERMIT_VALIDATION_FUNCTIONID); + + _checkIfValidationAppliesCallData(msg.data, directCallValidationKey, false); + + // Direct call is allowed, run associated permission & validation hooks + + // Validation hooks + PluginEntity[] memory preRuntimeValidationHooks = + _storage.validationData[directCallValidationKey].preValidationHooks; + + uint256 hookLen = preRuntimeValidationHooks.length; + for (uint256 i = 0; i < hookLen; ++i) { + _doPreRuntimeValidationHook(preRuntimeValidationHooks[i], msg.data, ""); + } + + // Permission hooks + PostExecToRun[] memory postPermissionHooks = + _doPreHooks(_storage.validationData[directCallValidationKey].permissionHooks, msg.data); + + // Exec hooks + PostExecToRun[] memory postExecutionHooks = + _doPreHooks(_storage.selectorData[msg.sig].executionHooks, msg.data); + + return (postPermissionHooks, postExecutionHooks); + } + function _checkIfValidationAppliesCallData( bytes calldata callData, PluginEntity validationFunction, @@ -661,25 +719,6 @@ contract UpgradeableModularAccount is } } - function _checkIfValidationAppliesSelector(bytes4 selector, PluginEntity validationFunction, bool isGlobal) - internal - view - { - AccountStorage storage _storage = getAccountStorage(); - - // Check that the provided validation function is applicable to the selector - if (isGlobal) { - if (!_globalValidationAllowed(selector) || !_storage.validationData[validationFunction].isGlobal) { - revert UserOpValidationFunctionMissing(selector); - } - } else { - // Not global validation, but per-selector - if (!getAccountStorage().validationData[validationFunction].selectors.contains(toSetValue(selector))) { - revert UserOpValidationFunctionMissing(selector); - } - } - } - function _globalValidationAllowed(bytes4 selector) internal view returns (bool) { if ( selector == this.execute.selector || selector == this.executeBatch.selector @@ -693,14 +732,22 @@ contract UpgradeableModularAccount is return getAccountStorage().selectorData[selector].allowGlobalValidation; } - function _checkPermittedCallerIfNotFromEP() internal view { + function _checkIfValidationAppliesSelector(bytes4 selector, PluginEntity validationFunction, bool isGlobal) + internal + view + { AccountStorage storage _storage = getAccountStorage(); - if ( - msg.sender != address(_ENTRY_POINT) && msg.sender != address(this) - && !_storage.selectorData[msg.sig].isPublic - ) { - revert ExecFromPluginNotPermitted(msg.sender, msg.sig); + // Check that the provided validation function is applicable to the selector + if (isGlobal) { + if (!_globalValidationAllowed(selector) || !_storage.validationData[validationFunction].isGlobal) { + revert ValidationFunctionMissing(selector); + } + } else { + // Not global validation, but per-selector + if (!getAccountStorage().validationData[validationFunction].selectors.contains(toSetValue(selector))) { + revert ValidationFunctionMissing(selector); + } } } } diff --git a/src/helpers/ValidationConfigLib.sol b/src/helpers/ValidationConfigLib.sol index 95e8ea90..6d27b907 100644 --- a/src/helpers/ValidationConfigLib.sol +++ b/src/helpers/ValidationConfigLib.sol @@ -78,7 +78,7 @@ library ValidationConfigLib { return uint32(bytes4(ValidationConfig.unwrap(config) << 160)); } - function functionReference(ValidationConfig config) internal pure returns (PluginEntity) { + function pluginEntity(ValidationConfig config) internal pure returns (PluginEntity) { return PluginEntity.wrap(bytes24(ValidationConfig.unwrap(config))); } diff --git a/test/account/DirectCallsFromPlugin.t.sol b/test/account/DirectCallsFromPlugin.t.sol new file mode 100644 index 00000000..1c0fcca8 --- /dev/null +++ b/test/account/DirectCallsFromPlugin.t.sol @@ -0,0 +1,133 @@ +pragma solidity ^0.8.19; + +import {DirectCallPlugin} from "../mocks/plugins/DirectCallPlugin.sol"; +import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; +import {IStandardExecutor, Call} from "../../src/interfaces/IStandardExecutor.sol"; +import {PluginEntityLib, PluginEntity} from "../../src/helpers/PluginEntityLib.sol"; +import {ValidationConfig, ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; +import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; + +import {AccountTestBase} from "../utils/AccountTestBase.sol"; + +contract DirectCallsFromPluginTest is AccountTestBase { + using ValidationConfigLib for ValidationConfig; + + DirectCallPlugin internal _plugin; + PluginEntity internal _pluginEntity; + + function setUp() public { + _plugin = new DirectCallPlugin(); + assertFalse(_plugin.preHookRan()); + assertFalse(_plugin.postHookRan()); + _pluginEntity = PluginEntityLib.pack(address(_plugin), type(uint32).max); + } + + /* -------------------------------------------------------------------------- */ + /* Negatives */ + /* -------------------------------------------------------------------------- */ + + function test_Fail_DirectCallPluginNotInstalled() external { + vm.prank(address(_plugin)); + vm.expectRevert(_buildDirectCallDisallowedError(IStandardExecutor.execute.selector)); + account1.execute(address(0), 0, ""); + } + + function test_Fail_DirectCallPluginUninstalled() external { + _installPlugin(); + + _uninstallPlugin(); + + vm.prank(address(_plugin)); + vm.expectRevert(_buildDirectCallDisallowedError(IStandardExecutor.execute.selector)); + account1.execute(address(0), 0, ""); + } + + function test_Fail_DirectCallPluginCallOtherSelector() external { + _installPlugin(); + + Call[] memory calls = new Call[](0); + + vm.prank(address(_plugin)); + vm.expectRevert(_buildDirectCallDisallowedError(IStandardExecutor.executeBatch.selector)); + account1.executeBatch(calls); + } + + /* -------------------------------------------------------------------------- */ + /* Positives */ + /* -------------------------------------------------------------------------- */ + + function test_Pass_DirectCallFromPluginPrank() external { + _installPlugin(); + + vm.prank(address(_plugin)); + account1.execute(address(0), 0, ""); + + assertTrue(_plugin.preHookRan()); + assertTrue(_plugin.postHookRan()); + } + + function test_Pass_DirectCallFromPluginCallback() external { + _installPlugin(); + + bytes memory encodedCall = abi.encodeCall(DirectCallPlugin.directCall, ()); + + vm.prank(address(entryPoint)); + bytes memory result = account1.execute(address(_plugin), 0, encodedCall); + + assertTrue(_plugin.preHookRan()); + assertTrue(_plugin.postHookRan()); + + // the directCall() function in the _plugin calls back into `execute()` with an encoded call back into the + // _plugin's getData() function. + assertEq(abi.decode(result, (bytes)), abi.encode(_plugin.getData())); + } + + function test_Flow_DirectCallFromPluginSequence() external { + // Install => Succeesfully call => uninstall => fail to call + + _installPlugin(); + + vm.prank(address(_plugin)); + account1.execute(address(0), 0, ""); + + assertTrue(_plugin.preHookRan()); + assertTrue(_plugin.postHookRan()); + + _uninstallPlugin(); + + vm.prank(address(_plugin)); + vm.expectRevert(_buildDirectCallDisallowedError(IStandardExecutor.execute.selector)); + account1.execute(address(0), 0, ""); + } + + /* -------------------------------------------------------------------------- */ + /* Internals */ + /* -------------------------------------------------------------------------- */ + + function _installPlugin() internal { + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = IStandardExecutor.execute.selector; + + ExecutionHook[] memory permissionHooks = new ExecutionHook[](1); + bytes[] memory permissionHookInitDatas = new bytes[](1); + + permissionHooks[0] = ExecutionHook({hookFunction: _pluginEntity, isPreHook: true, isPostHook: true}); + + bytes memory encodedPermissionHooks = abi.encode(permissionHooks, permissionHookInitDatas); + + vm.prank(address(entryPoint)); + + ValidationConfig validationConfig = ValidationConfigLib.pack(_pluginEntity, false, false); + + account1.installValidation(validationConfig, selectors, "", "", encodedPermissionHooks); + } + + function _uninstallPlugin() internal { + vm.prank(address(entryPoint)); + account1.uninstallValidation(_pluginEntity, "", abi.encode(new bytes[](0)), abi.encode(new bytes[](1))); + } + + function _buildDirectCallDisallowedError(bytes4 selector) internal pure returns (bytes memory) { + return abi.encodeWithSelector(UpgradeableModularAccount.ValidationFunctionMissing.selector, selector); + } +} diff --git a/test/account/PermittedCallPermissions.t.sol b/test/account/PermittedCallPermissions.t.sol index 18257955..885015b0 100644 --- a/test/account/PermittedCallPermissions.t.sol +++ b/test/account/PermittedCallPermissions.t.sol @@ -48,9 +48,7 @@ contract PermittedCallPermissionsTest is AccountTestBase { function test_permittedCall_NotAllowed() public { vm.expectRevert( abi.encodeWithSelector( - UpgradeableModularAccount.ExecFromPluginNotPermitted.selector, - address(permittedCallerPlugin), - ResultCreatorPlugin.bar.selector + UpgradeableModularAccount.ValidationFunctionMissing.selector, ResultCreatorPlugin.bar.selector ) ); PermittedCallerPlugin(address(account1)).usePermittedCallNotAllowed(); diff --git a/test/account/SelfCallAuthorization.t.sol b/test/account/SelfCallAuthorization.t.sol index c490eea4..4b6d04c7 100644 --- a/test/account/SelfCallAuthorization.t.sol +++ b/test/account/SelfCallAuthorization.t.sol @@ -40,8 +40,7 @@ contract SelfCallAuthorizationTest is AccountTestBase { 0, "AA23 reverted", abi.encodeWithSelector( - UpgradeableModularAccount.UserOpValidationFunctionMissing.selector, - ComprehensivePlugin.foo.selector + UpgradeableModularAccount.ValidationFunctionMissing.selector, ComprehensivePlugin.foo.selector ) ) ); @@ -56,8 +55,7 @@ contract SelfCallAuthorizationTest is AccountTestBase { 0, "AA23 reverted", abi.encodeWithSelector( - UpgradeableModularAccount.UserOpValidationFunctionMissing.selector, - ComprehensivePlugin.foo.selector + UpgradeableModularAccount.ValidationFunctionMissing.selector, ComprehensivePlugin.foo.selector ) ) ); @@ -68,8 +66,7 @@ contract SelfCallAuthorizationTest is AccountTestBase { _runtimeCall( abi.encodeCall(ComprehensivePlugin.foo, ()), abi.encodeWithSelector( - UpgradeableModularAccount.UserOpValidationFunctionMissing.selector, - ComprehensivePlugin.foo.selector + UpgradeableModularAccount.ValidationFunctionMissing.selector, ComprehensivePlugin.foo.selector ) ); } @@ -99,8 +96,7 @@ contract SelfCallAuthorizationTest is AccountTestBase { 0, "AA23 reverted", abi.encodeWithSelector( - UpgradeableModularAccount.UserOpValidationFunctionMissing.selector, - ComprehensivePlugin.foo.selector + UpgradeableModularAccount.ValidationFunctionMissing.selector, ComprehensivePlugin.foo.selector ) ) ); @@ -136,8 +132,7 @@ contract SelfCallAuthorizationTest is AccountTestBase { 0, "AA23 reverted", abi.encodeWithSelector( - UpgradeableModularAccount.UserOpValidationFunctionMissing.selector, - ComprehensivePlugin.foo.selector + UpgradeableModularAccount.ValidationFunctionMissing.selector, ComprehensivePlugin.foo.selector ) ) ); @@ -159,8 +154,7 @@ contract SelfCallAuthorizationTest is AccountTestBase { _runtimeExecBatchExpFail( calls, abi.encodeWithSelector( - UpgradeableModularAccount.UserOpValidationFunctionMissing.selector, - ComprehensivePlugin.foo.selector + UpgradeableModularAccount.ValidationFunctionMissing.selector, ComprehensivePlugin.foo.selector ) ); } diff --git a/test/mocks/plugins/DirectCallPlugin.sol b/test/mocks/plugins/DirectCallPlugin.sol new file mode 100644 index 00000000..8ab5dd42 --- /dev/null +++ b/test/mocks/plugins/DirectCallPlugin.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {PluginManifest, PluginMetadata} from "../../../src/interfaces/IPlugin.sol"; +import {IStandardExecutor} from "../../../src/interfaces/IStandardExecutor.sol"; +import {IExecutionHook} from "../../../src/interfaces/IExecutionHook.sol"; + +import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; + +contract DirectCallPlugin is BasePlugin, IExecutionHook { + bool public preHookRan = false; + bool public postHookRan = false; + + function onInstall(bytes calldata) external override {} + + function onUninstall(bytes calldata) external override {} + + function pluginManifest() external pure override returns (PluginManifest memory) {} + + function directCall() external returns (bytes memory) { + return IStandardExecutor(msg.sender).execute(address(this), 0, abi.encodeCall(this.getData, ())); + } + + function getData() external pure returns (bytes memory) { + return hex"04546b"; + } + + function pluginMetadata() external pure override returns (PluginMetadata memory) {} + + function preExecutionHook(uint32, address sender, uint256, bytes calldata) + external + override + returns (bytes memory) + { + require(sender == address(this), "mock direct call pre permission hook failed"); + preHookRan = true; + return abi.encode(keccak256(hex"04546b")); + } + + function postExecutionHook(uint32, bytes calldata preExecHookData) external override { + require( + abi.decode(preExecHookData, (bytes32)) == keccak256(hex"04546b"), + "mock direct call post permission hook failed" + ); + postHookRan = true; + } +} From 70ccd3ed73aa20550544cbc787c314547a2f0008 Mon Sep 17 00:00:00 2001 From: Adam Egyed <5456061+adamegyed@users.noreply.github.com> Date: Wed, 17 Jul 2024 13:01:21 -0400 Subject: [PATCH 046/111] style: [v0.8-develop] linter and fmt update (#98) --- foundry.toml | 3 + package.json | 4 +- src/account/AccountLoupe.sol | 7 ++- src/account/AccountStorage.sol | 2 +- src/account/PluginManager2.sol | 7 ++- src/account/PluginManagerInternals.sol | 11 ++-- src/account/UpgradeableModularAccount.sol | 21 ++++--- src/helpers/KnownSelectors.sol | 4 +- src/plugins/BasePlugin.sol | 2 +- src/plugins/ERC20TokenLimitPlugin.sol | 16 +++--- src/plugins/NativeTokenLimitPlugin.sol | 7 ++- src/plugins/TokenReceiverPlugin.sol | 14 ++--- .../validation/SingleSignerValidation.sol | 56 +++++++++---------- .../permissionhooks/AllowlistPlugin.sol | 5 +- test/account/AccountExecHooks.t.sol | 4 +- test/account/AccountReturnData.t.sol | 4 +- test/account/DirectCallsFromPlugin.t.sol | 10 ++-- test/account/MultiValidation.t.sol | 7 ++- test/account/PerHookData.t.sol | 4 +- test/account/PermittedCallPermissions.t.sol | 2 +- test/account/SelfCallAuthorization.t.sol | 5 +- test/account/UpgradeableModularAccount.t.sol | 14 +++-- test/comparison/CompareSimpleAccount.t.sol | 8 +-- test/libraries/AccountStorage.t.sol | 2 +- test/libraries/KnowSelectors.t.sol | 2 +- test/mocks/Counter.t.sol | 2 +- test/mocks/MockPlugin.sol | 16 +++--- test/mocks/SingleSignerFactoryFixture.sol | 6 +- test/mocks/plugins/ComprehensivePlugin.sol | 16 +++--- test/mocks/plugins/DirectCallPlugin.sol | 2 +- .../plugins/MockAccessControlHookPlugin.sol | 5 +- test/mocks/plugins/ReturnDataPluginMocks.sol | 3 +- test/mocks/plugins/ValidationPluginMocks.sol | 4 +- test/plugin/ERC20TokenLimitPlugin.t.sol | 32 ++++++----- test/plugin/NativeTokenLimitPlugin.t.sol | 45 ++++++++------- test/plugin/TokenReceiverPlugin.t.sol | 8 ++- test/samples/AllowlistPlugin.t.sol | 7 ++- test/utils/AccountTestBase.sol | 10 ++-- test/utils/CustomValidationTestBase.sol | 2 +- test/utils/OptimizedTest.sol | 3 +- test/validation/SingleSignerValidation.t.sol | 2 +- 41 files changed, 208 insertions(+), 176 deletions(-) diff --git a/foundry.toml b/foundry.toml index 67d9eb7c..2aa76ba2 100644 --- a/foundry.toml +++ b/foundry.toml @@ -47,6 +47,9 @@ depth = 32 [fmt] line_length = 115 wrap_comments = true +sort_imports = true +number_underscore = "thousands" +int_types = "long" [rpc_endpoints] mainnet = "${RPC_URL_MAINNET}" diff --git a/package.json b/package.json index a9cc7c23..1c1540e7 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ }, "scripts": { "lint": "pnpm lint:src && pnpm lint:test", - "lint:src": "solhint -c .solhint-src.json './src/**/*.sol'", - "lint:test": "solhint -c .solhint-test.json './test/**/*.sol'" + "lint:src": "solhint --max-warnings 0 -c .solhint-src.json './src/**/*.sol'", + "lint:test": "solhint --max-warnings 0 -c .solhint-test.json './test/**/*.sol'" } } diff --git a/src/account/AccountLoupe.sol b/src/account/AccountLoupe.sol index cca17149..ef679e4e 100644 --- a/src/account/AccountLoupe.sol +++ b/src/account/AccountLoupe.sol @@ -2,11 +2,12 @@ pragma solidity ^0.8.25; import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; -import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; + import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; +import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import {IAccountLoupe, ExecutionHook} from "../interfaces/IAccountLoupe.sol"; -import {PluginEntity, IPluginManager} from "../interfaces/IPluginManager.sol"; +import {ExecutionHook, IAccountLoupe} from "../interfaces/IAccountLoupe.sol"; +import {IPluginManager, PluginEntity} from "../interfaces/IPluginManager.sol"; import {IStandardExecutor} from "../interfaces/IStandardExecutor.sol"; import {getAccountStorage, toExecutionHook, toSelector} from "./AccountStorage.sol"; diff --git a/src/account/AccountStorage.sol b/src/account/AccountStorage.sol index aa56f75d..ea2f4551 100644 --- a/src/account/AccountStorage.sol +++ b/src/account/AccountStorage.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.25; -import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; +import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; import {PluginEntity} from "../interfaces/IPluginManager.sol"; diff --git a/src/account/PluginManager2.sol b/src/account/PluginManager2.sol index 2ff6a9d1..845b80b8 100644 --- a/src/account/PluginManager2.sol +++ b/src/account/PluginManager2.sol @@ -3,12 +3,13 @@ pragma solidity ^0.8.25; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import {IPlugin} from "../interfaces/IPlugin.sol"; -import {PluginEntity, ValidationConfig} from "../interfaces/IPluginManager.sol"; import {PluginEntityLib} from "../helpers/PluginEntityLib.sol"; import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; -import {ValidationData, getAccountStorage, toSetValue} from "./AccountStorage.sol"; + import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; +import {IPlugin} from "../interfaces/IPlugin.sol"; +import {PluginEntity, ValidationConfig} from "../interfaces/IPluginManager.sol"; +import {ValidationData, getAccountStorage, toSetValue} from "./AccountStorage.sol"; // Temporary additional functions for a user-controlled install flow for validation functions. abstract contract PluginManager2 { diff --git a/src/account/PluginManagerInternals.sol b/src/account/PluginManagerInternals.sol index ccc4c68b..e473730e 100644 --- a/src/account/PluginManagerInternals.sol +++ b/src/account/PluginManagerInternals.sol @@ -2,15 +2,16 @@ pragma solidity ^0.8.25; import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; -import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; + import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; +import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import {KnownSelectors} from "../helpers/KnownSelectors.sol"; import {PluginEntityLib} from "../helpers/PluginEntityLib.sol"; -import {IPlugin, ManifestExecutionHook, ManifestValidation, PluginManifest} from "../interfaces/IPlugin.sol"; import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; -import {PluginEntity, IPluginManager} from "../interfaces/IPluginManager.sol"; -import {KnownSelectors} from "../helpers/KnownSelectors.sol"; -import {AccountStorage, getAccountStorage, SelectorData, toSetValue} from "./AccountStorage.sol"; +import {IPlugin, ManifestExecutionHook, ManifestValidation, PluginManifest} from "../interfaces/IPlugin.sol"; +import {IPluginManager, PluginEntity} from "../interfaces/IPluginManager.sol"; +import {AccountStorage, SelectorData, getAccountStorage, toSetValue} from "./AccountStorage.sol"; abstract contract PluginManagerInternals is IPluginManager { using EnumerableSet for EnumerableSet.Bytes32Set; diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 6c577ed2..4f64cdd3 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -2,30 +2,35 @@ pragma solidity ^0.8.25; import {BaseAccount} from "@eth-infinitism/account-abstraction/core/BaseAccount.sol"; + +import {IAccountExecute} from "@eth-infinitism/account-abstraction/interfaces/IAccountExecute.sol"; import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; -import {IAccountExecute} from "@eth-infinitism/account-abstraction/interfaces/IAccountExecute.sol"; + +import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {PluginEntityLib} from "../helpers/PluginEntityLib.sol"; -import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; + import {SparseCalldataSegmentLib} from "../helpers/SparseCalldataSegmentLib.sol"; +import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; import {_coalescePreValidation, _coalesceValidation} from "../helpers/ValidationResHelpers.sol"; + +import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; import {IPlugin, PluginManifest} from "../interfaces/IPlugin.sol"; +import {IPluginManager, PluginEntity, ValidationConfig} from "../interfaces/IPluginManager.sol"; +import {Call, IStandardExecutor} from "../interfaces/IStandardExecutor.sol"; import {IValidation} from "../interfaces/IValidation.sol"; import {IValidationHook} from "../interfaces/IValidationHook.sol"; -import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; -import {PluginEntity, IPluginManager, ValidationConfig} from "../interfaces/IPluginManager.sol"; -import {IStandardExecutor, Call} from "../interfaces/IStandardExecutor.sol"; import {AccountExecutor} from "./AccountExecutor.sol"; import {AccountLoupe} from "./AccountLoupe.sol"; -import {AccountStorage, getAccountStorage, toSetValue, toExecutionHook} from "./AccountStorage.sol"; +import {AccountStorage, getAccountStorage, toExecutionHook, toSetValue} from "./AccountStorage.sol"; import {AccountStorageInitializable} from "./AccountStorageInitializable.sol"; -import {PluginManagerInternals} from "./PluginManagerInternals.sol"; + import {PluginManager2} from "./PluginManager2.sol"; +import {PluginManagerInternals} from "./PluginManagerInternals.sol"; contract UpgradeableModularAccount is AccountExecutor, diff --git a/src/helpers/KnownSelectors.sol b/src/helpers/KnownSelectors.sol index 1d02d2a3..ae91f3b7 100644 --- a/src/helpers/KnownSelectors.sol +++ b/src/helpers/KnownSelectors.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.25; -import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; import {IAccount} from "@eth-infinitism/account-abstraction/interfaces/IAccount.sol"; import {IAggregator} from "@eth-infinitism/account-abstraction/interfaces/IAggregator.sol"; import {IPaymaster} from "@eth-infinitism/account-abstraction/interfaces/IPaymaster.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; +import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import {IAccountLoupe} from "../interfaces/IAccountLoupe.sol"; import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; diff --git a/src/plugins/BasePlugin.sol b/src/plugins/BasePlugin.sol index f5c8523b..40dcd47b 100644 --- a/src/plugins/BasePlugin.sol +++ b/src/plugins/BasePlugin.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.25; -import {ERC165, IERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; import {IAccountExecute} from "@eth-infinitism/account-abstraction/interfaces/IAccountExecute.sol"; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; +import {ERC165, IERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; import {IPlugin} from "../interfaces/IPlugin.sol"; diff --git a/src/plugins/ERC20TokenLimitPlugin.sol b/src/plugins/ERC20TokenLimitPlugin.sol index 4e9c17ac..28ba4e04 100644 --- a/src/plugins/ERC20TokenLimitPlugin.sol +++ b/src/plugins/ERC20TokenLimitPlugin.sol @@ -1,20 +1,22 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.25; -import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {UserOperationLib} from "@eth-infinitism/account-abstraction/core/UserOperationLib.sol"; -import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; + import { - SetValue, AssociatedLinkedListSet, - AssociatedLinkedListSetLib + AssociatedLinkedListSetLib, + SetValue } from "@modular-account-libs/libraries/AssociatedLinkedListSetLib.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; import {PluginManifest, PluginMetadata} from "../interfaces/IPlugin.sol"; -import {IStandardExecutor, Call} from "../interfaces/IStandardExecutor.sol"; import {IPlugin} from "../interfaces/IPlugin.sol"; -import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; +import {Call, IStandardExecutor} from "../interfaces/IStandardExecutor.sol"; + import {BasePlugin, IERC165} from "./BasePlugin.sol"; /// @title ERC20 Token Limit Plugin diff --git a/src/plugins/NativeTokenLimitPlugin.sol b/src/plugins/NativeTokenLimitPlugin.sol index de0d9f3d..37f4399e 100644 --- a/src/plugins/NativeTokenLimitPlugin.sol +++ b/src/plugins/NativeTokenLimitPlugin.sol @@ -1,14 +1,15 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.25; -import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {UserOperationLib} from "@eth-infinitism/account-abstraction/core/UserOperationLib.sol"; +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; import {PluginManifest, PluginMetadata} from "../interfaces/IPlugin.sol"; -import {IStandardExecutor, Call} from "../interfaces/IStandardExecutor.sol"; import {IPlugin} from "../interfaces/IPlugin.sol"; -import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; +import {Call, IStandardExecutor} from "../interfaces/IStandardExecutor.sol"; + import {IValidationHook} from "../interfaces/IValidationHook.sol"; import {BasePlugin, IERC165} from "./BasePlugin.sol"; diff --git a/src/plugins/TokenReceiverPlugin.sol b/src/plugins/TokenReceiverPlugin.sol index d326a9d4..95fbcfe0 100644 --- a/src/plugins/TokenReceiverPlugin.sol +++ b/src/plugins/TokenReceiverPlugin.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.25; -import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; import {IERC1155Receiver} from "@openzeppelin/contracts/interfaces/IERC1155Receiver.sol"; +import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; import {IPlugin, ManifestExecutionFunction, PluginManifest, PluginMetadata} from "../interfaces/IPlugin.sol"; import {BasePlugin} from "./BasePlugin.sol"; @@ -12,9 +12,9 @@ import {BasePlugin} from "./BasePlugin.sol"; /// @notice This plugin allows modular accounts to receive various types of tokens by implementing /// required token receiver interfaces. contract TokenReceiverPlugin is BasePlugin, IERC721Receiver, IERC1155Receiver { - string public constant NAME = "Token Receiver Plugin"; - string public constant VERSION = "1.0.0"; - string public constant AUTHOR = "ERC-6900 Authors"; + string internal constant _NAME = "Token Receiver Plugin"; + string internal constant _VERSION = "1.0.0"; + string internal constant _AUTHOR = "ERC-6900 Authors"; // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ // ┃ Execution functions ┃ @@ -85,9 +85,9 @@ contract TokenReceiverPlugin is BasePlugin, IERC721Receiver, IERC1155Receiver { /// @inheritdoc IPlugin function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { PluginMetadata memory metadata; - metadata.name = NAME; - metadata.version = VERSION; - metadata.author = AUTHOR; + metadata.name = _NAME; + metadata.version = _VERSION; + metadata.author = _AUTHOR; return metadata; } } diff --git a/src/plugins/validation/SingleSignerValidation.sol b/src/plugins/validation/SingleSignerValidation.sol index 5ae4517d..6699cb58 100644 --- a/src/plugins/validation/SingleSignerValidation.sol +++ b/src/plugins/validation/SingleSignerValidation.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.25; +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; -import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; import {IPlugin, PluginManifest, PluginMetadata} from "../../interfaces/IPlugin.sol"; @@ -28,9 +28,9 @@ contract SingleSignerValidation is ISingleSignerValidation, BasePlugin { using ECDSA for bytes32; using MessageHashUtils for bytes32; - string public constant NAME = "SingleSigner Validation"; - string public constant VERSION = "1.0.0"; - string public constant AUTHOR = "ERC-6900 Authors"; + string internal constant _NAME = "SingleSigner Validation"; + string internal constant _VERSION = "1.0.0"; + string internal constant _AUTHOR = "ERC-6900 Authors"; uint256 internal constant _SIG_VALIDATION_PASSED = 0; uint256 internal constant _SIG_VALIDATION_FAILED = 1; @@ -41,35 +41,11 @@ contract SingleSignerValidation is ISingleSignerValidation, BasePlugin { mapping(uint32 entityId => mapping(address account => address)) public signer; - /// @inheritdoc ISingleSignerValidation - function signerOf(uint32 entityId, address account) external view returns (address) { - return signer[entityId][account]; - } - /// @inheritdoc ISingleSignerValidation function transferSigner(uint32 entityId, address newSigner) external { _transferSigner(entityId, newSigner); } - // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Plugin interface functions ┃ - // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - - /// @inheritdoc IPlugin - function pluginManifest() external pure override returns (PluginManifest memory) { - PluginManifest memory manifest; - return manifest; - } - - /// @inheritdoc IPlugin - function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { - PluginMetadata memory metadata; - metadata.name = NAME; - metadata.version = VERSION; - metadata.author = AUTHOR; - return metadata; - } - /// @inheritdoc IPlugin function onInstall(bytes calldata data) external override { (uint32 entityId, address newSigner) = abi.decode(data, (uint32, address)); @@ -83,6 +59,11 @@ contract SingleSignerValidation is ISingleSignerValidation, BasePlugin { _transferSigner(abi.decode(data, (uint32)), address(0)); } + /// @inheritdoc ISingleSignerValidation + function signerOf(uint32 entityId, address account) external view returns (address) { + return signer[entityId][account]; + } + /// @inheritdoc IValidation function validateUserOp(uint32 entityId, PackedUserOperation calldata userOp, bytes32 userOpHash) external @@ -133,6 +114,25 @@ contract SingleSignerValidation is ISingleSignerValidation, BasePlugin { return _1271_INVALID; } + // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ + // ┃ Plugin interface functions ┃ + // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + + /// @inheritdoc IPlugin + function pluginManifest() external pure override returns (PluginManifest memory) { + PluginManifest memory manifest; + return manifest; + } + + /// @inheritdoc IPlugin + function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { + PluginMetadata memory metadata; + metadata.name = _NAME; + metadata.version = _VERSION; + metadata.author = _AUTHOR; + return metadata; + } + // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ // ┃ Internal / Private functions ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/src/samples/permissionhooks/AllowlistPlugin.sol b/src/samples/permissionhooks/AllowlistPlugin.sol index 2d86ca18..bb468ae8 100644 --- a/src/samples/permissionhooks/AllowlistPlugin.sol +++ b/src/samples/permissionhooks/AllowlistPlugin.sol @@ -3,9 +3,10 @@ pragma solidity ^0.8.25; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; -import {PluginMetadata, PluginManifest} from "../../interfaces/IPlugin.sol"; +import {PluginManifest, PluginMetadata} from "../../interfaces/IPlugin.sol"; + +import {Call, IStandardExecutor} from "../../interfaces/IStandardExecutor.sol"; import {IValidationHook} from "../../interfaces/IValidationHook.sol"; -import {IStandardExecutor, Call} from "../../interfaces/IStandardExecutor.sol"; import {BasePlugin} from "../../plugins/BasePlugin.sol"; contract AllowlistPlugin is IValidationHook, BasePlugin { diff --git a/test/account/AccountExecHooks.t.sol b/test/account/AccountExecHooks.t.sol index 98d16dcf..ba3a2098 100644 --- a/test/account/AccountExecHooks.t.sol +++ b/test/account/AccountExecHooks.t.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; +import {IExecutionHook} from "../../src/interfaces/IExecutionHook.sol"; import { IPlugin, - ManifestExecutionHook, ManifestExecutionFunction, + ManifestExecutionHook, PluginManifest } from "../../src/interfaces/IPlugin.sol"; -import {IExecutionHook} from "../../src/interfaces/IExecutionHook.sol"; import {MockPlugin} from "../mocks/MockPlugin.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; diff --git a/test/account/AccountReturnData.t.sol b/test/account/AccountReturnData.t.sol index bb081325..fa8bdf86 100644 --- a/test/account/AccountReturnData.t.sol +++ b/test/account/AccountReturnData.t.sol @@ -6,8 +6,8 @@ import {Call} from "../../src/interfaces/IStandardExecutor.sol"; import { RegularResultContract, - ResultCreatorPlugin, - ResultConsumerPlugin + ResultConsumerPlugin, + ResultCreatorPlugin } from "../mocks/plugins/ReturnDataPluginMocks.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; import {TEST_DEFAULT_VALIDATION_ENTITY_ID} from "../utils/TestConstants.sol"; diff --git a/test/account/DirectCallsFromPlugin.t.sol b/test/account/DirectCallsFromPlugin.t.sol index 1c0fcca8..9e56739a 100644 --- a/test/account/DirectCallsFromPlugin.t.sol +++ b/test/account/DirectCallsFromPlugin.t.sol @@ -1,11 +1,11 @@ pragma solidity ^0.8.19; -import {DirectCallPlugin} from "../mocks/plugins/DirectCallPlugin.sol"; -import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; -import {IStandardExecutor, Call} from "../../src/interfaces/IStandardExecutor.sol"; -import {PluginEntityLib, PluginEntity} from "../../src/helpers/PluginEntityLib.sol"; -import {ValidationConfig, ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; +import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; +import {ValidationConfig, ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; +import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; +import {Call, IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; +import {DirectCallPlugin} from "../mocks/plugins/DirectCallPlugin.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; diff --git a/test/account/MultiValidation.t.sol b/test/account/MultiValidation.t.sol index 641fcefb..afb84f1a 100644 --- a/test/account/MultiValidation.t.sol +++ b/test/account/MultiValidation.t.sol @@ -1,17 +1,18 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.21; -import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {PluginEntity} from "../../src/interfaces/IPluginManager.sol"; -import {IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; + import {PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; +import {PluginEntity} from "../../src/interfaces/IPluginManager.sol"; +import {IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; diff --git a/test/account/PerHookData.t.sol b/test/account/PerHookData.t.sol index bcb53171..c4405d38 100644 --- a/test/account/PerHookData.t.sol +++ b/test/account/PerHookData.t.sol @@ -1,15 +1,15 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.25; -import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; -import {MockAccessControlHookPlugin} from "../mocks/plugins/MockAccessControlHookPlugin.sol"; import {Counter} from "../mocks/Counter.sol"; +import {MockAccessControlHookPlugin} from "../mocks/plugins/MockAccessControlHookPlugin.sol"; import {CustomValidationTestBase} from "../utils/CustomValidationTestBase.sol"; contract PerHookDataTest is CustomValidationTestBase { diff --git a/test/account/PermittedCallPermissions.t.sol b/test/account/PermittedCallPermissions.t.sol index 885015b0..607c50b4 100644 --- a/test/account/PermittedCallPermissions.t.sol +++ b/test/account/PermittedCallPermissions.t.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.19; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {ResultCreatorPlugin} from "../mocks/plugins/ReturnDataPluginMocks.sol"; import {PermittedCallerPlugin} from "../mocks/plugins/PermittedCallMocks.sol"; +import {ResultCreatorPlugin} from "../mocks/plugins/ReturnDataPluginMocks.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; contract PermittedCallPermissionsTest is AccountTestBase { diff --git a/test/account/SelfCallAuthorization.t.sol b/test/account/SelfCallAuthorization.t.sol index 4b6d04c7..743a0241 100644 --- a/test/account/SelfCallAuthorization.t.sol +++ b/test/account/SelfCallAuthorization.t.sol @@ -6,12 +6,13 @@ import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntry import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {IStandardExecutor, Call} from "../../src/interfaces/IStandardExecutor.sol"; + import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; +import {Call, IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; -import {AccountTestBase} from "../utils/AccountTestBase.sol"; import {ComprehensivePlugin} from "../mocks/plugins/ComprehensivePlugin.sol"; +import {AccountTestBase} from "../utils/AccountTestBase.sol"; contract SelfCallAuthorizationTest is AccountTestBase { ComprehensivePlugin public comprehensivePlugin; diff --git a/test/account/UpgradeableModularAccount.t.sol b/test/account/UpgradeableModularAccount.t.sol index 54af25c6..8b77332f 100644 --- a/test/account/UpgradeableModularAccount.t.sol +++ b/test/account/UpgradeableModularAccount.t.sol @@ -3,23 +3,27 @@ pragma solidity ^0.8.19; import {console} from "forge-std/Test.sol"; -import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; -import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; + import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {PluginManagerInternals} from "../../src/account/PluginManagerInternals.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {PluginManifest} from "../../src/interfaces/IPlugin.sol"; + import {IAccountLoupe} from "../../src/interfaces/IAccountLoupe.sol"; +import {PluginManifest} from "../../src/interfaces/IPlugin.sol"; import {IPluginManager} from "../../src/interfaces/IPluginManager.sol"; import {Call} from "../../src/interfaces/IStandardExecutor.sol"; -import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; + import {TokenReceiverPlugin} from "../../src/plugins/TokenReceiverPlugin.sol"; +import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; import {Counter} from "../mocks/Counter.sol"; -import {ComprehensivePlugin} from "../mocks/plugins/ComprehensivePlugin.sol"; + import {MockPlugin} from "../mocks/MockPlugin.sol"; +import {ComprehensivePlugin} from "../mocks/plugins/ComprehensivePlugin.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; import {TEST_DEFAULT_VALIDATION_ENTITY_ID} from "../utils/TestConstants.sol"; diff --git a/test/comparison/CompareSimpleAccount.t.sol b/test/comparison/CompareSimpleAccount.t.sol index 93b75ebe..a7f59b69 100644 --- a/test/comparison/CompareSimpleAccount.t.sol +++ b/test/comparison/CompareSimpleAccount.t.sol @@ -3,10 +3,10 @@ pragma solidity ^0.8.19; import {Test} from "forge-std/Test.sol"; -import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.sol"; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {SimpleAccount} from "@eth-infinitism/account-abstraction/samples/SimpleAccount.sol"; import {SimpleAccountFactory} from "@eth-infinitism/account-abstraction/samples/SimpleAccountFactory.sol"; @@ -34,8 +34,8 @@ contract CompareSimpleAccountTest is Test { Counter public counter; - uint256 public constant CALL_GAS_LIMIT = 500000; - uint256 public constant VERIFICATION_GAS_LIMIT = 500000; + uint256 public constant CALL_GAS_LIMIT = 500_000; + uint256 public constant VERIFICATION_GAS_LIMIT = 500_000; // helper function to compress 2 gas values into a single bytes32 function _encodeGas(uint256 g1, uint256 g2) internal pure returns (bytes32) { diff --git a/test/libraries/AccountStorage.t.sol b/test/libraries/AccountStorage.t.sol index 25594cd2..71c35b6e 100644 --- a/test/libraries/AccountStorage.t.sol +++ b/test/libraries/AccountStorage.t.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {Test} from "forge-std/Test.sol"; import {_ACCOUNT_STORAGE_SLOT} from "../../src/account/AccountStorage.sol"; import {AccountStorageInitializable} from "../../src/account/AccountStorageInitializable.sol"; import {MockDiamondStorageContract} from "../mocks/MockDiamondStorageContract.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {Test} from "forge-std/Test.sol"; // Test implementation of AccountStorageInitializable which is contained in UpgradeableModularAccount contract AccountStorageTest is Test { diff --git a/test/libraries/KnowSelectors.t.sol b/test/libraries/KnowSelectors.t.sol index 893b831b..7ce0910d 100644 --- a/test/libraries/KnowSelectors.t.sol +++ b/test/libraries/KnowSelectors.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.25; -import {Test} from "forge-std/Test.sol"; import {IAccount} from "@eth-infinitism/account-abstraction/interfaces/IAccount.sol"; import {IPaymaster} from "@eth-infinitism/account-abstraction/interfaces/IPaymaster.sol"; +import {Test} from "forge-std/Test.sol"; import {KnownSelectors} from "../../src/helpers/KnownSelectors.sol"; import {IPlugin} from "../../src/interfaces/IPlugin.sol"; diff --git a/test/mocks/Counter.t.sol b/test/mocks/Counter.t.sol index 8b0f5ccf..4749a846 100644 --- a/test/mocks/Counter.t.sol +++ b/test/mocks/Counter.t.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {Test} from "forge-std/Test.sol"; import {Counter} from "./Counter.sol"; +import {Test} from "forge-std/Test.sol"; contract CounterTest is Test { Counter public counter; diff --git a/test/mocks/MockPlugin.sol b/test/mocks/MockPlugin.sol index 3a2bf984..ef79c018 100644 --- a/test/mocks/MockPlugin.sol +++ b/test/mocks/MockPlugin.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.19; import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; -import {PluginManifest, IPlugin, PluginMetadata} from "../../src/interfaces/IPlugin.sol"; -import {IValidation} from "../../src/interfaces/IValidation.sol"; import {IExecutionHook} from "../../src/interfaces/IExecutionHook.sol"; +import {IPlugin, PluginManifest, PluginMetadata} from "../../src/interfaces/IPlugin.sol"; +import {IValidation} from "../../src/interfaces/IValidation.sol"; contract MockPlugin is ERC165 { // It's super inefficient to hold the entire abi-encoded manifest in storage, but this is fine since it's @@ -17,9 +17,9 @@ contract MockPlugin is ERC165 { // struct ManifestAssociatedFunction memory[] memory to storage not yet supported. bytes internal _manifest; - string public constant NAME = "Mock Plugin Modifiable"; - string public constant VERSION = "1.0.0"; - string public constant AUTHOR = "ERC-6900 Authors"; + string internal constant _NAME = "Mock Plugin Modifiable"; + string internal constant _VERSION = "1.0.0"; + string internal constant _AUTHOR = "ERC-6900 Authors"; event ReceivedCall(bytes msgData, uint256 msgValue); @@ -52,9 +52,9 @@ contract MockPlugin is ERC165 { function pluginMetadata() external pure returns (PluginMetadata memory) { PluginMetadata memory metadata; - metadata.name = NAME; - metadata.version = VERSION; - metadata.author = AUTHOR; + metadata.name = _NAME; + metadata.version = _VERSION; + metadata.author = _AUTHOR; return metadata; } diff --git a/test/mocks/SingleSignerFactoryFixture.sol b/test/mocks/SingleSignerFactoryFixture.sol index b3da73ec..b751d440 100644 --- a/test/mocks/SingleSignerFactoryFixture.sol +++ b/test/mocks/SingleSignerFactoryFixture.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; -import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; -import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; +import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; import {OptimizedTest} from "../utils/OptimizedTest.sol"; diff --git a/test/mocks/plugins/ComprehensivePlugin.sol b/test/mocks/plugins/ComprehensivePlugin.sol index 306e96a4..e4233cf2 100644 --- a/test/mocks/plugins/ComprehensivePlugin.sol +++ b/test/mocks/plugins/ComprehensivePlugin.sol @@ -3,9 +3,10 @@ pragma solidity ^0.8.19; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; +import {IExecutionHook} from "../../../src/interfaces/IExecutionHook.sol"; import { - ManifestExecutionHook, ManifestExecutionFunction, + ManifestExecutionHook, ManifestValidation, PluginManifest, PluginMetadata @@ -13,7 +14,6 @@ import { import {PluginManifest} from "../../../src/interfaces/IPlugin.sol"; import {IValidation} from "../../../src/interfaces/IValidation.sol"; import {IValidationHook} from "../../../src/interfaces/IValidationHook.sol"; -import {IExecutionHook} from "../../../src/interfaces/IExecutionHook.sol"; import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; @@ -28,9 +28,9 @@ contract ComprehensivePlugin is IValidation, IValidationHook, IExecutionHook, Ba SIG_VALIDATION } - string public constant NAME = "Comprehensive Plugin"; - string public constant VERSION = "1.0.0"; - string public constant AUTHOR = "ERC-6900 Authors"; + string internal constant _NAME = "Comprehensive Plugin"; + string internal constant _VERSION = "1.0.0"; + string internal constant _AUTHOR = "ERC-6900 Authors"; // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ // ┃ Execution functions ┃ @@ -176,9 +176,9 @@ contract ComprehensivePlugin is IValidation, IValidationHook, IExecutionHook, Ba function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { PluginMetadata memory metadata; - metadata.name = NAME; - metadata.version = VERSION; - metadata.author = AUTHOR; + metadata.name = _NAME; + metadata.version = _VERSION; + metadata.author = _AUTHOR; return metadata; } } diff --git a/test/mocks/plugins/DirectCallPlugin.sol b/test/mocks/plugins/DirectCallPlugin.sol index 8ab5dd42..7ccda4f0 100644 --- a/test/mocks/plugins/DirectCallPlugin.sol +++ b/test/mocks/plugins/DirectCallPlugin.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; +import {IExecutionHook} from "../../../src/interfaces/IExecutionHook.sol"; import {PluginManifest, PluginMetadata} from "../../../src/interfaces/IPlugin.sol"; import {IStandardExecutor} from "../../../src/interfaces/IStandardExecutor.sol"; -import {IExecutionHook} from "../../../src/interfaces/IExecutionHook.sol"; import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; diff --git a/test/mocks/plugins/MockAccessControlHookPlugin.sol b/test/mocks/plugins/MockAccessControlHookPlugin.sol index 6bd593f2..8fa49f8e 100644 --- a/test/mocks/plugins/MockAccessControlHookPlugin.sol +++ b/test/mocks/plugins/MockAccessControlHookPlugin.sol @@ -3,9 +3,10 @@ pragma solidity ^0.8.25; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; -import {PluginMetadata, PluginManifest} from "../../../src/interfaces/IPlugin.sol"; -import {IValidationHook} from "../../../src/interfaces/IValidationHook.sol"; +import {PluginManifest, PluginMetadata} from "../../../src/interfaces/IPlugin.sol"; + import {IStandardExecutor} from "../../../src/interfaces/IStandardExecutor.sol"; +import {IValidationHook} from "../../../src/interfaces/IValidationHook.sol"; import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; // A pre validaiton hook plugin that uses per-hook data. diff --git a/test/mocks/plugins/ReturnDataPluginMocks.sol b/test/mocks/plugins/ReturnDataPluginMocks.sol index 3adbb6e4..96a77ffe 100644 --- a/test/mocks/plugins/ReturnDataPluginMocks.sol +++ b/test/mocks/plugins/ReturnDataPluginMocks.sol @@ -9,8 +9,9 @@ import { PluginManifest, PluginMetadata } from "../../../src/interfaces/IPlugin.sol"; -import {IValidation} from "../../../src/interfaces/IValidation.sol"; + import {IStandardExecutor} from "../../../src/interfaces/IStandardExecutor.sol"; +import {IValidation} from "../../../src/interfaces/IValidation.sol"; import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; diff --git a/test/mocks/plugins/ValidationPluginMocks.sol b/test/mocks/plugins/ValidationPluginMocks.sol index 8d9b6ce2..3cfe1d09 100644 --- a/test/mocks/plugins/ValidationPluginMocks.sol +++ b/test/mocks/plugins/ValidationPluginMocks.sol @@ -6,8 +6,8 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import { ManifestExecutionFunction, ManifestValidation, - PluginMetadata, - PluginManifest + PluginManifest, + PluginMetadata } from "../../../src/interfaces/IPlugin.sol"; import {IValidation} from "../../../src/interfaces/IValidation.sol"; import {IValidationHook} from "../../../src/interfaces/IValidationHook.sol"; diff --git a/test/plugin/ERC20TokenLimitPlugin.t.sol b/test/plugin/ERC20TokenLimitPlugin.t.sol index 9693f98f..cf7b422a 100644 --- a/test/plugin/ERC20TokenLimitPlugin.t.sol +++ b/test/plugin/ERC20TokenLimitPlugin.t.sol @@ -1,19 +1,21 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {MockERC20} from "../mocks/MockERC20.sol"; +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {PluginEntity} from "../../src/helpers/PluginEntityLib.sol"; -import {ERC20TokenLimitPlugin} from "../../src/plugins/ERC20TokenLimitPlugin.sol"; -import {MockPlugin} from "../mocks/MockPlugin.sol"; -import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; + import {PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; -import {IStandardExecutor, Call} from "../../src/interfaces/IStandardExecutor.sol"; -import {PluginManifest} from "../../src/interfaces/IPlugin.sol"; + import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; +import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; +import {PluginManifest} from "../../src/interfaces/IPlugin.sol"; +import {Call, IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; +import {ERC20TokenLimitPlugin} from "../../src/plugins/ERC20TokenLimitPlugin.sol"; +import {MockPlugin} from "../mocks/MockPlugin.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; @@ -71,8 +73,8 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { nonce: 0, initCode: "", callData: abi.encodePacked(UpgradeableModularAccount.executeUserOp.selector, callData), - accountGasLimits: bytes32(bytes16(uint128(200000))) | bytes32(uint256(200000)), - preVerificationGas: 200000, + accountGasLimits: bytes32(bytes16(uint128(200_000))) | bytes32(uint256(200_000)), + preVerificationGas: 200_000, gasFees: bytes32(uint256(uint128(0))), paymasterAndData: "", signature: _encodeSignature(PluginEntityLib.pack(address(validationPlugin), 0), 1, "") @@ -102,13 +104,13 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { calls[2] = Call({ target: address(erc20), value: 0, - data: abi.encodeCall(IERC20.transfer, (recipient, 5 ether + 100000)) + data: abi.encodeCall(IERC20.transfer, (recipient, 5 ether + 100_000)) }); vm.startPrank(address(entryPoint)); assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether); acct.executeUserOp(_getPackedUO(abi.encodeCall(IStandardExecutor.executeBatch, (calls))), bytes32(0)); - assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether - 6 ether - 100001); + assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether - 6 ether - 100_001); } function test_userOp_executeBatch_approveAndTransferLimit() public { @@ -120,13 +122,13 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { calls[2] = Call({ target: address(erc20), value: 0, - data: abi.encodeCall(IERC20.approve, (recipient, 5 ether + 100000)) + data: abi.encodeCall(IERC20.approve, (recipient, 5 ether + 100_000)) }); vm.startPrank(address(entryPoint)); assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether); acct.executeUserOp(_getPackedUO(abi.encodeCall(IStandardExecutor.executeBatch, (calls))), bytes32(0)); - assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether - 6 ether - 100001); + assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether - 6 ether - 100_001); } function test_userOp_executeBatch_approveAndTransferLimit_fail() public { @@ -138,7 +140,7 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { calls[2] = Call({ target: address(erc20), value: 0, - data: abi.encodeCall(IERC20.approve, (recipient, 9 ether + 100000)) + data: abi.encodeCall(IERC20.approve, (recipient, 9 ether + 100_000)) }); vm.startPrank(address(entryPoint)); @@ -168,7 +170,7 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { calls[2] = Call({ target: address(erc20), value: 0, - data: abi.encodeCall(IERC20.approve, (recipient, 5 ether + 100000)) + data: abi.encodeCall(IERC20.approve, (recipient, 5 ether + 100_000)) }); assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether); @@ -176,6 +178,6 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { abi.encodeCall(IStandardExecutor.executeBatch, (calls)), _encodeSignature(PluginEntityLib.pack(address(validationPlugin), 0), 1, "") ); - assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether - 6 ether - 100001); + assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether - 6 ether - 100_001); } } diff --git a/test/plugin/NativeTokenLimitPlugin.t.sol b/test/plugin/NativeTokenLimitPlugin.t.sol index 33635216..0ae9ae40 100644 --- a/test/plugin/NativeTokenLimitPlugin.t.sol +++ b/test/plugin/NativeTokenLimitPlugin.t.sol @@ -5,13 +5,15 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {PluginEntity} from "../../src/helpers/PluginEntityLib.sol"; -import {NativeTokenLimitPlugin} from "../../src/plugins/NativeTokenLimitPlugin.sol"; -import {MockPlugin} from "../mocks/MockPlugin.sol"; -import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; + import {PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; -import {IStandardExecutor, Call} from "../../src/interfaces/IStandardExecutor.sol"; -import {PluginManifest} from "../../src/interfaces/IPlugin.sol"; + import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; +import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; +import {PluginManifest} from "../../src/interfaces/IPlugin.sol"; +import {Call, IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; +import {NativeTokenLimitPlugin} from "../../src/plugins/NativeTokenLimitPlugin.sol"; +import {MockPlugin} from "../mocks/MockPlugin.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; @@ -92,16 +94,16 @@ contract NativeTokenLimitPluginTest is AccountTestBase { // uses 10e - 200000 of gas assertEq(plugin.limits(0, address(acct)), 10 ether); uint256 result = acct.validateUserOp( - _getPackedUO(100000, 100000, 10 ether - 400000, 1, _getExecuteWithValue(0)), bytes32(0), 0 + _getPackedUO(100_000, 100_000, 10 ether - 400_000, 1, _getExecuteWithValue(0)), bytes32(0), 0 ); - assertEq(plugin.limits(0, address(acct)), 200000); + assertEq(plugin.limits(0, address(acct)), 200_000); uint256 expected = uint256(type(uint48).max) << 160; assertEq(result, expected); // uses 200k + 1 wei of gas vm.expectRevert(NativeTokenLimitPlugin.ExceededNativeTokenLimit.selector); - result = acct.validateUserOp(_getPackedUO(100000, 100000, 1, 1, _getExecuteWithValue(0)), bytes32(0), 0); + result = acct.validateUserOp(_getPackedUO(100_000, 100_000, 1, 1, _getExecuteWithValue(0)), bytes32(0), 0); } function test_userOp_executeLimit() public { @@ -130,24 +132,24 @@ contract NativeTokenLimitPluginTest is AccountTestBase { Call[] memory calls = new Call[](3); calls[0] = Call({target: recipient, value: 1, data: ""}); calls[1] = Call({target: recipient, value: 1 ether, data: ""}); - calls[2] = Call({target: recipient, value: 5 ether + 100000, data: ""}); + calls[2] = Call({target: recipient, value: 5 ether + 100_000, data: ""}); vm.startPrank(address(entryPoint)); assertEq(plugin.limits(0, address(acct)), 10 ether); acct.executeUserOp( _getPackedUO(0, 0, 0, 0, abi.encodeCall(IStandardExecutor.executeBatch, (calls))), bytes32(0) ); - assertEq(plugin.limits(0, address(acct)), 10 ether - 6 ether - 100001); - assertEq(recipient.balance, 6 ether + 100001); + assertEq(plugin.limits(0, address(acct)), 10 ether - 6 ether - 100_001); + assertEq(recipient.balance, 6 ether + 100_001); } function test_userOp_combinedExecLimit_success() public { assertEq(plugin.limits(0, address(acct)), 10 ether); PackedUserOperation[] memory uos = new PackedUserOperation[](1); - uos[0] = _getPackedUO(200000, 200000, 200000, 1, _getExecuteWithValue(5 ether)); + uos[0] = _getPackedUO(200_000, 200_000, 200_000, 1, _getExecuteWithValue(5 ether)); entryPoint.handleOps(uos, bundler); - assertEq(plugin.limits(0, address(acct)), 5 ether - 600000); + assertEq(plugin.limits(0, address(acct)), 5 ether - 600_000); assertEq(recipient.balance, 5 ether); } @@ -155,25 +157,26 @@ contract NativeTokenLimitPluginTest is AccountTestBase { Call[] memory calls = new Call[](3); calls[0] = Call({target: recipient, value: 1, data: ""}); calls[1] = Call({target: recipient, value: 1 ether, data: ""}); - calls[2] = Call({target: recipient, value: 5 ether + 100000, data: ""}); + calls[2] = Call({target: recipient, value: 5 ether + 100_000, data: ""}); vm.startPrank(address(entryPoint)); assertEq(plugin.limits(0, address(acct)), 10 ether); PackedUserOperation[] memory uos = new PackedUserOperation[](1); - uos[0] = _getPackedUO(200000, 200000, 200000, 1, abi.encodeCall(IStandardExecutor.executeBatch, (calls))); + uos[0] = + _getPackedUO(200_000, 200_000, 200_000, 1, abi.encodeCall(IStandardExecutor.executeBatch, (calls))); entryPoint.handleOps(uos, bundler); - assertEq(plugin.limits(0, address(acct)), 10 ether - 6 ether - 700001); - assertEq(recipient.balance, 6 ether + 100001); + assertEq(plugin.limits(0, address(acct)), 10 ether - 6 ether - 700_001); + assertEq(recipient.balance, 6 ether + 100_001); } function test_userOp_combinedExecLimit_failExec() public { assertEq(plugin.limits(0, address(acct)), 10 ether); PackedUserOperation[] memory uos = new PackedUserOperation[](1); - uos[0] = _getPackedUO(200000, 200000, 200000, 1, _getExecuteWithValue(10 ether)); + uos[0] = _getPackedUO(200_000, 200_000, 200_000, 1, _getExecuteWithValue(10 ether)); entryPoint.handleOps(uos, bundler); - assertEq(plugin.limits(0, address(acct)), 10 ether - 600000); + assertEq(plugin.limits(0, address(acct)), 10 ether - 600_000); assertEq(recipient.balance, 0); } @@ -187,12 +190,12 @@ contract NativeTokenLimitPluginTest is AccountTestBase { Call[] memory calls = new Call[](3); calls[0] = Call({target: recipient, value: 1, data: ""}); calls[1] = Call({target: recipient, value: 1 ether, data: ""}); - calls[2] = Call({target: recipient, value: 5 ether + 100000, data: ""}); + calls[2] = Call({target: recipient, value: 5 ether + 100_000, data: ""}); assertEq(plugin.limits(0, address(acct)), 10 ether); acct.executeWithAuthorization( abi.encodeCall(IStandardExecutor.executeBatch, (calls)), _encodeSignature(validationFunction, 1, "") ); - assertEq(plugin.limits(0, address(acct)), 4 ether - 100001); + assertEq(plugin.limits(0, address(acct)), 4 ether - 100_001); } } diff --git a/test/plugin/TokenReceiverPlugin.t.sol b/test/plugin/TokenReceiverPlugin.t.sol index 1e198f0b..32fa2a9a 100644 --- a/test/plugin/TokenReceiverPlugin.t.sol +++ b/test/plugin/TokenReceiverPlugin.t.sol @@ -2,15 +2,17 @@ pragma solidity ^0.8.19; import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.sol"; -import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; + import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; +import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {TokenReceiverPlugin} from "../../src/plugins/TokenReceiverPlugin.sol"; -import {SingleSignerFactoryFixture} from "../mocks/SingleSignerFactoryFixture.sol"; -import {MockERC721} from "../mocks/MockERC721.sol"; import {MockERC1155} from "../mocks/MockERC1155.sol"; +import {MockERC721} from "../mocks/MockERC721.sol"; +import {SingleSignerFactoryFixture} from "../mocks/SingleSignerFactoryFixture.sol"; + import {OptimizedTest} from "../utils/OptimizedTest.sol"; contract TokenReceiverPluginTest is OptimizedTest, IERC1155Receiver { diff --git a/test/samples/AllowlistPlugin.t.sol b/test/samples/AllowlistPlugin.t.sol index 441fbdcb..d2a28f5e 100644 --- a/test/samples/AllowlistPlugin.t.sol +++ b/test/samples/AllowlistPlugin.t.sol @@ -3,13 +3,14 @@ pragma solidity ^0.8.25; import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; -import {Call} from "../../src/interfaces/IStandardExecutor.sol"; -import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; +import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; +import {Call} from "../../src/interfaces/IStandardExecutor.sol"; + import {AllowlistPlugin} from "../../src/samples/permissionhooks/AllowlistPlugin.sol"; -import {CustomValidationTestBase} from "../utils/CustomValidationTestBase.sol"; import {Counter} from "../mocks/Counter.sol"; +import {CustomValidationTestBase} from "../utils/CustomValidationTestBase.sol"; contract AllowlistPluginTest is CustomValidationTestBase { AllowlistPlugin public allowlistPlugin; diff --git a/test/utils/AccountTestBase.sol b/test/utils/AccountTestBase.sol index 526365d0..94caaabb 100644 --- a/test/utils/AccountTestBase.sol +++ b/test/utils/AccountTestBase.sol @@ -5,10 +5,10 @@ import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.so import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; -import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; -import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; -import {IStandardExecutor, Call} from "../../src/interfaces/IStandardExecutor.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; +import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; +import {Call, IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; +import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; import {OptimizedTest} from "./OptimizedTest.sol"; import {TEST_DEFAULT_VALIDATION_ENTITY_ID as EXT_CONST_TEST_DEFAULT_VALIDATION_ENTITY_ID} from @@ -40,8 +40,8 @@ abstract contract AccountTestBase is OptimizedTest { // Re-declare the constant to prevent derived test contracts from having to import it uint32 public constant TEST_DEFAULT_VALIDATION_ENTITY_ID = EXT_CONST_TEST_DEFAULT_VALIDATION_ENTITY_ID; - uint256 public constant CALL_GAS_LIMIT = 100000; - uint256 public constant VERIFICATION_GAS_LIMIT = 1200000; + uint256 public constant CALL_GAS_LIMIT = 100_000; + uint256 public constant VERIFICATION_GAS_LIMIT = 1_200_000; struct PreValidationHookData { uint8 index; diff --git a/test/utils/CustomValidationTestBase.sol b/test/utils/CustomValidationTestBase.sol index bd119fa1..f9e37c27 100644 --- a/test/utils/CustomValidationTestBase.sol +++ b/test/utils/CustomValidationTestBase.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.25; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {PluginEntity} from "../../src/helpers/PluginEntityLib.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; -import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {AccountTestBase} from "./AccountTestBase.sol"; diff --git a/test/utils/OptimizedTest.sol b/test/utils/OptimizedTest.sol index d884193f..dc94380b 100644 --- a/test/utils/OptimizedTest.sol +++ b/test/utils/OptimizedTest.sol @@ -6,8 +6,9 @@ import {Test} from "forge-std/Test.sol"; import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; + import {TokenReceiverPlugin} from "../../src/plugins/TokenReceiverPlugin.sol"; +import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; /// @dev This contract provides functions to deploy optimized (via IR) precompiled contracts. By compiling just /// the source contracts (excluding the test suite) via IR, and using the resulting bytecode within the tests diff --git a/test/validation/SingleSignerValidation.t.sol b/test/validation/SingleSignerValidation.t.sol index a983ea93..e81706fe 100644 --- a/test/validation/SingleSignerValidation.t.sol +++ b/test/validation/SingleSignerValidation.t.sol @@ -8,9 +8,9 @@ import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAcc import {PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; +import {ContractOwner} from "../mocks/ContractOwner.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; import {TEST_DEFAULT_VALIDATION_ENTITY_ID} from "../utils/TestConstants.sol"; -import {ContractOwner} from "../mocks/ContractOwner.sol"; contract SingleSignerValidationTest is AccountTestBase { using MessageHashUtils for bytes32; From 5d0c3bbf8373626e20d5bd2799de164acdd7b9f8 Mon Sep 17 00:00:00 2001 From: Zer0dot Date: Thu, 18 Jul 2024 02:26:01 +0800 Subject: [PATCH 047/111] Zer0dot/remove redundancy (#99) --- src/account/PluginManager2.sol | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/account/PluginManager2.sol b/src/account/PluginManager2.sol index 845b80b8..b77ae1bf 100644 --- a/src/account/PluginManager2.sol +++ b/src/account/PluginManager2.sol @@ -124,20 +124,20 @@ abstract contract PluginManager2 { // Clear permission hooks EnumerableSet.Bytes32Set storage permissionHooks = _validationData.permissionHooks; - uint256 len = permissionHooks.length(); - for (uint256 i = 0; i < len; ++i) { + uint256 permissionHookLen = permissionHooks.length(); + for (uint256 i = 0; i < permissionHookLen; ++i) { bytes32 permissionHook = permissionHooks.at(0); permissionHooks.remove(permissionHook); address permissionHookPlugin = address(uint160(bytes20(permissionHook))); IPlugin(permissionHookPlugin).onUninstall(permissionHookUninstallDatas[i]); } } - delete _validationData.preValidationHooks; // Clear selectors - while (_validationData.selectors.length() > 0) { - bytes32 selector = _validationData.selectors.at(0); - _validationData.selectors.remove(selector); + uint256 selectorLen = _validationData.selectors.length(); + for (uint256 i = 0; i < selectorLen; ++i) { + bytes32 selectorSetValue = _validationData.selectors.at(0); + _validationData.selectors.remove(selectorSetValue); } if (uninstallData.length > 0) { From a257ded87a00f36906f6b0caf6ad984c9ab532f8 Mon Sep 17 00:00:00 2001 From: fangting-alchemy <119372438+fangting-alchemy@users.noreply.github.com> Date: Wed, 17 Jul 2024 11:46:34 -0700 Subject: [PATCH 048/111] fix: Add back pluginEntityLib test and update old references and docs (#100) --- src/account/AccountStorage.sol | 7 ++--- test/libraries/PluginEntityLib.t.sol | 40 ++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 test/libraries/PluginEntityLib.t.sol diff --git a/src/account/AccountStorage.sol b/src/account/AccountStorage.sol index ea2f4551..dbf0d956 100644 --- a/src/account/AccountStorage.sol +++ b/src/account/AccountStorage.sol @@ -15,7 +15,8 @@ struct SelectorData { // The plugin that implements this execution function. // If this is a native function, the address must remain address(0). address plugin; - // Whether or not the function needs runtime validation, or can be called by anyone. + // 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 isPublic; @@ -59,8 +60,8 @@ function getAccountStorage() pure returns (AccountStorage storage _storage) { using EnumerableSet for EnumerableSet.Bytes32Set; -function toSetValue(PluginEntity functionReference) pure returns (bytes32) { - return bytes32(PluginEntity.unwrap(functionReference)); +function toSetValue(PluginEntity pluginEntity) pure returns (bytes32) { + return bytes32(PluginEntity.unwrap(pluginEntity)); } function toPluginEntity(bytes32 setValue) pure returns (PluginEntity) { diff --git a/test/libraries/PluginEntityLib.t.sol b/test/libraries/PluginEntityLib.t.sol new file mode 100644 index 00000000..c93e1b61 --- /dev/null +++ b/test/libraries/PluginEntityLib.t.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {Test} from "forge-std/Test.sol"; + +import {PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; +import {PluginEntity} from "../../src/interfaces/IPluginManager.sol"; + +contract PluginEntityLibTest is Test { + using PluginEntityLib for PluginEntity; + + function testFuzz_pluginEntity_packing(address addr, uint32 entityId) public { + // console.log("addr: ", addr); + // console.log("entityId: ", vm.toString(entityId)); + PluginEntity fr = PluginEntityLib.pack(addr, entityId); + // console.log("packed: ", vm.toString(PluginEntity.unwrap(fr))); + (address addr2, uint32 entityId2) = PluginEntityLib.unpack(fr); + // console.log("addr2: ", addr2); + // console.log("entityId2: ", vm.toString(entityId2)); + assertEq(addr, addr2); + assertEq(entityId, entityId2); + } + + function testFuzz_pluginEntity_operators(PluginEntity a, PluginEntity b) public { + assertTrue(a.eq(a)); + assertTrue(b.eq(b)); + + if (PluginEntity.unwrap(a) == PluginEntity.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)); + } + } +} From 51e64d09cdc6cdc59a4dfdb4129da38f6f2d4411 Mon Sep 17 00:00:00 2001 From: zer0dot Date: Thu, 18 Jul 2024 21:00:08 +0800 Subject: [PATCH 049/111] forge install: solady v0.0.226 --- .gitmodules | 3 +++ lib/solady | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/solady diff --git a/.gitmodules b/.gitmodules index 05bd137f..229aff1b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "lib/modular-account-libs"] path = lib/modular-account-libs url = https://github.com/erc6900/modular-account-libs +[submodule "lib/solady"] + path = lib/solady + url = https://github.com/vectorized/solady diff --git a/lib/solady b/lib/solady new file mode 160000 index 00000000..753c57ba --- /dev/null +++ b/lib/solady @@ -0,0 +1 @@ +Subproject commit 753c57bad8912defb609fec6d2342f8396e6a5ac From cca457205ea4859d041c398eef1a7c4d87c14022 Mon Sep 17 00:00:00 2001 From: fangting-alchemy <119372438+fangting-alchemy@users.noreply.github.com> Date: Fri, 19 Jul 2024 10:17:20 -0700 Subject: [PATCH 050/111] fix: Rename plugin to module and update readme and docs (#106) --- README.md | 4 +- src/account/AccountLoupe.sol | 22 +- src/account/AccountStorage.sol | 35 ++- ...{PluginManager2.sol => ModuleManager2.sol} | 56 ++-- ...ternals.sol => ModuleManagerInternals.sol} | 128 ++++---- src/account/UpgradeableModularAccount.sol | 170 +++++------ src/helpers/KnownSelectors.sol | 16 +- src/helpers/ModuleEntityLib.sol | 35 +++ src/helpers/PluginEntityLib.sol | 35 --- src/helpers/ValidationConfigLib.sol | 26 +- src/interfaces/IAccountLoupe.sol | 26 +- src/interfaces/IExecutionHook.sol | 4 +- src/interfaces/{IPlugin.sol => IModule.sol} | 48 +-- ...{IPluginManager.sol => IModuleManager.sol} | 36 +-- src/interfaces/IStandardExecutor.sol | 3 +- src/interfaces/IValidation.sol | 4 +- src/interfaces/IValidationHook.sol | 4 +- .../BasePlugin.sol => modules/BaseModule.sol} | 16 +- .../ERC20TokenLimitModule.sol} | 36 +-- .../NativeTokenLimitModule.sol} | 30 +- .../TokenReceiverModule.sol} | 30 +- .../validation/ISingleSignerValidation.sol | 0 .../validation/SingleSignerValidation.sol | 24 +- ...llowlistPlugin.sol => AllowlistModule.sol} | 14 +- standard/ERCs/ERC6900Diagrams.excalidraw | 104 +++---- standard/ERCs/erc-6900.md | 284 +++++++++--------- .../MSCA_Shared_Components_Diagram.svg | 2 +- .../eip-6900/Modular_Account_Call_Flow.svg | 2 +- .../assets/eip-6900/Plugin_Execution_Flow.svg | 2 +- test/account/AccountExecHooks.t.sol | 68 ++--- test/account/AccountLoupe.t.sol | 104 +++---- test/account/AccountReturnData.t.sol | 52 ++-- ...ugin.t.sol => DirectCallsFromModule.t.sol} | 88 +++--- test/account/GlobalValidationTest.t.sol | 4 +- test/account/MultiValidation.t.sol | 20 +- test/account/PerHookData.t.sol | 28 +- test/account/PermittedCallPermissions.t.sol | 40 +-- test/account/SelfCallAuthorization.t.sol | 102 +++---- test/account/UpgradeableModularAccount.t.sol | 180 +++++------ test/account/ValidationIntersection.t.sol | 138 ++++----- test/libraries/KnowSelectors.t.sol | 6 +- ...nEntityLib.t.sol => ModuleEntityLib.t.sol} | 20 +- test/mocks/{MockPlugin.sol => MockModule.sol} | 32 +- test/mocks/SingleSignerFactoryFixture.sol | 8 +- .../ComprehensiveModule.sol} | 24 +- .../DirectCallModule.sol} | 10 +- .../MockAccessControlHookModule.sol} | 12 +- .../PermittedCallMocks.sol | 22 +- .../ReturnDataModuleMocks.sol} | 30 +- .../ValidationModuleMocks.sol} | 38 +-- .../ERC20TokenLimitModule.t.sol} | 62 ++-- .../NativeTokenLimitModule.t.sol} | 72 ++--- .../TokenReceiverModule.t.sol} | 20 +- ...listPlugin.t.sol => AllowlistModule.t.sol} | 62 ++-- test/utils/AccountTestBase.sol | 22 +- test/utils/CustomValidationTestBase.sol | 6 +- test/utils/OptimizedTest.sol | 10 +- test/validation/SingleSignerValidation.t.sol | 8 +- 58 files changed, 1241 insertions(+), 1243 deletions(-) rename src/account/{PluginManager2.sol => ModuleManager2.sol} (75%) rename src/account/{PluginManagerInternals.sol => ModuleManagerInternals.sol} (67%) create mode 100644 src/helpers/ModuleEntityLib.sol delete mode 100644 src/helpers/PluginEntityLib.sol rename src/interfaces/{IPlugin.sol => IModule.sol} (66%) rename src/interfaces/{IPluginManager.sol => IModuleManager.sol} (63%) rename src/{plugins/BasePlugin.sol => modules/BaseModule.sol} (83%) rename src/{plugins/ERC20TokenLimitPlugin.sol => modules/ERC20TokenLimitModule.sol} (85%) rename src/{plugins/NativeTokenLimitPlugin.sol => modules/NativeTokenLimitModule.sol} (89%) rename src/{plugins/TokenReceiverPlugin.sol => modules/TokenReceiverModule.sol} (78%) rename src/{plugins => modules}/validation/ISingleSignerValidation.sol (100%) rename src/{plugins => modules}/validation/SingleSignerValidation.sol (91%) rename src/samples/permissionhooks/{AllowlistPlugin.sol => AllowlistModule.sol} (91%) rename test/account/{DirectCallsFromPlugin.t.sol => DirectCallsFromModule.t.sol} (59%) rename test/libraries/{PluginEntityLib.t.sol => ModuleEntityLib.t.sol} (56%) rename test/mocks/{MockPlugin.sol => MockModule.sol} (75%) rename test/mocks/{plugins/ComprehensivePlugin.sol => modules/ComprehensiveModule.sol} (89%) rename test/mocks/{plugins/DirectCallPlugin.sol => modules/DirectCallModule.sol} (77%) rename test/mocks/{plugins/MockAccessControlHookPlugin.sol => modules/MockAccessControlHookModule.sol} (85%) rename test/mocks/{plugins => modules}/PermittedCallMocks.sol (53%) rename test/mocks/{plugins/ReturnDataPluginMocks.sol => modules/ReturnDataModuleMocks.sol} (84%) rename test/mocks/{plugins/ValidationPluginMocks.sol => modules/ValidationModuleMocks.sol} (86%) rename test/{plugin/ERC20TokenLimitPlugin.t.sol => module/ERC20TokenLimitModule.t.sol} (75%) rename test/{plugin/NativeTokenLimitPlugin.t.sol => module/NativeTokenLimitModule.t.sol} (74%) rename test/{plugin/TokenReceiverPlugin.t.sol => module/TokenReceiverModule.t.sol} (92%) rename test/samples/{AllowlistPlugin.t.sol => AllowlistModule.t.sol} (83%) diff --git a/README.md b/README.md index 1c90c710..b3b625b4 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Reference implementation for [ERC-6900](https://eips.ethereum.org/EIPS/eip-6900). It is an early draft implementation. -The implementation includes an upgradable modular account with two plugins (`SingleSignerValidation` and `TokenReceiverPlugin`). It is compliant with ERC-6900 with the latest updates. +The implementation includes an upgradable modular account with three modules (`SingleSignerValidation`, `TokenReceiverModule`, and `AllowlistModule`). It is compliant with ERC-6900 with the latest updates. ## Important Callouts @@ -11,7 +11,7 @@ The implementation includes an upgradable modular account with two plugins (`Sin ## Development -Anyone is welcome to submit feedback and/or PRs to improve code or add Plugins. +Anyone is welcome to submit feedback and/or PRs to improve code. ### Testing diff --git a/src/account/AccountLoupe.sol b/src/account/AccountLoupe.sol index ef679e4e..851c9cfa 100644 --- a/src/account/AccountLoupe.sol +++ b/src/account/AccountLoupe.sol @@ -7,7 +7,7 @@ import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {ExecutionHook, IAccountLoupe} from "../interfaces/IAccountLoupe.sol"; -import {IPluginManager, PluginEntity} from "../interfaces/IPluginManager.sol"; +import {IModuleManager, ModuleEntity} from "../interfaces/IModuleManager.sol"; import {IStandardExecutor} from "../interfaces/IStandardExecutor.sol"; import {getAccountStorage, toExecutionHook, toSelector} from "./AccountStorage.sol"; @@ -16,21 +16,21 @@ abstract contract AccountLoupe is IAccountLoupe { using EnumerableMap for EnumerableMap.AddressToUintMap; /// @inheritdoc IAccountLoupe - function getExecutionFunctionHandler(bytes4 selector) external view override returns (address plugin) { + function getExecutionFunctionHandler(bytes4 selector) external view override returns (address module) { if ( selector == IStandardExecutor.execute.selector || selector == IStandardExecutor.executeBatch.selector || selector == UUPSUpgradeable.upgradeToAndCall.selector - || selector == IPluginManager.installPlugin.selector - || selector == IPluginManager.uninstallPlugin.selector + || selector == IModuleManager.installModule.selector + || selector == IModuleManager.uninstallModule.selector ) { return address(this); } - return getAccountStorage().selectorData[selector].plugin; + return getAccountStorage().selectorData[selector].module; } /// @inheritdoc IAccountLoupe - function getSelectors(PluginEntity validationFunction) external view returns (bytes4[] memory) { + function getSelectors(ModuleEntity validationFunction) external view returns (bytes4[] memory) { uint256 length = getAccountStorage().validationData[validationFunction].selectors.length(); bytes4[] memory selectors = new bytes4[](length); @@ -62,7 +62,7 @@ abstract contract AccountLoupe is IAccountLoupe { } /// @inheritdoc IAccountLoupe - function getPermissionHooks(PluginEntity validationFunction) + function getPermissionHooks(ModuleEntity validationFunction) external view override @@ -80,17 +80,17 @@ abstract contract AccountLoupe is IAccountLoupe { } /// @inheritdoc IAccountLoupe - function getPreValidationHooks(PluginEntity validationFunction) + function getPreValidationHooks(ModuleEntity validationFunction) external view override - returns (PluginEntity[] memory preValidationHooks) + returns (ModuleEntity[] memory preValidationHooks) { preValidationHooks = getAccountStorage().validationData[validationFunction].preValidationHooks; } /// @inheritdoc IAccountLoupe - function getInstalledPlugins() external view override returns (address[] memory pluginAddresses) { - pluginAddresses = getAccountStorage().pluginManifestHashes.keys(); + function getInstalledModules() external view override returns (address[] memory moduleAddresses) { + moduleAddresses = getAccountStorage().moduleManifestHashes.keys(); } } diff --git a/src/account/AccountStorage.sol b/src/account/AccountStorage.sol index dbf0d956..2c480e32 100644 --- a/src/account/AccountStorage.sol +++ b/src/account/AccountStorage.sol @@ -5,16 +5,16 @@ import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; -import {PluginEntity} from "../interfaces/IPluginManager.sol"; +import {ModuleEntity} from "../interfaces/IModuleManager.sol"; // bytes = keccak256("ERC6900.UpgradeableModularAccount.Storage") bytes32 constant _ACCOUNT_STORAGE_SLOT = 0x9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e40; // Represents data associated with a specifc function selector. struct SelectorData { - // The plugin that implements this execution function. + // The module that implements this execution function. // If this is a native function, the address must remain address(0). - address plugin; + 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 @@ -32,7 +32,7 @@ struct ValidationData { // Whether or not this validation is a signature validator. bool isSignatureValidation; // The pre validation hooks for this validation function. - PluginEntity[] preValidationHooks; + ModuleEntity[] preValidationHooks; // Permission hooks for this validation function. EnumerableSet.Bytes32Set permissionHooks; // The set of selectors that may be validated by this validation function. @@ -43,11 +43,10 @@ struct AccountStorage { // AccountStorageInitializable variables uint8 initialized; bool initializing; - // Plugin metadata storage - EnumerableMap.AddressToUintMap pluginManifestHashes; + EnumerableMap.AddressToUintMap moduleManifestHashes; // Execution functions and their associated functions mapping(bytes4 => SelectorData) selectorData; - mapping(PluginEntity validationFunction => ValidationData) validationData; + mapping(ModuleEntity validationFunction => ValidationData) validationData; // For ERC165 introspection mapping(bytes4 => uint256) supportedIfaces; } @@ -60,30 +59,30 @@ function getAccountStorage() pure returns (AccountStorage storage _storage) { using EnumerableSet for EnumerableSet.Bytes32Set; -function toSetValue(PluginEntity pluginEntity) pure returns (bytes32) { - return bytes32(PluginEntity.unwrap(pluginEntity)); +function toSetValue(ModuleEntity moduleEntity) pure returns (bytes32) { + return bytes32(ModuleEntity.unwrap(moduleEntity)); } -function toPluginEntity(bytes32 setValue) pure returns (PluginEntity) { - return PluginEntity.wrap(bytes24(setValue)); +function toModuleEntity(bytes32 setValue) pure returns (ModuleEntity) { + return ModuleEntity.wrap(bytes24(setValue)); } // ExecutionHook layout: -// 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF______________________ Hook Plugin Entity +// 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF______________________ Hook Module Entity // 0x________________________________________________AA____________________ is pre hook // 0x__________________________________________________BB__________________ is post hook function toSetValue(ExecutionHook memory executionHook) pure returns (bytes32) { - return bytes32(PluginEntity.unwrap(executionHook.hookFunction)) + return bytes32(ModuleEntity.unwrap(executionHook.hookFunction)) | bytes32(executionHook.isPreHook ? uint256(1) << 56 : 0) | bytes32(executionHook.isPostHook ? uint256(1) << 48 : 0); } function toExecutionHook(bytes32 setValue) pure - returns (PluginEntity hookFunction, bool isPreHook, bool isPostHook) + returns (ModuleEntity hookFunction, bool isPreHook, bool isPostHook) { - hookFunction = PluginEntity.wrap(bytes24(setValue)); + hookFunction = ModuleEntity.wrap(bytes24(setValue)); isPreHook = (uint256(setValue) >> 56) & 0xFF == 1; isPostHook = (uint256(setValue) >> 48) & 0xFF == 1; } @@ -97,12 +96,12 @@ function toSelector(bytes32 setValue) pure returns (bytes4) { } /// @dev Helper function to get all elements of a set into memory. -function toPluginEntityArray(EnumerableSet.Bytes32Set storage set) view returns (PluginEntity[] memory) { +function toModuleEntityArray(EnumerableSet.Bytes32Set storage set) view returns (ModuleEntity[] memory) { uint256 length = set.length(); - PluginEntity[] memory result = new PluginEntity[](length); + ModuleEntity[] memory result = new ModuleEntity[](length); for (uint256 i = 0; i < length; ++i) { bytes32 key = set.at(i); - result[i] = PluginEntity.wrap(bytes24(key)); + result[i] = ModuleEntity.wrap(bytes24(key)); } return result; } diff --git a/src/account/PluginManager2.sol b/src/account/ModuleManager2.sol similarity index 75% rename from src/account/PluginManager2.sol rename to src/account/ModuleManager2.sol index b77ae1bf..e1868403 100644 --- a/src/account/PluginManager2.sol +++ b/src/account/ModuleManager2.sol @@ -3,16 +3,16 @@ pragma solidity ^0.8.25; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import {PluginEntityLib} from "../helpers/PluginEntityLib.sol"; +import {ModuleEntityLib} from "../helpers/ModuleEntityLib.sol"; import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; -import {IPlugin} from "../interfaces/IPlugin.sol"; -import {PluginEntity, ValidationConfig} from "../interfaces/IPluginManager.sol"; +import {IModule} from "../interfaces/IModule.sol"; +import {ModuleEntity, ValidationConfig} from "../interfaces/IModuleManager.sol"; import {ValidationData, getAccountStorage, toSetValue} from "./AccountStorage.sol"; // Temporary additional functions for a user-controlled install flow for validation functions. -abstract contract PluginManager2 { +abstract contract ModuleManager2 { using EnumerableSet for EnumerableSet.Bytes32Set; using ValidationConfigLib for ValidationConfig; @@ -20,10 +20,10 @@ abstract contract PluginManager2 { uint8 internal constant _RESERVED_VALIDATION_DATA_INDEX = 255; uint32 internal constant _SELF_PERMIT_VALIDATION_FUNCTIONID = type(uint32).max; - error PreValidationAlreadySet(PluginEntity validationFunction, PluginEntity preValidationFunction); - error ValidationAlreadySet(bytes4 selector, PluginEntity validationFunction); - error ValidationNotSet(bytes4 selector, PluginEntity validationFunction); - error PermissionAlreadySet(PluginEntity validationFunction, ExecutionHook hook); + error PreValidationAlreadySet(ModuleEntity validationFunction, ModuleEntity preValidationFunction); + error ValidationAlreadySet(bytes4 selector, ModuleEntity validationFunction); + error ValidationNotSet(bytes4 selector, ModuleEntity validationFunction); + error PermissionAlreadySet(ModuleEntity validationFunction, ExecutionHook hook); error PreValidationHookLimitExceeded(); function _installValidation( @@ -34,20 +34,20 @@ abstract contract PluginManager2 { bytes memory permissionHooks ) internal { ValidationData storage _validationData = - getAccountStorage().validationData[validationConfig.pluginEntity()]; + getAccountStorage().validationData[validationConfig.moduleEntity()]; if (preValidationHooks.length > 0) { - (PluginEntity[] memory preValidationFunctions, bytes[] memory initDatas) = - abi.decode(preValidationHooks, (PluginEntity[], bytes[])); + (ModuleEntity[] memory preValidationFunctions, bytes[] memory initDatas) = + abi.decode(preValidationHooks, (ModuleEntity[], bytes[])); for (uint256 i = 0; i < preValidationFunctions.length; ++i) { - PluginEntity preValidationFunction = preValidationFunctions[i]; + ModuleEntity preValidationFunction = preValidationFunctions[i]; _validationData.preValidationHooks.push(preValidationFunction); if (initDatas[i].length > 0) { - (address preValidationPlugin,) = PluginEntityLib.unpack(preValidationFunction); - IPlugin(preValidationPlugin).onInstall(initDatas[i]); + (address preValidationModule,) = ModuleEntityLib.unpack(preValidationFunction); + IModule(preValidationModule).onInstall(initDatas[i]); } } @@ -65,12 +65,12 @@ abstract contract PluginManager2 { ExecutionHook memory permissionFunction = permissionFunctions[i]; if (!_validationData.permissionHooks.add(toSetValue(permissionFunction))) { - revert PermissionAlreadySet(validationConfig.pluginEntity(), permissionFunction); + revert PermissionAlreadySet(validationConfig.moduleEntity(), permissionFunction); } if (initDatas[i].length > 0) { - (address executionPlugin,) = PluginEntityLib.unpack(permissionFunction.hookFunction); - IPlugin(executionPlugin).onInstall(initDatas[i]); + (address executionModule,) = ModuleEntityLib.unpack(permissionFunction.hookFunction); + IModule(executionModule).onInstall(initDatas[i]); } } } @@ -78,7 +78,7 @@ abstract contract PluginManager2 { for (uint256 i = 0; i < selectors.length; ++i) { bytes4 selector = selectors[i]; if (!_validationData.selectors.add(toSetValue(selector))) { - revert ValidationAlreadySet(selector, validationConfig.pluginEntity()); + revert ValidationAlreadySet(selector, validationConfig.moduleEntity()); } } @@ -88,13 +88,13 @@ abstract contract PluginManager2 { _validationData.isGlobal = validationConfig.isGlobal(); _validationData.isSignatureValidation = validationConfig.isSignatureValidation(); if (installData.length > 0) { - IPlugin(validationConfig.plugin()).onInstall(installData); + IModule(validationConfig.module()).onInstall(installData); } } } function _uninstallValidation( - PluginEntity validationFunction, + ModuleEntity validationFunction, bytes calldata uninstallData, bytes calldata preValidationHookUninstallData, bytes calldata permissionHookUninstallData @@ -108,12 +108,12 @@ abstract contract PluginManager2 { bytes[] memory preValidationHookUninstallDatas = abi.decode(preValidationHookUninstallData, (bytes[])); // Clear pre validation hooks - PluginEntity[] storage preValidationHooks = _validationData.preValidationHooks; + ModuleEntity[] storage preValidationHooks = _validationData.preValidationHooks; for (uint256 i = 0; i < preValidationHooks.length; ++i) { - PluginEntity preValidationFunction = preValidationHooks[i]; + ModuleEntity preValidationFunction = preValidationHooks[i]; if (preValidationHookUninstallDatas[0].length > 0) { - (address preValidationPlugin,) = PluginEntityLib.unpack(preValidationFunction); - IPlugin(preValidationPlugin).onUninstall(preValidationHookUninstallDatas[0]); + (address preValidationModule,) = ModuleEntityLib.unpack(preValidationFunction); + IModule(preValidationModule).onUninstall(preValidationHookUninstallDatas[0]); } } delete _validationData.preValidationHooks; @@ -128,8 +128,8 @@ abstract contract PluginManager2 { for (uint256 i = 0; i < permissionHookLen; ++i) { bytes32 permissionHook = permissionHooks.at(0); permissionHooks.remove(permissionHook); - address permissionHookPlugin = address(uint160(bytes20(permissionHook))); - IPlugin(permissionHookPlugin).onUninstall(permissionHookUninstallDatas[i]); + address permissionHookModule = address(uint160(bytes20(permissionHook))); + IModule(permissionHookModule).onUninstall(permissionHookUninstallDatas[i]); } } @@ -141,8 +141,8 @@ abstract contract PluginManager2 { } if (uninstallData.length > 0) { - (address plugin,) = PluginEntityLib.unpack(validationFunction); - IPlugin(plugin).onUninstall(uninstallData); + (address module,) = ModuleEntityLib.unpack(validationFunction); + IModule(module).onUninstall(uninstallData); } } } diff --git a/src/account/PluginManagerInternals.sol b/src/account/ModuleManagerInternals.sol similarity index 67% rename from src/account/PluginManagerInternals.sol rename to src/account/ModuleManagerInternals.sol index e473730e..68d6ea1d 100644 --- a/src/account/PluginManagerInternals.sol +++ b/src/account/ModuleManagerInternals.sol @@ -7,39 +7,39 @@ import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {KnownSelectors} from "../helpers/KnownSelectors.sol"; -import {PluginEntityLib} from "../helpers/PluginEntityLib.sol"; +import {ModuleEntityLib} from "../helpers/ModuleEntityLib.sol"; import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; -import {IPlugin, ManifestExecutionHook, ManifestValidation, PluginManifest} from "../interfaces/IPlugin.sol"; -import {IPluginManager, PluginEntity} from "../interfaces/IPluginManager.sol"; +import {IModule, ManifestExecutionHook, ManifestValidation, ModuleManifest} from "../interfaces/IModule.sol"; +import {IModuleManager, ModuleEntity} from "../interfaces/IModuleManager.sol"; import {AccountStorage, SelectorData, getAccountStorage, toSetValue} from "./AccountStorage.sol"; -abstract contract PluginManagerInternals is IPluginManager { +abstract contract ModuleManagerInternals is IModuleManager { using EnumerableSet for EnumerableSet.Bytes32Set; using EnumerableMap for EnumerableMap.AddressToUintMap; - using PluginEntityLib for PluginEntity; + using ModuleEntityLib for ModuleEntity; error ArrayLengthMismatch(); error Erc4337FunctionNotAllowed(bytes4 selector); error ExecutionFunctionAlreadySet(bytes4 selector); - error InvalidPluginManifest(); - error IPluginFunctionNotAllowed(bytes4 selector); + error InvalidModuleManifest(); + error IModuleFunctionNotAllowed(bytes4 selector); error NativeFunctionNotAllowed(bytes4 selector); - error NullPluginEntity(); - error NullPlugin(); - error PluginAlreadyInstalled(address plugin); - error PluginInstallCallbackFailed(address plugin, bytes revertReason); - error PluginInterfaceNotSupported(address plugin); - error PluginNotInstalled(address plugin); - error ValidationFunctionAlreadySet(bytes4 selector, PluginEntity validationFunction); + error NullModuleEntity(); + error NullModule(); + error ModuleAlreadyInstalled(address module); + error ModuleInstallCallbackFailed(address module, bytes revertReason); + error ModuleInterfaceNotSupported(address module); + error ModuleNotInstalled(address module); + error ValidationFunctionAlreadySet(bytes4 selector, ModuleEntity validationFunction); // Storage update operations - function _setExecutionFunction(bytes4 selector, bool isPublic, bool allowGlobalValidation, address plugin) + function _setExecutionFunction(bytes4 selector, bool isPublic, bool allowGlobalValidation, address module) internal { SelectorData storage _selectorData = getAccountStorage().selectorData[selector]; - if (_selectorData.plugin != address(0)) { + if (_selectorData.module != address(0)) { revert ExecutionFunctionAlreadySet(selector); } @@ -49,13 +49,13 @@ abstract contract PluginManagerInternals is IPluginManager { revert NativeFunctionNotAllowed(selector); } - // Make sure incoming execution function is not a function in IPlugin - if (KnownSelectors.isIPluginFunction(selector)) { - revert IPluginFunctionNotAllowed(selector); + // Make sure incoming execution function is not a function in IModule + if (KnownSelectors.isIModuleFunction(selector)) { + revert IModuleFunctionNotAllowed(selector); } // Also make sure it doesn't collide with functions defined by ERC-4337 - // and called by the entry point. This prevents a malicious plugin from + // and called by the entry point. This prevents a malicious module from // sneaking in a function with the same selector as e.g. // `validatePaymasterUserOp` and turning the account into their own // personal paymaster. @@ -63,7 +63,7 @@ abstract contract PluginManagerInternals is IPluginManager { revert Erc4337FunctionNotAllowed(selector); } - _selectorData.plugin = plugin; + _selectorData.module = module; _selectorData.isPublic = isPublic; _selectorData.allowGlobalValidation = allowGlobalValidation; } @@ -71,15 +71,15 @@ abstract contract PluginManagerInternals is IPluginManager { function _removeExecutionFunction(bytes4 selector) internal { SelectorData storage _selectorData = getAccountStorage().selectorData[selector]; - _selectorData.plugin = address(0); + _selectorData.module = address(0); _selectorData.isPublic = false; _selectorData.allowGlobalValidation = false; } - function _addValidationFunction(address plugin, ManifestValidation memory mv) internal { + function _addValidationFunction(address module, ManifestValidation memory mv) internal { AccountStorage storage _storage = getAccountStorage(); - PluginEntity validationFunction = PluginEntityLib.pack(plugin, mv.entityId); + ModuleEntity validationFunction = ModuleEntityLib.pack(module, mv.entityId); if (mv.isDefault) { _storage.validationData[validationFunction].isGlobal = true; @@ -97,10 +97,10 @@ abstract contract PluginManagerInternals is IPluginManager { } } - function _removeValidationFunction(address plugin, ManifestValidation memory mv) internal { + function _removeValidationFunction(address module, ManifestValidation memory mv) internal { AccountStorage storage _storage = getAccountStorage(); - PluginEntity validationFunction = PluginEntityLib.pack(plugin, mv.entityId); + ModuleEntity validationFunction = ModuleEntityLib.pack(module, mv.entityId); _storage.validationData[validationFunction].isGlobal = false; _storage.validationData[validationFunction].isSignatureValidation = false; @@ -114,7 +114,7 @@ abstract contract PluginManagerInternals is IPluginManager { function _addExecHooks( EnumerableSet.Bytes32Set storage hooks, - PluginEntity hookFunction, + ModuleEntity hookFunction, bool isPreExecHook, bool isPostExecHook ) internal { @@ -127,7 +127,7 @@ abstract contract PluginManagerInternals is IPluginManager { function _removeExecHooks( EnumerableSet.Bytes32Set storage hooks, - PluginEntity hookFunction, + ModuleEntity hookFunction, bool isPreExecHook, bool isPostExecHook ) internal { @@ -138,31 +138,31 @@ abstract contract PluginManagerInternals is IPluginManager { ); } - function _installPlugin(address plugin, bytes32 manifestHash, bytes memory pluginInstallData) internal { + function _installModule(address module, bytes32 manifestHash, bytes memory moduleInstallData) internal { AccountStorage storage _storage = getAccountStorage(); - if (plugin == address(0)) { - revert NullPlugin(); + if (module == address(0)) { + revert NullModule(); } - // Check if the plugin exists. - if (_storage.pluginManifestHashes.contains(plugin)) { - revert PluginAlreadyInstalled(plugin); + // Check if the module exists. + if (_storage.moduleManifestHashes.contains(module)) { + revert ModuleAlreadyInstalled(module); } - // Check that the plugin supports the IPlugin interface. - if (!ERC165Checker.supportsInterface(plugin, type(IPlugin).interfaceId)) { - revert PluginInterfaceNotSupported(plugin); + // Check that the module supports the IModule interface. + if (!ERC165Checker.supportsInterface(module, type(IModule).interfaceId)) { + revert ModuleInterfaceNotSupported(module); } // Check manifest hash. - PluginManifest memory manifest = IPlugin(plugin).pluginManifest(); - if (!_isValidPluginManifest(manifest, manifestHash)) { - revert InvalidPluginManifest(); + ModuleManifest memory manifest = IModule(module).moduleManifest(); + if (!_isValidModuleManifest(manifest, manifestHash)) { + revert InvalidModuleManifest(); } - // Add the plugin metadata to the account - _storage.pluginManifestHashes.set(plugin, uint256(manifestHash)); + // Add the module metadata to the account + _storage.moduleManifestHashes.set(module, uint256(manifestHash)); // Update components according to the manifest. uint256 length = manifest.executionFunctions.length; @@ -170,21 +170,21 @@ abstract contract PluginManagerInternals is IPluginManager { bytes4 selector = manifest.executionFunctions[i].executionSelector; bool isPublic = manifest.executionFunctions[i].isPublic; bool allowGlobalValidation = manifest.executionFunctions[i].allowGlobalValidation; - _setExecutionFunction(selector, isPublic, allowGlobalValidation, plugin); + _setExecutionFunction(selector, isPublic, allowGlobalValidation, module); } length = manifest.validationFunctions.length; for (uint256 i = 0; i < length; ++i) { // Todo: limit this to only "direct runtime call" validation path (old EFP), // and add a way for the user to specify permission/pre-val hooks here. - _addValidationFunction(plugin, manifest.validationFunctions[i]); + _addValidationFunction(module, manifest.validationFunctions[i]); } length = manifest.executionHooks.length; for (uint256 i = 0; i < length; ++i) { ManifestExecutionHook memory mh = manifest.executionHooks[i]; EnumerableSet.Bytes32Set storage execHooks = _storage.selectorData[mh.executionSelector].executionHooks; - PluginEntity hookFunction = PluginEntityLib.pack(plugin, mh.entityId); + ModuleEntity hookFunction = ModuleEntityLib.pack(module, mh.entityId); _addExecHooks(execHooks, hookFunction, mh.isPreHook, mh.isPostHook); } @@ -193,30 +193,30 @@ abstract contract PluginManagerInternals is IPluginManager { _storage.supportedIfaces[manifest.interfaceIds[i]] += 1; } - // Initialize the plugin storage for the account. + // Initialize the module storage for the account. // solhint-disable-next-line no-empty-blocks - try IPlugin(plugin).onInstall(pluginInstallData) {} + try IModule(module).onInstall(moduleInstallData) {} catch (bytes memory revertReason) { - revert PluginInstallCallbackFailed(plugin, revertReason); + revert ModuleInstallCallbackFailed(module, revertReason); } - emit PluginInstalled(plugin, manifestHash); + emit ModuleInstalled(module, manifestHash); } - function _uninstallPlugin(address plugin, PluginManifest memory manifest, bytes memory uninstallData) + function _uninstallModule(address module, ModuleManifest memory manifest, bytes memory uninstallData) internal { AccountStorage storage _storage = getAccountStorage(); - // Check if the plugin exists. - if (!_storage.pluginManifestHashes.contains(plugin)) { - revert PluginNotInstalled(plugin); + // Check if the module exists. + if (!_storage.moduleManifestHashes.contains(module)) { + revert ModuleNotInstalled(module); } // Check manifest hash. - bytes32 manifestHash = bytes32(_storage.pluginManifestHashes.get(plugin)); - if (!_isValidPluginManifest(manifest, manifestHash)) { - revert InvalidPluginManifest(); + bytes32 manifestHash = bytes32(_storage.moduleManifestHashes.get(module)); + if (!_isValidModuleManifest(manifest, manifestHash)) { + revert InvalidModuleManifest(); } // Remove components according to the manifest, in reverse order (by component type) of their installation. @@ -224,14 +224,14 @@ abstract contract PluginManagerInternals is IPluginManager { uint256 length = manifest.executionHooks.length; for (uint256 i = 0; i < length; ++i) { ManifestExecutionHook memory mh = manifest.executionHooks[i]; - PluginEntity hookFunction = PluginEntityLib.pack(plugin, mh.entityId); + ModuleEntity hookFunction = ModuleEntityLib.pack(module, mh.entityId); EnumerableSet.Bytes32Set storage execHooks = _storage.selectorData[mh.executionSelector].executionHooks; _removeExecHooks(execHooks, hookFunction, mh.isPreHook, mh.isPostHook); } length = manifest.validationFunctions.length; for (uint256 i = 0; i < length; ++i) { - _removeValidationFunction(plugin, manifest.validationFunctions[i]); + _removeValidationFunction(module, manifest.validationFunctions[i]); } length = manifest.executionFunctions.length; @@ -245,21 +245,21 @@ abstract contract PluginManagerInternals is IPluginManager { _storage.supportedIfaces[manifest.interfaceIds[i]] -= 1; } - // Remove the plugin metadata from the account. - _storage.pluginManifestHashes.remove(plugin); + // Remove the module metadata from the account. + _storage.moduleManifestHashes.remove(module); - // Clear the plugin storage for the account. + // Clear the module storage for the account. bool onUninstallSuccess = true; // solhint-disable-next-line no-empty-blocks - try IPlugin(plugin).onUninstall(uninstallData) {} + try IModule(module).onUninstall(uninstallData) {} catch { onUninstallSuccess = false; } - emit PluginUninstalled(plugin, onUninstallSuccess); + emit ModuleUninstalled(module, onUninstallSuccess); } - function _isValidPluginManifest(PluginManifest memory manifest, bytes32 manifestHash) + function _isValidModuleManifest(ModuleManifest memory manifest, bytes32 manifestHash) internal pure returns (bool) diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 4f64cdd3..9f102313 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -12,15 +12,15 @@ import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeab import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import {PluginEntityLib} from "../helpers/PluginEntityLib.sol"; +import {ModuleEntityLib} from "../helpers/ModuleEntityLib.sol"; import {SparseCalldataSegmentLib} from "../helpers/SparseCalldataSegmentLib.sol"; import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; import {_coalescePreValidation, _coalesceValidation} from "../helpers/ValidationResHelpers.sol"; import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; -import {IPlugin, PluginManifest} from "../interfaces/IPlugin.sol"; -import {IPluginManager, PluginEntity, ValidationConfig} from "../interfaces/IPluginManager.sol"; +import {IModule, ModuleManifest} from "../interfaces/IModule.sol"; +import {IModuleManager, ModuleEntity, ValidationConfig} from "../interfaces/IModuleManager.sol"; import {Call, IStandardExecutor} from "../interfaces/IStandardExecutor.sol"; import {IValidation} from "../interfaces/IValidation.sol"; import {IValidationHook} from "../interfaces/IValidationHook.sol"; @@ -29,8 +29,8 @@ import {AccountLoupe} from "./AccountLoupe.sol"; import {AccountStorage, getAccountStorage, toExecutionHook, toSetValue} from "./AccountStorage.sol"; import {AccountStorageInitializable} from "./AccountStorageInitializable.sol"; -import {PluginManager2} from "./PluginManager2.sol"; -import {PluginManagerInternals} from "./PluginManagerInternals.sol"; +import {ModuleManager2} from "./ModuleManager2.sol"; +import {ModuleManagerInternals} from "./ModuleManagerInternals.sol"; contract UpgradeableModularAccount is AccountExecutor, @@ -41,18 +41,18 @@ contract UpgradeableModularAccount is IERC1271, IStandardExecutor, IAccountExecute, - PluginManagerInternals, - PluginManager2, + ModuleManagerInternals, + ModuleManager2, UUPSUpgradeable { using EnumerableSet for EnumerableSet.Bytes32Set; - using PluginEntityLib for PluginEntity; + using ModuleEntityLib for ModuleEntity; using ValidationConfigLib for ValidationConfig; using SparseCalldataSegmentLib for bytes; struct PostExecToRun { bytes preExecHookReturnData; - PluginEntity postExecHook; + ModuleEntity postExecHook; } IEntryPoint private immutable _ENTRY_POINT; @@ -68,28 +68,28 @@ contract UpgradeableModularAccount is event ModularAccountInitialized(IEntryPoint indexed entryPoint); error AuthorizeUpgradeReverted(bytes revertReason); - error ExecFromPluginNotPermitted(address plugin, bytes4 selector); - error ExecFromPluginExternalNotPermitted(address plugin, address target, uint256 value, bytes data); - error NativeTokenSpendingNotPermitted(address plugin); + error ExecFromModuleNotPermitted(address module, bytes4 selector); + error ExecFromModuleExternalNotPermitted(address module, address target, uint256 value, bytes data); + error NativeTokenSpendingNotPermitted(address module); error NonCanonicalEncoding(); error NotEntryPoint(); - error PostExecHookReverted(address plugin, uint32 entityId, bytes revertReason); - error PreExecHookReverted(address plugin, uint32 entityId, bytes revertReason); - error PreRuntimeValidationHookFailed(address plugin, uint32 entityId, bytes revertReason); + error PostExecHookReverted(address module, uint32 entityId, bytes revertReason); + error PreExecHookReverted(address module, uint32 entityId, bytes revertReason); + error PreRuntimeValidationHookFailed(address module, uint32 entityId, bytes revertReason); error RequireUserOperationContext(); error RuntimeValidationFunctionMissing(bytes4 selector); - error RuntimeValidationFunctionReverted(address plugin, uint32 entityId, bytes revertReason); + error RuntimeValidationFunctionReverted(address module, uint32 entityId, bytes revertReason); error SelfCallRecursionDepthExceeded(); - error SignatureValidationInvalid(address plugin, uint32 entityId); - error UnexpectedAggregator(address plugin, uint32 entityId, address aggregator); + error SignatureValidationInvalid(address module, uint32 entityId); + error UnexpectedAggregator(address module, uint32 entityId, address aggregator); error UnrecognizedFunction(bytes4 selector); error ValidationFunctionMissing(bytes4 selector); - error ValidationDoesNotApply(bytes4 selector, address plugin, uint32 entityId, bool isGlobal); + error ValidationDoesNotApply(bytes4 selector, address module, uint32 entityId, bool isGlobal); error ValidationSignatureSegmentMissing(); error SignatureSegmentOutOfOrder(); // Wraps execution of a native function with runtime validation and hooks - // Used for upgradeTo, upgradeToAndCall, execute, executeBatch, installPlugin, uninstallPlugin + // Used for upgradeTo, upgradeToAndCall, execute, executeBatch, installModule, uninstallModule modifier wrapNativeFunction() { (PostExecToRun[] memory postPermissionHooks, PostExecToRun[] memory postExecHooks) = _checkPermittedCallerAndAssociatedHooks(); @@ -107,23 +107,23 @@ contract UpgradeableModularAccount is // EXTERNAL FUNCTIONS - /// @notice Initializes the account with a set of plugins - /// @param plugins The plugins to install - /// @param manifestHashes The manifest hashes of the plugins to install - /// @param pluginInstallDatas The plugin install datas of the plugins to install + /// @notice Initializes the account with a set of modules + /// @param modules The modules to install + /// @param manifestHashes The manifest hashes of the modules to install + /// @param moduleInstallDatas The module install datas of the modules to install function initialize( - address[] memory plugins, + address[] memory modules, bytes32[] memory manifestHashes, - bytes[] memory pluginInstallDatas + bytes[] memory moduleInstallDatas ) external initializer { - uint256 length = plugins.length; + uint256 length = modules.length; - if (length != manifestHashes.length || length != pluginInstallDatas.length) { + if (length != manifestHashes.length || length != moduleInstallDatas.length) { revert ArrayLengthMismatch(); } for (uint256 i = 0; i < length; ++i) { - _installPlugin(plugins[i], manifestHashes[i], pluginInstallDatas[i]); + _installModule(modules[i], manifestHashes[i], moduleInstallDatas[i]); } emit ModularAccountInitialized(_ENTRY_POINT); @@ -133,10 +133,10 @@ contract UpgradeableModularAccount is /// @notice Fallback function /// @dev We route calls to execution functions based on incoming msg.sig - /// @dev If there's no plugin associated with this function selector, revert + /// @dev If there's no module associated with this function selector, revert fallback(bytes calldata) external payable returns (bytes memory) { - address execPlugin = getAccountStorage().selectorData[msg.sig].plugin; - if (execPlugin == address(0)) { + address execModule = getAccountStorage().selectorData[msg.sig].module; + if (execModule == address(0)) { revert UnrecognizedFunction(msg.sig); } @@ -147,10 +147,10 @@ contract UpgradeableModularAccount is postExecHooks = _doPreHooks(getAccountStorage().selectorData[msg.sig].executionHooks, msg.data); // execute the function, bubbling up any reverts - (bool execSuccess, bytes memory execReturnData) = execPlugin.call(msg.data); + (bool execSuccess, bytes memory execReturnData) = execModule.call(msg.data); if (!execSuccess) { - // Bubble up revert reasons from plugins + // Bubble up revert reasons from modules assembly ("memory-safe") { revert(add(execReturnData, 32), mload(execReturnData)) } @@ -168,7 +168,7 @@ contract UpgradeableModularAccount is revert NotEntryPoint(); } - PluginEntity userOpValidationFunction = PluginEntity.wrap(bytes24(userOp.signature[:24])); + ModuleEntity userOpValidationFunction = ModuleEntity.wrap(bytes24(userOp.signature[:24])); PostExecToRun[] memory postPermissionHooks = _doPreHooks(getAccountStorage().validationData[userOpValidationFunction].permissionHooks, msg.data); @@ -221,7 +221,7 @@ contract UpgradeableModularAccount is returns (bytes memory) { // Revert if the provided `authorization` less than 21 bytes long, rather than right-padding. - PluginEntity runtimeValidationFunction = PluginEntity.wrap(bytes24(authorization[:24])); + ModuleEntity runtimeValidationFunction = ModuleEntity.wrap(bytes24(authorization[:24])); // Check if the runtime validation function is allowed to be called bool isGlobalValidation = uint8(authorization[24]) == 1; @@ -247,32 +247,32 @@ contract UpgradeableModularAccount is return returnData; } - /// @inheritdoc IPluginManager + /// @inheritdoc IModuleManager /// @notice May be validated by a global validation. - function installPlugin(address plugin, bytes32 manifestHash, bytes calldata pluginInstallData) + function installModule(address module, bytes32 manifestHash, bytes calldata moduleInstallData) external override wrapNativeFunction { - _installPlugin(plugin, manifestHash, pluginInstallData); + _installModule(module, manifestHash, moduleInstallData); } - /// @inheritdoc IPluginManager + /// @inheritdoc IModuleManager /// @notice May be validated by a global validation. - function uninstallPlugin(address plugin, bytes calldata config, bytes calldata pluginUninstallData) + function uninstallModule(address module, bytes calldata config, bytes calldata moduleUninstallData) external override wrapNativeFunction { - PluginManifest memory manifest; + ModuleManifest memory manifest; if (config.length > 0) { - manifest = abi.decode(config, (PluginManifest)); + manifest = abi.decode(config, (ModuleManifest)); } else { - manifest = IPlugin(plugin).pluginManifest(); + manifest = IModule(module).moduleManifest(); } - _uninstallPlugin(plugin, manifest, pluginUninstallData); + _uninstallModule(module, manifest, moduleUninstallData); } /// @notice Initializes the account with a validation function added to the global pool. @@ -290,7 +290,7 @@ contract UpgradeableModularAccount is emit ModularAccountInitialized(_ENTRY_POINT); } - /// @inheritdoc IPluginManager + /// @inheritdoc IModuleManager /// @notice May be validated by a global validation. function installValidation( ValidationConfig validationConfig, @@ -302,10 +302,10 @@ contract UpgradeableModularAccount is _installValidation(validationConfig, selectors, installData, preValidationHooks, permissionHooks); } - /// @inheritdoc IPluginManager + /// @inheritdoc IModuleManager /// @notice May be validated by a global validation. function uninstallValidation( - PluginEntity validationFunction, + ModuleEntity validationFunction, bytes calldata uninstallData, bytes calldata preValidationHookUninstallData, bytes calldata permissionHookUninstallData @@ -345,15 +345,15 @@ contract UpgradeableModularAccount is function isValidSignature(bytes32 hash, bytes calldata signature) public view override returns (bytes4) { AccountStorage storage _storage = getAccountStorage(); - PluginEntity sigValidation = PluginEntity.wrap(bytes24(signature)); + ModuleEntity sigValidation = ModuleEntity.wrap(bytes24(signature)); - (address plugin, uint32 entityId) = sigValidation.unpack(); + (address module, uint32 entityId) = sigValidation.unpack(); if (!_storage.validationData[sigValidation].isSignatureValidation) { - revert SignatureValidationInvalid(plugin, entityId); + revert SignatureValidationInvalid(module, entityId); } if ( - IValidation(plugin).validateSignature(address(this), entityId, msg.sender, hash, signature[24:]) + IValidation(module).validateSignature(address(this), entityId, msg.sender, hash, signature[24:]) == _1271_MAGIC_VALUE ) { return _1271_MAGIC_VALUE; @@ -381,7 +381,7 @@ contract UpgradeableModularAccount is } // Revert if the provided `authorization` less than 21 bytes long, rather than right-padding. - PluginEntity userOpValidationFunction = PluginEntity.wrap(bytes24(userOp.signature[:24])); + ModuleEntity userOpValidationFunction = ModuleEntity.wrap(bytes24(userOp.signature[:24])); bool isGlobalValidation = uint8(userOp.signature[24]) == 1; _checkIfValidationAppliesCallData(userOp.callData, userOpValidationFunction, isGlobalValidation); @@ -402,7 +402,7 @@ contract UpgradeableModularAccount is // To support gas estimation, we don't fail early when the failure is caused by a signature failure function _doUserOpValidation( - PluginEntity userOpValidationFunction, + ModuleEntity userOpValidationFunction, PackedUserOperation memory userOp, bytes calldata signature, bytes32 userOpHash @@ -414,7 +414,7 @@ contract UpgradeableModularAccount is uint256 validationRes; // Do preUserOpValidation hooks - PluginEntity[] memory preUserOpValidationHooks = + ModuleEntity[] memory preUserOpValidationHooks = getAccountStorage().validationData[userOpValidationFunction].preValidationHooks; for (uint256 i = 0; i < preUserOpValidationHooks.length; ++i) { @@ -438,13 +438,13 @@ contract UpgradeableModularAccount is userOp.signature = ""; } - (address plugin, uint32 entityId) = preUserOpValidationHooks[i].unpack(); + (address module, uint32 entityId) = preUserOpValidationHooks[i].unpack(); uint256 currentValidationRes = - IValidationHook(plugin).preUserOpValidationHook(entityId, userOp, userOpHash); + IValidationHook(module).preUserOpValidationHook(entityId, userOp, userOpHash); if (uint160(currentValidationRes) > 1) { // If the aggregator is not 0 or 1, it is an unexpected value - revert UnexpectedAggregator(plugin, entityId, address(uint160(currentValidationRes))); + revert UnexpectedAggregator(module, entityId, address(uint160(currentValidationRes))); } validationRes = _coalescePreValidation(validationRes, currentValidationRes); } @@ -457,8 +457,8 @@ contract UpgradeableModularAccount is userOp.signature = signatureSegment.getBody(); - (address plugin, uint32 entityId) = userOpValidationFunction.unpack(); - uint256 currentValidationRes = IValidation(plugin).validateUserOp(entityId, userOp, userOpHash); + (address module, uint32 entityId) = userOpValidationFunction.unpack(); + uint256 currentValidationRes = IValidation(module).validateUserOp(entityId, userOp, userOpHash); if (preUserOpValidationHooks.length != 0) { // If we have other validation data we need to coalesce with @@ -472,7 +472,7 @@ contract UpgradeableModularAccount is } function _doRuntimeValidation( - PluginEntity runtimeValidationFunction, + ModuleEntity runtimeValidationFunction, bytes calldata callData, bytes calldata authorizationData ) internal { @@ -481,7 +481,7 @@ contract UpgradeableModularAccount is (authSegment, authorizationData) = authorizationData.getNextSegment(); // run all preRuntimeValidation hooks - PluginEntity[] memory preRuntimeValidationHooks = + ModuleEntity[] memory preRuntimeValidationHooks = getAccountStorage().validationData[runtimeValidationFunction].preValidationHooks; for (uint256 i = 0; i < preRuntimeValidationHooks.length; ++i) { @@ -511,16 +511,16 @@ contract UpgradeableModularAccount is revert ValidationSignatureSegmentMissing(); } - (address plugin, uint32 entityId) = runtimeValidationFunction.unpack(); + (address module, uint32 entityId) = runtimeValidationFunction.unpack(); - try IValidation(plugin).validateRuntime( + try IValidation(module).validateRuntime( address(this), entityId, msg.sender, msg.value, callData, authSegment.getBody() ) // forgefmt: disable-start // solhint-disable-next-line no-empty-blocks {} catch (bytes memory revertReason) { // forgefmt: disable-end - revert RuntimeValidationFunctionReverted(plugin, entityId, revertReason); + revert RuntimeValidationFunctionReverted(module, entityId, revertReason); } } @@ -536,7 +536,7 @@ contract UpgradeableModularAccount is // be sure that the set of hooks to run will not be affected by state changes mid-execution. for (uint256 i = 0; i < hooksLength; ++i) { bytes32 key = executionHooks.at(i); - (PluginEntity hookFunction,, bool isPostHook) = toExecutionHook(key); + (ModuleEntity hookFunction,, bool isPostHook) = toExecutionHook(key); if (isPostHook) { postHooksToRun[i].postExecHook = hookFunction; } @@ -546,7 +546,7 @@ contract UpgradeableModularAccount is // exists. for (uint256 i = 0; i < hooksLength; ++i) { bytes32 key = executionHooks.at(i); - (PluginEntity hookFunction, bool isPreHook, bool isPostHook) = toExecutionHook(key); + (ModuleEntity hookFunction, bool isPreHook, bool isPostHook) = toExecutionHook(key); if (isPreHook) { bytes memory preExecHookReturnData; @@ -561,18 +561,18 @@ contract UpgradeableModularAccount is } } - function _runPreExecHook(PluginEntity preExecHook, bytes memory data) + function _runPreExecHook(ModuleEntity preExecHook, bytes memory data) internal returns (bytes memory preExecHookReturnData) { - (address plugin, uint32 entityId) = preExecHook.unpack(); - try IExecutionHook(plugin).preExecutionHook(entityId, msg.sender, msg.value, data) returns ( + (address module, uint32 entityId) = preExecHook.unpack(); + try IExecutionHook(module).preExecutionHook(entityId, msg.sender, msg.value, data) returns ( bytes memory returnData ) { preExecHookReturnData = returnData; } catch (bytes memory revertReason) { - // TODO: same issue with EP0.6 - we can't do bytes4 error codes in plugins - revert PreExecHookReverted(plugin, entityId, revertReason); + // TODO: same issue with EP0.6 - we can't do bytes4 error codes in modules + revert PreExecHookReverted(module, entityId, revertReason); } } @@ -590,29 +590,29 @@ contract UpgradeableModularAccount is continue; } - (address plugin, uint32 entityId) = postHookToRun.postExecHook.unpack(); + (address module, uint32 entityId) = postHookToRun.postExecHook.unpack(); // solhint-disable-next-line no-empty-blocks - try IExecutionHook(plugin).postExecutionHook(entityId, postHookToRun.preExecHookReturnData) {} + try IExecutionHook(module).postExecutionHook(entityId, postHookToRun.preExecHookReturnData) {} catch (bytes memory revertReason) { - revert PostExecHookReverted(plugin, entityId, revertReason); + revert PostExecHookReverted(module, entityId, revertReason); } } } function _doPreRuntimeValidationHook( - PluginEntity validationHook, + ModuleEntity validationHook, bytes memory callData, bytes memory currentAuthData ) internal { - (address hookPlugin, uint32 hookEntityId) = validationHook.unpack(); - try IValidationHook(hookPlugin).preRuntimeValidationHook( + (address hookModule, uint32 hookEntityId) = validationHook.unpack(); + try IValidationHook(hookModule).preRuntimeValidationHook( hookEntityId, msg.sender, msg.value, callData, currentAuthData ) // forgefmt: disable-start // solhint-disable-next-line no-empty-blocks {} catch (bytes memory revertReason) { // forgefmt: disable-end - revert PreRuntimeValidationHookFailed(hookPlugin, hookEntityId, revertReason); + revert PreRuntimeValidationHookFailed(hookModule, hookEntityId, revertReason); } } @@ -645,14 +645,14 @@ contract UpgradeableModularAccount is return (new PostExecToRun[](0), new PostExecToRun[](0)); } - PluginEntity directCallValidationKey = PluginEntityLib.pack(msg.sender, _SELF_PERMIT_VALIDATION_FUNCTIONID); + ModuleEntity directCallValidationKey = ModuleEntityLib.pack(msg.sender, _SELF_PERMIT_VALIDATION_FUNCTIONID); _checkIfValidationAppliesCallData(msg.data, directCallValidationKey, false); // Direct call is allowed, run associated permission & validation hooks // Validation hooks - PluginEntity[] memory preRuntimeValidationHooks = + ModuleEntity[] memory preRuntimeValidationHooks = _storage.validationData[directCallValidationKey].preValidationHooks; uint256 hookLen = preRuntimeValidationHooks.length; @@ -673,7 +673,7 @@ contract UpgradeableModularAccount is function _checkIfValidationAppliesCallData( bytes calldata callData, - PluginEntity validationFunction, + ModuleEntity validationFunction, bool isGlobal ) internal view { bytes4 outerSelector = bytes4(callData[:4]); @@ -712,7 +712,7 @@ contract UpgradeableModularAccount is // To prevent arbitrarily-deep recursive checking, we limit the depth of self-calls to one // for the purposes of batching. // This means that all self-calls must occur at the top level of the batch. - // Note that plugins of other contracts using `executeWithAuthorization` may still + // Note that modules of other contracts using `executeWithAuthorization` may still // independently call into this account with a different validation function, allowing // composition of multiple batches. revert SelfCallRecursionDepthExceeded(); @@ -727,7 +727,7 @@ contract UpgradeableModularAccount is function _globalValidationAllowed(bytes4 selector) internal view returns (bool) { if ( selector == this.execute.selector || selector == this.executeBatch.selector - || selector == this.installPlugin.selector || selector == this.uninstallPlugin.selector + || selector == this.installModule.selector || selector == this.uninstallModule.selector || selector == this.installValidation.selector || selector == this.uninstallValidation.selector || selector == this.upgradeToAndCall.selector ) { @@ -737,7 +737,7 @@ contract UpgradeableModularAccount is return getAccountStorage().selectorData[selector].allowGlobalValidation; } - function _checkIfValidationAppliesSelector(bytes4 selector, PluginEntity validationFunction, bool isGlobal) + function _checkIfValidationAppliesSelector(bytes4 selector, ModuleEntity validationFunction, bool isGlobal) internal view { diff --git a/src/helpers/KnownSelectors.sol b/src/helpers/KnownSelectors.sol index ae91f3b7..811f2f5d 100644 --- a/src/helpers/KnownSelectors.sol +++ b/src/helpers/KnownSelectors.sol @@ -9,8 +9,8 @@ import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import {IAccountLoupe} from "../interfaces/IAccountLoupe.sol"; import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; -import {IPlugin} from "../interfaces/IPlugin.sol"; -import {IPluginManager} from "../interfaces/IPluginManager.sol"; +import {IModule} from "../interfaces/IModule.sol"; +import {IModuleManager} from "../interfaces/IModuleManager.sol"; import {IStandardExecutor} from "../interfaces/IStandardExecutor.sol"; import {IValidation} from "../interfaces/IValidation.sol"; import {IValidationHook} from "../interfaces/IValidationHook.sol"; @@ -22,8 +22,8 @@ library KnownSelectors { return // check against IAccount methods selector == IAccount.validateUserOp.selector - // check against IPluginManager methods - || selector == IPluginManager.installPlugin.selector || selector == IPluginManager.uninstallPlugin.selector + // check against IModuleManager methods + || selector == IModuleManager.installModule.selector || selector == IModuleManager.uninstallModule.selector // check against IERC165 methods || selector == IERC165.supportsInterface.selector // check against UUPSUpgradeable methods @@ -36,7 +36,7 @@ library KnownSelectors { || selector == IAccountLoupe.getExecutionFunctionHandler.selector || selector == IAccountLoupe.getSelectors.selector || selector == IAccountLoupe.getExecutionHooks.selector || selector == IAccountLoupe.getPreValidationHooks.selector - || selector == IAccountLoupe.getInstalledPlugins.selector; + || selector == IAccountLoupe.getInstalledModules.selector; } function isErc4337Function(bytes4 selector) internal pure returns (bool) { @@ -46,9 +46,9 @@ library KnownSelectors { || selector == IPaymaster.validatePaymasterUserOp.selector || selector == IPaymaster.postOp.selector; } - function isIPluginFunction(bytes4 selector) internal pure returns (bool) { - return selector == IPlugin.onInstall.selector || selector == IPlugin.onUninstall.selector - || selector == IPlugin.pluginManifest.selector || selector == IPlugin.pluginMetadata.selector + function isIModuleFunction(bytes4 selector) internal pure returns (bool) { + return selector == IModule.onInstall.selector || selector == IModule.onUninstall.selector + || selector == IModule.moduleManifest.selector || selector == IModule.moduleMetadata.selector || selector == IExecutionHook.preExecutionHook.selector || selector == IExecutionHook.postExecutionHook.selector || selector == IValidation.validateUserOp.selector || selector == IValidation.validateRuntime.selector || selector == IValidation.validateSignature.selector diff --git a/src/helpers/ModuleEntityLib.sol b/src/helpers/ModuleEntityLib.sol new file mode 100644 index 00000000..d8473a5b --- /dev/null +++ b/src/helpers/ModuleEntityLib.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.25; + +import {ModuleEntity} from "../interfaces/IModuleManager.sol"; + +library ModuleEntityLib { + // Magic value for hooks that should always revert. + ModuleEntity internal constant _PRE_HOOK_ALWAYS_DENY = ModuleEntity.wrap(bytes24(uint192(2))); + + function pack(address addr, uint32 entityId) internal pure returns (ModuleEntity) { + return ModuleEntity.wrap(bytes24(bytes20(addr)) | bytes24(uint192(entityId))); + } + + function unpack(ModuleEntity fr) internal pure returns (address addr, uint32 entityId) { + bytes24 underlying = ModuleEntity.unwrap(fr); + addr = address(bytes20(underlying)); + entityId = uint32(bytes4(underlying << 160)); + } + + function isEmpty(ModuleEntity fr) internal pure returns (bool) { + return ModuleEntity.unwrap(fr) == bytes24(0); + } + + function notEmpty(ModuleEntity fr) internal pure returns (bool) { + return ModuleEntity.unwrap(fr) != 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); + } +} diff --git a/src/helpers/PluginEntityLib.sol b/src/helpers/PluginEntityLib.sol deleted file mode 100644 index 423b7f70..00000000 --- a/src/helpers/PluginEntityLib.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.25; - -import {PluginEntity} from "../interfaces/IPluginManager.sol"; - -library PluginEntityLib { - // Magic value for hooks that should always revert. - PluginEntity internal constant _PRE_HOOK_ALWAYS_DENY = PluginEntity.wrap(bytes24(uint192(2))); - - function pack(address addr, uint32 entityId) internal pure returns (PluginEntity) { - return PluginEntity.wrap(bytes24(bytes20(addr)) | bytes24(uint192(entityId))); - } - - function unpack(PluginEntity fr) internal pure returns (address addr, uint32 entityId) { - bytes24 underlying = PluginEntity.unwrap(fr); - addr = address(bytes20(underlying)); - entityId = uint32(bytes4(underlying << 160)); - } - - function isEmpty(PluginEntity fr) internal pure returns (bool) { - return PluginEntity.unwrap(fr) == bytes24(0); - } - - function notEmpty(PluginEntity fr) internal pure returns (bool) { - return PluginEntity.unwrap(fr) != bytes24(0); - } - - function eq(PluginEntity a, PluginEntity b) internal pure returns (bool) { - return PluginEntity.unwrap(a) == PluginEntity.unwrap(b); - } - - function notEq(PluginEntity a, PluginEntity b) internal pure returns (bool) { - return PluginEntity.unwrap(a) != PluginEntity.unwrap(b); - } -} diff --git a/src/helpers/ValidationConfigLib.sol b/src/helpers/ValidationConfigLib.sol index 6d27b907..1c127c3b 100644 --- a/src/helpers/ValidationConfigLib.sol +++ b/src/helpers/ValidationConfigLib.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.25; -import {PluginEntity, ValidationConfig} from "../interfaces/IPluginManager.sol"; +import {ModuleEntity, ValidationConfig} from "../interfaces/IModuleManager.sol"; // Validation config is a packed representation of a validation function and flags for its configuration. // Layout: @@ -12,14 +12,14 @@ import {PluginEntity, ValidationConfig} from "../interfaces/IPluginManager.sol"; // 0x____________________________________________________000000000000 // unused library ValidationConfigLib { - function pack(PluginEntity _validationFunction, bool _isGlobal, bool _isSignatureValidation) + function pack(ModuleEntity _validationFunction, bool _isGlobal, bool _isSignatureValidation) internal pure returns (ValidationConfig) { return ValidationConfig.wrap( bytes26( - bytes26(PluginEntity.unwrap(_validationFunction)) + bytes26(ModuleEntity.unwrap(_validationFunction)) // isGlobal flag stored in the 25th byte | bytes26(bytes32(_isGlobal ? uint256(1) << 56 : 0)) // isSignatureValidation flag stored in the 26th byte @@ -28,15 +28,15 @@ library ValidationConfigLib { ); } - function pack(address _plugin, uint32 _entityId, bool _isGlobal, bool _isSignatureValidation) + function pack(address _module, uint32 _entityId, bool _isGlobal, bool _isSignatureValidation) internal pure returns (ValidationConfig) { return ValidationConfig.wrap( bytes26( - // plugin address stored in the first 20 bytes - bytes26(bytes20(_plugin)) + // module address stored in the first 20 bytes + bytes26(bytes20(_module)) // entityId stored in the 21st - 24th byte | bytes26(bytes24(uint192(_entityId))) // isGlobal flag stored in the 25th byte @@ -50,10 +50,10 @@ library ValidationConfigLib { function unpackUnderlying(ValidationConfig config) internal pure - returns (address _plugin, uint32 _entityId, bool _isGlobal, bool _isSignatureValidation) + returns (address _module, uint32 _entityId, bool _isGlobal, bool _isSignatureValidation) { bytes26 configBytes = ValidationConfig.unwrap(config); - _plugin = address(bytes20(configBytes)); + _module = address(bytes20(configBytes)); _entityId = uint32(bytes4(configBytes << 160)); _isGlobal = uint8(configBytes[24]) == 1; _isSignatureValidation = uint8(configBytes[25]) == 1; @@ -62,15 +62,15 @@ library ValidationConfigLib { function unpack(ValidationConfig config) internal pure - returns (PluginEntity _validationFunction, bool _isGlobal, bool _isSignatureValidation) + returns (ModuleEntity _validationFunction, bool _isGlobal, bool _isSignatureValidation) { bytes26 configBytes = ValidationConfig.unwrap(config); - _validationFunction = PluginEntity.wrap(bytes24(configBytes)); + _validationFunction = ModuleEntity.wrap(bytes24(configBytes)); _isGlobal = uint8(configBytes[24]) == 1; _isSignatureValidation = uint8(configBytes[25]) == 1; } - function plugin(ValidationConfig config) internal pure returns (address) { + function module(ValidationConfig config) internal pure returns (address) { return address(bytes20(ValidationConfig.unwrap(config))); } @@ -78,8 +78,8 @@ library ValidationConfigLib { return uint32(bytes4(ValidationConfig.unwrap(config) << 160)); } - function pluginEntity(ValidationConfig config) internal pure returns (PluginEntity) { - return PluginEntity.wrap(bytes24(ValidationConfig.unwrap(config))); + function moduleEntity(ValidationConfig config) internal pure returns (ModuleEntity) { + return ModuleEntity.wrap(bytes24(ValidationConfig.unwrap(config))); } function isGlobal(ValidationConfig config) internal pure returns (bool) { diff --git a/src/interfaces/IAccountLoupe.sol b/src/interfaces/IAccountLoupe.sol index 3e7d9f11..01cca512 100644 --- a/src/interfaces/IAccountLoupe.sol +++ b/src/interfaces/IAccountLoupe.sol @@ -1,27 +1,27 @@ // SPDX-License-Identifier: CC0-1.0 pragma solidity ^0.8.25; -import {PluginEntity} from "../interfaces/IPluginManager.sol"; +import {ModuleEntity} from "../interfaces/IModuleManager.sol"; /// @notice Pre and post hooks for a given selector. /// @dev It's possible for one of either `preExecHook` or `postExecHook` to be empty. struct ExecutionHook { - PluginEntity hookFunction; + ModuleEntity hookFunction; bool isPreHook; bool isPostHook; } interface IAccountLoupe { - /// @notice Get the plugin address for a selector. - /// @dev If the selector is a native function, the plugin address will be the address of the account. + /// @notice Get the module address 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 configuration for. - /// @return plugin The plugin address for this selector. - function getExecutionFunctionHandler(bytes4 selector) external view returns (address plugin); + /// @return module The module address for this selector. + function getExecutionFunctionHandler(bytes4 selector) external view returns (address module); /// @notice Get the selectors for a validation function. /// @param validationFunction The validation function to get the selectors for. /// @return The allowed selectors for this validation function. - function getSelectors(PluginEntity validationFunction) external view returns (bytes4[] memory); + function getSelectors(ModuleEntity validationFunction) external view returns (bytes4[] memory); /// @notice Get the pre and post execution hooks for a selector. /// @param selector The selector to get the hooks for. @@ -31,17 +31,17 @@ interface IAccountLoupe { /// @notice Get the pre and post execution hooks for a validation function. /// @param validationFunction The validation function to get the hooks for. /// @return The pre and post execution hooks for this validation function. - function getPermissionHooks(PluginEntity validationFunction) external view returns (ExecutionHook[] memory); + function getPermissionHooks(ModuleEntity validationFunction) external view returns (ExecutionHook[] memory); /// @notice Get the pre user op and runtime validation hooks associated with a selector. /// @param validationFunction The validation function to get the hooks for. /// @return preValidationHooks The pre validation hooks for this selector. - function getPreValidationHooks(PluginEntity validationFunction) + function getPreValidationHooks(ModuleEntity validationFunction) external view - returns (PluginEntity[] memory preValidationHooks); + returns (ModuleEntity[] memory preValidationHooks); - /// @notice Get an array of all installed plugins. - /// @return The addresses of all installed plugins. - function getInstalledPlugins() external view returns (address[] memory); + /// @notice Get an array of all installed modules. + /// @return The addresses of all installed modules. + function getInstalledModules() external view returns (address[] memory); } diff --git a/src/interfaces/IExecutionHook.sol b/src/interfaces/IExecutionHook.sol index 9cb16482..ad9e52b6 100644 --- a/src/interfaces/IExecutionHook.sol +++ b/src/interfaces/IExecutionHook.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: CC0-1.0 pragma solidity ^0.8.25; -import {IPlugin} from "./IPlugin.sol"; +import {IModule} from "./IModule.sol"; -interface IExecutionHook is IPlugin { +interface IExecutionHook 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 diff --git a/src/interfaces/IPlugin.sol b/src/interfaces/IModule.sol similarity index 66% rename from src/interfaces/IPlugin.sol rename to src/interfaces/IModule.sol index 824a1ddf..8851acaf 100644 --- a/src/interfaces/IPlugin.sol +++ b/src/interfaces/IModule.sol @@ -34,53 +34,53 @@ struct SelectorPermission { string permissionDescription; } -/// @dev A struct holding fields to describe the plugin in a purely view context. Intended for front end clients. -struct PluginMetadata { - // A human-readable name of the plugin. +/// @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 plugin, following the semantic versioning scheme. + // 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 plugin. + // that created this module. string author; // String desciptions of the relative sensitivity of specific functions. The selectors MUST be selectors for - // functions implemented by this plugin. + // functions implemented by this module. SelectorPermission[] permissionDescriptors; - // A list of all ERC-7715 permission strings that the plugin could possibly use + // A list of all ERC-7715 permission strings that the module could possibly use string[] permissionRequest; } -/// @dev A struct describing how the plugin should be installed on a modular account. -struct PluginManifest { - // Execution functions defined in this plugin to be installed on the MSCA. +/// @dev A struct describing how the module should be installed on a modular account. +struct ModuleManifest { + // Execution functions defined in this module to be installed on the MSCA. ManifestExecutionFunction[] executionFunctions; ManifestValidation[] validationFunctions; ManifestExecutionHook[] executionHooks; // List of ERC-165 interface IDs to add to account to support introspection checks. This MUST NOT include - // IPlugin's interface ID. + // IModule's interface ID. bytes4[] interfaceIds; } -interface IPlugin is IERC165 { - /// @notice Initialize plugin data for the modular account. - /// @dev Called by the modular account during `installPlugin`. - /// @param data Optional bytes array to be decoded and used by the plugin to setup initial plugin data for the +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 plugin data for the modular account. - /// @dev Called by the modular account during `uninstallPlugin`. - /// @param data Optional bytes array to be decoded and used by the plugin to clear plugin data for the modular + /// @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 Describe the contents and intended configuration of the plugin. + /// @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 plugin. - function pluginManifest() external pure returns (PluginManifest memory); + /// @return A manifest describing the contents and intended configuration of the module. + function moduleManifest() external pure returns (ModuleManifest memory); - /// @notice Describe the metadata of the plugin. + /// @notice Describe the metadata of the module. /// @dev This metadata MUST stay constant over time. - /// @return A metadata struct describing the plugin. - function pluginMetadata() external pure returns (PluginMetadata memory); + /// @return A metadata struct describing the module. + function moduleMetadata() external pure returns (ModuleMetadata memory); } diff --git a/src/interfaces/IPluginManager.sol b/src/interfaces/IModuleManager.sol similarity index 63% rename from src/interfaces/IPluginManager.sol rename to src/interfaces/IModuleManager.sol index d2df72b5..eb2db1ea 100644 --- a/src/interfaces/IPluginManager.sol +++ b/src/interfaces/IModuleManager.sol @@ -1,21 +1,21 @@ // SPDX-License-Identifier: CC0-1.0 pragma solidity ^0.8.25; -type PluginEntity is bytes24; +type ModuleEntity is bytes24; type ValidationConfig is bytes26; -interface IPluginManager { - event PluginInstalled(address indexed plugin, bytes32 manifestHash); +interface IModuleManager { + event ModuleInstalled(address indexed module, bytes32 manifestHash); - event PluginUninstalled(address indexed plugin, bool indexed onUninstallSucceeded); + event ModuleUninstalled(address indexed module, bool indexed onUninstallSucceeded); - /// @notice Install a plugin to the modular account. - /// @param plugin The plugin to install. - /// @param manifestHash The hash of the plugin manifest. - /// @param pluginInstallData Optional data to be decoded and used by the plugin to setup initial plugin data + /// @notice Install a module to the modular account. + /// @param module The module to install. + /// @param manifestHash The hash of the module manifest. + /// @param moduleInstallData Optional data to be decoded and used by the module to setup initial module data /// for the modular account. - function installPlugin(address plugin, bytes32 manifestHash, bytes calldata pluginInstallData) external; + function installModule(address module, bytes32 manifestHash, bytes calldata moduleInstallData) external; /// @notice Temporary install function - pending a different user-supplied install config & manifest validation /// path. @@ -25,7 +25,7 @@ interface IPluginManager { /// @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 decoded and used by the plugin to setup initial plugin state. + /// @param installData Optional data to be decoded and used by the module to setup initial module state. /// @param preValidationHooks Optional pre-validation hooks to install for the validation function. /// @param permissionHooks Optional permission hooks to install for the validation function. function installValidation( @@ -39,23 +39,23 @@ interface IPluginManager { /// @notice Uninstall a validation function from a set of execution selectors. /// TODO: remove or update. /// @param validationFunction The validation function to uninstall. - /// @param uninstallData Optional data to be decoded and used by the plugin to clear plugin data for the + /// @param uninstallData Optional data to be decoded and used by the module to clear module data for the /// account. - /// @param preValidationHookUninstallData Optional data to be decoded and used by the plugin to clear account + /// @param preValidationHookUninstallData Optional data to be decoded and used by the module to clear account /// data - /// @param permissionHookUninstallData Optional data to be decoded and used by the plugin to clear account data + /// @param permissionHookUninstallData Optional data to be decoded and used by the module to clear account data function uninstallValidation( - PluginEntity validationFunction, + ModuleEntity validationFunction, bytes calldata uninstallData, bytes calldata preValidationHookUninstallData, bytes calldata permissionHookUninstallData ) external; - /// @notice Uninstall a plugin from the modular account. - /// @param plugin The plugin to uninstall. + /// @notice Uninstall a module from the modular account. + /// @param module The module to uninstall. /// @param config An optional, implementation-specific field that accounts may use to ensure consistency /// guarantees. - /// @param pluginUninstallData Optional data to be decoded and used by the plugin to clear plugin data for the + /// @param moduleUninstallData Optional data to be decoded and used by the module to clear module data for the /// modular account. - function uninstallPlugin(address plugin, bytes calldata config, bytes calldata pluginUninstallData) external; + function uninstallModule(address module, bytes calldata config, bytes calldata moduleUninstallData) external; } diff --git a/src/interfaces/IStandardExecutor.sol b/src/interfaces/IStandardExecutor.sol index fbeb89c4..db9a7c19 100644 --- a/src/interfaces/IStandardExecutor.sol +++ b/src/interfaces/IStandardExecutor.sol @@ -12,7 +12,6 @@ struct Call { interface IStandardExecutor { /// @notice Standard execute method. - /// @dev If the target is a plugin, the call SHOULD revert. /// @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. @@ -20,7 +19,7 @@ interface IStandardExecutor { function execute(address target, uint256 value, bytes calldata data) external payable returns (bytes memory); /// @notice Standard executeBatch method. - /// @dev If the target is a plugin, the call SHOULD revert. If any of the calls revert, the entire batch MUST + /// @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. diff --git a/src/interfaces/IValidation.sol b/src/interfaces/IValidation.sol index 471cd2c1..4f8fbbb8 100644 --- a/src/interfaces/IValidation.sol +++ b/src/interfaces/IValidation.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.25; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; -import {IPlugin} from "./IPlugin.sol"; +import {IModule} from "./IModule.sol"; -interface IValidation is IPlugin { +interface IValidation 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. diff --git a/src/interfaces/IValidationHook.sol b/src/interfaces/IValidationHook.sol index dd7e2500..1a8ee589 100644 --- a/src/interfaces/IValidationHook.sol +++ b/src/interfaces/IValidationHook.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.25; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; -import {IPlugin} from "./IPlugin.sol"; +import {IModule} from "./IModule.sol"; -interface IValidationHook is IPlugin { +interface IValidationHook 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 diff --git a/src/plugins/BasePlugin.sol b/src/modules/BaseModule.sol similarity index 83% rename from src/plugins/BasePlugin.sol rename to src/modules/BaseModule.sol index 40dcd47b..b6d785c5 100644 --- a/src/plugins/BasePlugin.sol +++ b/src/modules/BaseModule.sol @@ -5,13 +5,13 @@ import {IAccountExecute} from "@eth-infinitism/account-abstraction/interfaces/IA import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {ERC165, IERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; -import {IPlugin} from "../interfaces/IPlugin.sol"; +import {IModule} from "../interfaces/IModule.sol"; -/// @title Base contract for plugins -/// @dev Implements ERC-165 to support IPlugin's interface, which is a requirement -/// for plugin installation. This also ensures that plugin interactions cannot +/// @title Base contract for modules +/// @dev Implements ERC-165 to support IModule's interface, which is a requirement +/// for module installation. This also ensures that module interactions cannot /// happen via the standard execution funtions `execute` and `executeBatch`. -abstract contract BasePlugin is ERC165, IPlugin { +abstract contract BaseModule is ERC165, IModule { error NotImplemented(); /// @dev Returns true if this contract implements the interface defined by @@ -21,13 +21,13 @@ abstract contract BasePlugin is ERC165, IPlugin { /// /// This function call must use less than 30 000 gas. /// - /// Supporting the IPlugin interface is a requirement for plugin installation. This is also used + /// Supporting the IModule interface is a requirement for module installation. This is also used /// by the modular account to prevent standard execution functions `execute` and `executeBatch` from - /// making calls to plugins. + /// making calls to modules. /// @param interfaceId The interface ID to check for support. /// @return True if the contract supports `interfaceId`. function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { - return interfaceId == type(IPlugin).interfaceId || super.supportsInterface(interfaceId); + return interfaceId == type(IModule).interfaceId || super.supportsInterface(interfaceId); } function _getSelectorAndCalldata(bytes calldata data) internal pure returns (bytes4, bytes memory) { diff --git a/src/plugins/ERC20TokenLimitPlugin.sol b/src/modules/ERC20TokenLimitModule.sol similarity index 85% rename from src/plugins/ERC20TokenLimitPlugin.sol rename to src/modules/ERC20TokenLimitModule.sol index 28ba4e04..85d90585 100644 --- a/src/plugins/ERC20TokenLimitPlugin.sol +++ b/src/modules/ERC20TokenLimitModule.sol @@ -13,20 +13,20 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; -import {PluginManifest, PluginMetadata} from "../interfaces/IPlugin.sol"; -import {IPlugin} from "../interfaces/IPlugin.sol"; +import {ModuleManifest, ModuleMetadata} from "../interfaces/IModule.sol"; +import {IModule} from "../interfaces/IModule.sol"; import {Call, IStandardExecutor} from "../interfaces/IStandardExecutor.sol"; -import {BasePlugin, IERC165} from "./BasePlugin.sol"; +import {BaseModule, IERC165} from "./BaseModule.sol"; -/// @title ERC20 Token Limit Plugin +/// @title ERC20 Token Limit Module /// @author ERC-6900 Authors -/// @notice This plugin supports an ERC20 token spend limit. This should be combined with a contract whitelist -/// plugin to make sure that token transfers not tracked by the plugin don't happen. -/// Note: this plugin is opinionated on what selectors can be called for token contracts to guard against weird +/// @notice This module supports an ERC20 token spend limit. This should be combined with a contract whitelist +/// module to make sure that token transfers not tracked by the module don't happen. +/// Note: this module is opinionated on what selectors can be called for token contracts to guard against weird /// edge cases like DAI. You wouldn't be able to use uni v2 pairs directly as the pair contract is also the LP /// token contract -contract ERC20TokenLimitPlugin is BasePlugin, IExecutionHook { +contract ERC20TokenLimitModule is BaseModule, IExecutionHook { using UserOperationLib for PackedUserOperation; using EnumerableSet for EnumerableSet.AddressSet; using AssociatedLinkedListSetLib for AssociatedLinkedListSet; @@ -36,7 +36,7 @@ contract ERC20TokenLimitPlugin is BasePlugin, IExecutionHook { uint256[] limits; } - string internal constant _NAME = "ERC20 Token Limit Plugin"; + string internal constant _NAME = "ERC20 Token Limit Module"; string internal constant _VERSION = "1.0.0"; string internal constant _AUTHOR = "ERC-6900 Authors"; @@ -77,7 +77,7 @@ contract ERC20TokenLimitPlugin is BasePlugin, IExecutionHook { return ""; } - /// @inheritdoc IPlugin + /// @inheritdoc IModule function onInstall(bytes calldata data) external override { (uint32 startEntityId, ERC20SpendLimit[] memory spendLimits) = abi.decode(data, (uint32, ERC20SpendLimit[])); @@ -94,7 +94,7 @@ contract ERC20TokenLimitPlugin is BasePlugin, IExecutionHook { } } - /// @inheritdoc IPlugin + /// @inheritdoc IModule function onUninstall(bytes calldata data) external override { (address token, uint32 entityId) = abi.decode(data, (address, uint32)); delete limits[entityId][token][msg.sender]; @@ -114,13 +114,13 @@ contract ERC20TokenLimitPlugin is BasePlugin, IExecutionHook { revert NotImplemented(); } - /// @inheritdoc IPlugin + /// @inheritdoc IModule // solhint-disable-next-line no-empty-blocks - function pluginManifest() external pure override returns (PluginManifest memory) {} + function moduleManifest() external pure override returns (ModuleManifest memory) {} - /// @inheritdoc IPlugin - function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { - PluginMetadata memory metadata; + /// @inheritdoc IModule + function moduleMetadata() external pure virtual override returns (ModuleMetadata memory) { + ModuleMetadata memory metadata; metadata.name = _NAME; metadata.version = _VERSION; metadata.author = _AUTHOR; @@ -130,8 +130,8 @@ contract ERC20TokenLimitPlugin is BasePlugin, IExecutionHook { return metadata; } - /// @inheritdoc BasePlugin - function supportsInterface(bytes4 interfaceId) public view override(BasePlugin, IERC165) returns (bool) { + /// @inheritdoc BaseModule + function supportsInterface(bytes4 interfaceId) public view override(BaseModule, IERC165) returns (bool) { return super.supportsInterface(interfaceId); } diff --git a/src/plugins/NativeTokenLimitPlugin.sol b/src/modules/NativeTokenLimitModule.sol similarity index 89% rename from src/plugins/NativeTokenLimitPlugin.sol rename to src/modules/NativeTokenLimitModule.sol index 37f4399e..f09994be 100644 --- a/src/plugins/NativeTokenLimitPlugin.sol +++ b/src/modules/NativeTokenLimitModule.sol @@ -6,20 +6,20 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; -import {PluginManifest, PluginMetadata} from "../interfaces/IPlugin.sol"; -import {IPlugin} from "../interfaces/IPlugin.sol"; +import {ModuleManifest, ModuleMetadata} from "../interfaces/IModule.sol"; +import {IModule} from "../interfaces/IModule.sol"; import {Call, IStandardExecutor} from "../interfaces/IStandardExecutor.sol"; import {IValidationHook} from "../interfaces/IValidationHook.sol"; -import {BasePlugin, IERC165} from "./BasePlugin.sol"; +import {BaseModule, IERC165} from "./BaseModule.sol"; -/// @title Native Token Limit Plugin +/// @title Native Token Limit Module /// @author ERC-6900 Authors -/// @notice This plugin supports a single total native token spend limit. +/// @notice This module supports a single total native token spend limit. /// It tracks a total spend limit across UserOperation gas limits and native token transfers. /// If a non whitelisted paymaster is used, UO gas would not cause the limit to decrease. /// If a whitelisted paymaster is used, gas is still counted towards the limit -contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { +contract NativeTokenLimitModule is BaseModule, IExecutionHook, IValidationHook { using UserOperationLib for PackedUserOperation; using EnumerableSet for EnumerableSet.Bytes32Set; @@ -82,7 +82,7 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { return _checkAndDecrementLimit(entityId, data); } - /// @inheritdoc IPlugin + /// @inheritdoc IModule function onInstall(bytes calldata data) external override { (uint32 startEntityId, uint256[] memory spendLimits) = abi.decode(data, (uint32, uint256[])); @@ -95,7 +95,7 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { } } - /// @inheritdoc IPlugin + /// @inheritdoc IModule function onUninstall(bytes calldata data) external override { // This is the highest entityId that's being used by the account uint32 entityId = abi.decode(data, (uint32)); @@ -117,13 +117,13 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { override {} // solhint-disable-line no-empty-blocks - /// @inheritdoc IPlugin + /// @inheritdoc IModule // solhint-disable-next-line no-empty-blocks - function pluginManifest() external pure override returns (PluginManifest memory) {} + function moduleManifest() external pure override returns (ModuleManifest memory) {} - /// @inheritdoc IPlugin - function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { - PluginMetadata memory metadata; + /// @inheritdoc IModule + function moduleMetadata() external pure virtual override returns (ModuleMetadata memory) { + ModuleMetadata memory metadata; metadata.name = _NAME; metadata.version = _VERSION; metadata.author = _AUTHOR; @@ -138,8 +138,8 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { // ┃ EIP-165 ┃ // ┗━━━━━━━━━━━━━━━┛ - /// @inheritdoc BasePlugin - function supportsInterface(bytes4 interfaceId) public view override(BasePlugin, IERC165) returns (bool) { + /// @inheritdoc BaseModule + function supportsInterface(bytes4 interfaceId) public view override(BaseModule, IERC165) returns (bool) { return interfaceId == type(IExecutionHook).interfaceId || super.supportsInterface(interfaceId); } diff --git a/src/plugins/TokenReceiverPlugin.sol b/src/modules/TokenReceiverModule.sol similarity index 78% rename from src/plugins/TokenReceiverPlugin.sol rename to src/modules/TokenReceiverModule.sol index 95fbcfe0..b4cf839b 100644 --- a/src/plugins/TokenReceiverPlugin.sol +++ b/src/modules/TokenReceiverModule.sol @@ -4,15 +4,15 @@ pragma solidity ^0.8.25; import {IERC1155Receiver} from "@openzeppelin/contracts/interfaces/IERC1155Receiver.sol"; import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; -import {IPlugin, ManifestExecutionFunction, PluginManifest, PluginMetadata} from "../interfaces/IPlugin.sol"; -import {BasePlugin} from "./BasePlugin.sol"; +import {IModule, ManifestExecutionFunction, ModuleManifest, ModuleMetadata} from "../interfaces/IModule.sol"; +import {BaseModule} from "./BaseModule.sol"; -/// @title Token Receiver Plugin +/// @title Token Receiver Module /// @author ERC-6900 Authors -/// @notice This plugin allows modular accounts to receive various types of tokens by implementing +/// @notice This module allows modular accounts to receive various types of tokens by implementing /// required token receiver interfaces. -contract TokenReceiverPlugin is BasePlugin, IERC721Receiver, IERC1155Receiver { - string internal constant _NAME = "Token Receiver Plugin"; +contract TokenReceiverModule is BaseModule, IERC721Receiver, IERC1155Receiver { + string internal constant _NAME = "Token Receiver Module"; string internal constant _VERSION = "1.0.0"; string internal constant _AUTHOR = "ERC-6900 Authors"; @@ -43,20 +43,20 @@ contract TokenReceiverPlugin is BasePlugin, IERC721Receiver, IERC1155Receiver { } // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Plugin interface functions ┃ + // ┃ Module interface functions ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - /// @inheritdoc IPlugin + /// @inheritdoc IModule // solhint-disable-next-line no-empty-blocks function onInstall(bytes calldata) external pure override {} - /// @inheritdoc IPlugin + /// @inheritdoc IModule // solhint-disable-next-line no-empty-blocks function onUninstall(bytes calldata) external pure override {} - /// @inheritdoc IPlugin - function pluginManifest() external pure override returns (PluginManifest memory) { - PluginManifest memory manifest; + /// @inheritdoc IModule + function moduleManifest() external pure override returns (ModuleManifest memory) { + ModuleManifest memory manifest; manifest.executionFunctions = new ManifestExecutionFunction[](3); manifest.executionFunctions[0] = ManifestExecutionFunction({ @@ -82,9 +82,9 @@ contract TokenReceiverPlugin is BasePlugin, IERC721Receiver, IERC1155Receiver { return manifest; } - /// @inheritdoc IPlugin - function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { - PluginMetadata memory metadata; + /// @inheritdoc IModule + function moduleMetadata() external pure virtual override returns (ModuleMetadata memory) { + ModuleMetadata memory metadata; metadata.name = _NAME; metadata.version = _VERSION; metadata.author = _AUTHOR; diff --git a/src/plugins/validation/ISingleSignerValidation.sol b/src/modules/validation/ISingleSignerValidation.sol similarity index 100% rename from src/plugins/validation/ISingleSignerValidation.sol rename to src/modules/validation/ISingleSignerValidation.sol diff --git a/src/plugins/validation/SingleSignerValidation.sol b/src/modules/validation/SingleSignerValidation.sol similarity index 91% rename from src/plugins/validation/SingleSignerValidation.sol rename to src/modules/validation/SingleSignerValidation.sol index 6699cb58..a384cda7 100644 --- a/src/plugins/validation/SingleSignerValidation.sol +++ b/src/modules/validation/SingleSignerValidation.sol @@ -6,9 +6,9 @@ import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; -import {IPlugin, PluginManifest, PluginMetadata} from "../../interfaces/IPlugin.sol"; +import {IModule, ModuleManifest, ModuleMetadata} from "../../interfaces/IModule.sol"; import {IValidation} from "../../interfaces/IValidation.sol"; -import {BasePlugin} from "../BasePlugin.sol"; +import {BaseModule} from "../BaseModule.sol"; import {ISingleSignerValidation} from "./ISingleSignerValidation.sol"; /// @title ECSDA Validation @@ -24,7 +24,7 @@ import {ISingleSignerValidation} from "./ISingleSignerValidation.sol"; /// /// - This validation supports composition that other validation can relay on entities in this validation /// to validate partially or fully. -contract SingleSignerValidation is ISingleSignerValidation, BasePlugin { +contract SingleSignerValidation is ISingleSignerValidation, BaseModule { using ECDSA for bytes32; using MessageHashUtils for bytes32; @@ -46,13 +46,13 @@ contract SingleSignerValidation is ISingleSignerValidation, BasePlugin { _transferSigner(entityId, newSigner); } - /// @inheritdoc IPlugin + /// @inheritdoc IModule function onInstall(bytes calldata data) external override { (uint32 entityId, address newSigner) = abi.decode(data, (uint32, address)); _transferSigner(entityId, newSigner); } - /// @inheritdoc IPlugin + /// @inheritdoc IModule function onUninstall(bytes calldata data) external override { // ToDo: what does it mean in the world of composable validation world to uninstall one type of validation // We can either get rid of all SingleSigner signers. What about the nested ones? @@ -115,18 +115,18 @@ contract SingleSignerValidation is ISingleSignerValidation, BasePlugin { } // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Plugin interface functions ┃ + // ┃ Module interface functions ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - /// @inheritdoc IPlugin - function pluginManifest() external pure override returns (PluginManifest memory) { - PluginManifest memory manifest; + /// @inheritdoc IModule + function moduleManifest() external pure override returns (ModuleManifest memory) { + ModuleManifest memory manifest; return manifest; } - /// @inheritdoc IPlugin - function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { - PluginMetadata memory metadata; + /// @inheritdoc IModule + function moduleMetadata() external pure virtual override returns (ModuleMetadata memory) { + ModuleMetadata memory metadata; metadata.name = _NAME; metadata.version = _VERSION; metadata.author = _AUTHOR; diff --git a/src/samples/permissionhooks/AllowlistPlugin.sol b/src/samples/permissionhooks/AllowlistModule.sol similarity index 91% rename from src/samples/permissionhooks/AllowlistPlugin.sol rename to src/samples/permissionhooks/AllowlistModule.sol index bb468ae8..b90fd6c2 100644 --- a/src/samples/permissionhooks/AllowlistPlugin.sol +++ b/src/samples/permissionhooks/AllowlistModule.sol @@ -3,13 +3,13 @@ pragma solidity ^0.8.25; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; -import {PluginManifest, PluginMetadata} from "../../interfaces/IPlugin.sol"; +import {ModuleManifest, ModuleMetadata} from "../../interfaces/IModule.sol"; import {Call, IStandardExecutor} from "../../interfaces/IStandardExecutor.sol"; import {IValidationHook} from "../../interfaces/IValidationHook.sol"; -import {BasePlugin} from "../../plugins/BasePlugin.sol"; +import {BaseModule} from "../../modules/BaseModule.sol"; -contract AllowlistPlugin is IValidationHook, BasePlugin { +contract AllowlistModule is IValidationHook, BaseModule { enum EntityId { PRE_VALIDATION_HOOK } @@ -95,9 +95,9 @@ contract AllowlistPlugin is IValidationHook, BasePlugin { revert NotImplemented(); } - function pluginMetadata() external pure override returns (PluginMetadata memory) { - PluginMetadata memory metadata; - metadata.name = "Allowlist Plugin"; + function moduleMetadata() external pure override returns (ModuleMetadata memory) { + ModuleMetadata memory metadata; + metadata.name = "Allowlist Module"; metadata.version = "v0.0.1"; metadata.author = "ERC-6900 Working Group"; @@ -105,7 +105,7 @@ contract AllowlistPlugin is IValidationHook, BasePlugin { } // solhint-disable-next-line no-empty-blocks - function pluginManifest() external pure override returns (PluginManifest memory) {} + function moduleManifest() external pure override returns (ModuleManifest memory) {} function _checkAllowlistCalldata(bytes calldata callData) internal view { if (bytes4(callData[:4]) == IStandardExecutor.execute.selector) { diff --git a/standard/ERCs/ERC6900Diagrams.excalidraw b/standard/ERCs/ERC6900Diagrams.excalidraw index aeef2f91..0b37d0b4 100644 --- a/standard/ERCs/ERC6900Diagrams.excalidraw +++ b/standard/ERCs/ERC6900Diagrams.excalidraw @@ -734,11 +734,11 @@ "locked": false, "fontSize": 25.664343526865803, "fontFamily": 2, - "text": "Native Function / (Plugin) Execution Function", + "text": "Native Function / (Module) Execution Function", "textAlign": "center", "verticalAlign": "middle", "containerId": "FSqrJM9FRySWN10YDIFuH", - "originalText": "Native Function / (Plugin) Execution Function", + "originalText": "Native Function / (Module) Execution Function", "lineHeight": 1.15, "baseline": 24 }, @@ -1274,11 +1274,11 @@ "locked": false, "fontSize": 20, "fontFamily": 3, - "text": "\n executeFromPlugin", + "text": "\n executeFromModule", "textAlign": "left", "verticalAlign": "top", "containerId": "V64RYeLbqd8REancwGi30", - "originalText": "\n executeFromPlugin", + "originalText": "\n executeFromModule", "lineHeight": 1.2, "baseline": 44 }, @@ -1513,11 +1513,11 @@ "locked": false, "fontSize": 25.664343526865803, "fontFamily": 2, - "text": "Permitted Plugin Execution Function", + "text": "Permitted Module Execution Function", "textAlign": "center", "verticalAlign": "middle", "containerId": "3HJCra6w4bVr0zNxhwPIg", - "originalText": "Permitted Plugin Execution Function", + "originalText": "Permitted Module Execution Function", "lineHeight": 1.15, "baseline": 24 }, @@ -1681,11 +1681,11 @@ "locked": false, "fontSize": 25.664343526865803, "fontFamily": 2, - "text": "Plugins", + "text": "Modules", "textAlign": "center", "verticalAlign": "middle", "containerId": "owBW-NHsmLmREuOjDCv06", - "originalText": "Plugins", + "originalText": "Modules", "lineHeight": 1.15, "baseline": 24 }, @@ -1720,11 +1720,11 @@ "locked": false, "fontSize": 25.664343526865803, "fontFamily": 2, - "text": "Plugin Permission Check", + "text": "Module Permission Check", "textAlign": "center", "verticalAlign": "middle", "containerId": "wBFdRyQG4wrtwbKkyNsa-", - "originalText": "Plugin Permission Check", + "originalText": "Module Permission Check", "lineHeight": 1.15, "baseline": 24 }, @@ -1796,11 +1796,11 @@ "locked": false, "fontSize": 20, "fontFamily": 3, - "text": "\n executeFromPluginExternal", + "text": "\n executeFromModuleExternal", "textAlign": "left", "verticalAlign": "top", "containerId": "dgC4KV1DfhkMlJgqA9-10", - "originalText": "\n executeFromPluginExternal", + "originalText": "\n executeFromModuleExternal", "lineHeight": 1.2, "baseline": 44 }, @@ -1876,11 +1876,11 @@ "locked": false, "fontSize": 25.664343526865803, "fontFamily": 2, - "text": "(Plugin) Execution Function", + "text": "(Module) Execution Function", "textAlign": "center", "verticalAlign": "middle", "containerId": "j7kk61t1I6enSnhuaVZHW", - "originalText": "(Plugin) Execution Function", + "originalText": "(Module) Execution Function", "lineHeight": 1.15, "baseline": 24 }, @@ -1977,11 +1977,11 @@ "locked": false, "fontSize": 20, "fontFamily": 2, - "text": "1 calls plugin ", + "text": "1 calls module ", "textAlign": "center", "verticalAlign": "middle", "containerId": "71sd7fDDpQnB-DSlvQqP_", - "originalText": "1 calls plugin ", + "originalText": "1 calls module ", "lineHeight": 1.15, "baseline": 19 }, @@ -2133,11 +2133,11 @@ "locked": false, "fontSize": 20, "fontFamily": 2, - "text": "2.2 calls external contracts \nthrough\n executeFromPluginExternal", + "text": "2.2 calls external contracts \nthrough\n executeFromModuleExternal", "textAlign": "center", "verticalAlign": "middle", "containerId": "W_TcvUOGvsHsXmj31qq0O", - "originalText": "2.2 calls external contracts through\n executeFromPluginExternal", + "originalText": "2.2 calls external contracts through\n executeFromModuleExternal", "lineHeight": 1.15, "baseline": 65 }, @@ -2259,11 +2259,11 @@ "locked": false, "fontSize": 25.664343526865803, "fontFamily": 2, - "text": "Plugin Permission Check", + "text": "Module Permission Check", "textAlign": "center", "verticalAlign": "middle", "containerId": "K34YWjpoqir9H5g1FHSe1", - "originalText": "Plugin Permission Check", + "originalText": "Module Permission Check", "lineHeight": 1.15, "baseline": 24 }, @@ -2517,11 +2517,11 @@ "locked": false, "fontSize": 36, "fontFamily": 2, - "text": "Plugin Execution Flow", + "text": "Module Execution Flow", "textAlign": "left", "verticalAlign": "top", "containerId": null, - "originalText": "Plugin Execution Flow", + "originalText": "Module Execution Flow", "lineHeight": 1.15, "baseline": 33 }, @@ -2555,11 +2555,11 @@ "locked": false, "fontSize": 20, "fontFamily": 2, - "text": "2.1 calls other installed \nplugin through\n executeFromPlugin", + "text": "2.1 calls other installed \nmodule through\n executeFromModule", "textAlign": "center", "verticalAlign": "middle", "containerId": "fA1p68GcpvI-X1krxkJ29", - "originalText": "2.1 calls other installed plugin through\n executeFromPlugin", + "originalText": "2.1 calls other installed module through\n executeFromModule", "lineHeight": 1.15, "baseline": 65 }, @@ -2643,11 +2643,11 @@ "locked": false, "fontSize": 25.68325562404122, "fontFamily": 2, - "text": "Pre executeFromPluginExternal Hook(s)", + "text": "Pre executeFromModuleExternal Hook(s)", "textAlign": "center", "verticalAlign": "middle", "containerId": "emDobd4D91UDt1c8oZ0Iu", - "originalText": "Pre executeFromPluginExternal Hook(s)", + "originalText": "Pre executeFromModuleExternal Hook(s)", "lineHeight": 1.15, "baseline": 24 }, @@ -2727,11 +2727,11 @@ "locked": false, "fontSize": 25.664343526865803, "fontFamily": 2, - "text": "Post executeFromPluginExternal Hook(s)", + "text": "Post executeFromModuleExternal Hook(s)", "textAlign": "center", "verticalAlign": "middle", "containerId": "Hx9p9aaBWogvW3Z9zujgf", - "originalText": "Post executeFromPluginExternal Hook(s)", + "originalText": "Post executeFromModuleExternal Hook(s)", "lineHeight": 1.15, "baseline": 24 }, @@ -3325,11 +3325,11 @@ "locked": false, "fontSize": 20, "fontFamily": 3, - "text": "\n executeFromPlugin/executeFromPluginExternal", + "text": "\n executeFromModule/executeFromModuleExternal", "textAlign": "left", "verticalAlign": "top", "containerId": "2JLeKLisTh-Vc4SuDVk5b", - "originalText": "\n executeFromPlugin/executeFromPluginExternal", + "originalText": "\n executeFromModule/executeFromModuleExternal", "lineHeight": 1.2, "baseline": 44 }, @@ -3715,11 +3715,11 @@ "locked": false, "fontSize": 25.664343526865803, "fontFamily": 2, - "text": "Pre Plugin Execution Hook(s)", + "text": "Pre Module Execution Hook(s)", "textAlign": "center", "verticalAlign": "middle", "containerId": "uJ0egPTKleB3Aut1HFd2K", - "originalText": "Pre Plugin Execution Hook(s)", + "originalText": "Pre Module Execution Hook(s)", "lineHeight": 1.15, "baseline": 24 }, @@ -3793,11 +3793,11 @@ "locked": false, "fontSize": 25.664343526865803, "fontFamily": 2, - "text": "Post Plugin Execution Hook(s)", + "text": "Post Module Execution Hook(s)", "textAlign": "center", "verticalAlign": "middle", "containerId": "7Klv-GWoFcKLlBdaExqzT", - "originalText": "Post Plugin Execution Hook(s)", + "originalText": "Post Module Execution Hook(s)", "lineHeight": 1.15, "baseline": 24 }, @@ -4183,11 +4183,11 @@ "locked": false, "fontSize": 25.664343526865803, "fontFamily": 2, - "text": "Plugins", + "text": "Modules", "textAlign": "center", "verticalAlign": "middle", "containerId": "qLT39tSOFDyxvMCmG5W1h", - "originalText": "Plugins", + "originalText": "Modules", "lineHeight": 1.15, "baseline": 24 }, @@ -4781,11 +4781,11 @@ "locked": false, "fontSize": 20, "fontFamily": 3, - "text": "\n executeFromPlugin", + "text": "\n executeFromModule", "textAlign": "left", "verticalAlign": "top", "containerId": "h4-dGWuBPeBT6vEznfNUa", - "originalText": "\n executeFromPlugin", + "originalText": "\n executeFromModule", "lineHeight": 1.2, "baseline": 44 }, @@ -5171,11 +5171,11 @@ "locked": false, "fontSize": 25.664343526865803, "fontFamily": 2, - "text": "Pre Plugin Execution Hook(s)", + "text": "Pre Module Execution Hook(s)", "textAlign": "center", "verticalAlign": "middle", "containerId": "2tiy_HH9LYxsFnORyo1QN", - "originalText": "Pre Plugin Execution Hook(s)", + "originalText": "Pre Module Execution Hook(s)", "lineHeight": 1.15, "baseline": 24 }, @@ -5249,11 +5249,11 @@ "locked": false, "fontSize": 25.664343526865803, "fontFamily": 2, - "text": "Post Plugin Execution Hook(s)", + "text": "Post Module Execution Hook(s)", "textAlign": "center", "verticalAlign": "middle", "containerId": "T7fYn-6m0AnnYUsZ7Vu-6", - "originalText": "Post Plugin Execution Hook(s)", + "originalText": "Post Module Execution Hook(s)", "lineHeight": 1.15, "baseline": 24 }, @@ -5323,11 +5323,11 @@ "locked": false, "fontSize": 25.664343526865803, "fontFamily": 2, - "text": "Account-Native Function / Plugin Execution Function", + "text": "Account-Native Function / Module Execution Function", "textAlign": "center", "verticalAlign": "middle", "containerId": "Sj8y2LMf6pefmYIQqny_R", - "originalText": "Account-Native Function / Plugin Execution Function", + "originalText": "Account-Native Function / Module Execution Function", "lineHeight": 1.15, "baseline": 24 }, @@ -5639,11 +5639,11 @@ "locked": false, "fontSize": 25.664343526865803, "fontFamily": 2, - "text": "Plugins", + "text": "Modules", "textAlign": "center", "verticalAlign": "middle", "containerId": "UIOBlFmO5FdF3hqajkL-P", - "originalText": "Plugins", + "originalText": "Modules", "lineHeight": 1.15, "baseline": 24 }, @@ -6517,11 +6517,11 @@ "locked": false, "fontSize": 20, "fontFamily": 3, - "text": "\n executeFromPlugin", + "text": "\n executeFromModule", "textAlign": "left", "verticalAlign": "top", "containerId": "ejh1h99xkb4w1wuTImtiK", - "originalText": "\n executeFromPlugin", + "originalText": "\n executeFromModule", "lineHeight": 1.2, "baseline": 44 }, @@ -7207,11 +7207,11 @@ "locked": false, "fontSize": 25.664343526865803, "fontFamily": 2, - "text": "Plugins", + "text": "Modules", "textAlign": "center", "verticalAlign": "middle", "containerId": "9PEiLoAhsOByWsJZ9OKS9", - "originalText": "Plugins", + "originalText": "Modules", "lineHeight": 1.15, "baseline": 24 }, @@ -7339,11 +7339,11 @@ "locked": false, "fontSize": 25.664343526865803, "fontFamily": 2, - "text": "Plugin Permission Check", + "text": "Module Permission Check", "textAlign": "center", "verticalAlign": "middle", "containerId": "S3jaS7lY7njUd4iFC2CvV", - "originalText": "Plugin Permission Check", + "originalText": "Module Permission Check", "lineHeight": 1.15, "baseline": 24 } diff --git a/standard/ERCs/erc-6900.md b/standard/ERCs/erc-6900.md index fed6a625..d5d15744 100644 --- a/standard/ERCs/erc-6900.md +++ b/standard/ERCs/erc-6900.md @@ -1,6 +1,6 @@ --- eip: 6900 -title: Modular Smart Contract Accounts and Plugins +title: Modular Smart Contract Accounts and Modules description: Interfaces for composable contract accounts optionally supporting upgradability and introspection author: Adam Egyed (@adamegyed), Fangting Liu (@trinity-0111), Jay Paik (@jaypaik), Yoav Weiss (@yoavw) discussions-to: https://ethereum-magicians.org/t/eip-modular-smart-contract-accounts-and-plugins/13885 @@ -13,7 +13,7 @@ requires: 165, 4337 ## Abstract -This proposal standardizes smart contract accounts and account plugins, which are smart contract interfaces that allow for composable logic within smart contract accounts. This proposal is compliant with [ERC-4337](./eip-4337.md), and takes inspiration from [ERC-2535](./eip-2535.md) when defining interfaces for updating and querying modular function implementations. +This proposal standardizes smart contract accounts and account modules, which are smart contract interfaces that allow for composable logic within smart contract accounts. This proposal is compliant with [ERC-4337](./eip-4337.md), and takes inspiration from [ERC-2535](./eip-2535.md) when defining interfaces for updating and querying modular function implementations. This modular approach splits account functionality into three categories, implements them in external contracts, and defines an expected execution flow from accounts. @@ -21,24 +21,24 @@ This modular approach splits account functionality into three categories, implem One of the goals that ERC-4337 accomplishes is abstracting the logic for execution and validation to each smart contract account. -Many new features of accounts can be built by customizing the logic that goes into the validation and execution steps. Examples of such features include session keys, subscriptions, spending limits, and role-based access control. Currently, some of these features are implemented natively by specific smart contract accounts, and others are able to be implemented by plugin systems. Examples of proprietary plugin systems include Safe modules and ZeroDev plugins. +Many new features of accounts can be built by customizing the logic that goes into the validation and execution steps. Examples of such features include session keys, subscriptions, spending limits, and role-based access control. Currently, some of these features are implemented natively by specific smart contract accounts, and others are able to be implemented by module systems. Examples of proprietary module systems include Safe modules and ZeroDev modules. -However, managing multiple account instances provides a worse user experience, fragmenting accounts across supported features and security configurations. Additionally, it requires plugin developers to choose which platforms to support, causing either platform lock-in or duplicated development effort. +However, managing multiple account instances provides a worse user experience, fragmenting accounts across supported features and security configurations. Additionally, it requires module developers to choose which platforms to support, causing either platform lock-in or duplicated development effort. -We propose a standard that coordinates the implementation work between plugin developers and wallet developers. This standard defines a modular smart contract account capable of supporting all standard-conformant plugins. This allows users to have greater portability of their data, and for plugin developers to not have to choose specific account implementations to support. +We propose a standard that coordinates the implementation work between module developers and wallet developers. This standard defines a modular smart contract account capable of supporting all standard-conformant modules. This allows users to have greater portability of their data, and for module developers to not have to choose specific account implementations to support. -![diagram showing relationship between accounts and plugins with modular functions](../assets/eip-6900/MSCA_Shared_Components_Diagram.svg) +![diagram showing relationship between accounts and modules with modular functions](../assets/eip-6900/MSCA_Shared_Components_Diagram.svg) We take inspiration from ERC-2535's diamond pattern for routing execution based on function selectors, and create a similarly composable account. However, the standard does not require the multi-facet proxy pattern. -These plugins can contain execution logic, validation schemes, and hooks. Validation schemes define the circumstances under which the smart contract account will approve actions taken on its behalf, while hooks allow for pre- and post-execution controls. +These modules can contain execution logic, validation schemes, and hooks. Validation schemes define the circumstances under which the smart contract account will approve actions taken on its behalf, while hooks allow for pre- and post-execution controls. -Accounts adopting this standard will support modular, upgradable execution and validation logic. Defining this as a standard for smart contract accounts will make plugins easier to develop securely and will allow for greater interoperability. +Accounts adopting this standard will support modular, upgradable execution and validation logic. Defining this as a standard for smart contract accounts will make modules easier to develop securely and will allow for greater interoperability. Goals: - Provide standards for how validation, execution, and hook functions for smart contract accounts should be written. -- Provide standards for how compliant accounts should add, update, remove, and inspect plugins. +- Provide standards for how compliant accounts should add, update, remove, and inspect modules. ## Specification @@ -55,16 +55,16 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S - **User Operation Validation** functions handle calls to `validateUserOp` and check the validity of an ERC-4337 user operation. - **Runtime Validation** functions run before an execution function when not called via a user operation, and enforce checks. Common checks include allowing execution only by an owner. - An **execution function** is a smart contract function that defines the main execution step of a function for a modular account. -- The **standard execute** functions are two specific execute functions that are implemented natively by the modular account, and not on a plugin. These allow for open-ended execution. +- The **standard execute** functions are two specific execute functions that are implemented natively by the modular account, and not on a module. These allow for open-ended execution. - A **hook** is a smart contract function executed before or after another function, with the ability to modify state or cause the entire call to revert. There are four types of hooks: - **Pre User Operation Validation Hook** functions run before user operation validation functions. These can enforce permissions on what actions a validation function may perform via user operations. - **Pre Runtime Validation Hook** functions run before runtime validation functions. These can enforce permissions on what actions a validation function may perform via direct calls. - **Pre Execution Hook** functions run before an execution function. They may optionally return data to be consumed by their related post execution hook functions. - **Post Execution Hook** functions run after an execution function. They may optionally take returned data from their related pre execution hook functions. - An **associated function** refers to either a validation function or a hook. -- A **native function** refers to a function implemented natively by the modular account, as opposed to a function added by a plugin. -- A **plugin** is a deployed smart contract that hosts any amount of the above three kinds of modular functions: execution functions, validation functions, or hooks. -- A plugin **manifest** is responsible for describing the execution functions, validation functions, and hooks that will be configured on the MSCA during installation, as well as the plugin’s metadata, dependency requirements, and permissions. +- A **native function** refers to a function implemented natively by the modular account, as opposed to a function added by a module. +- A **module** is a deployed smart contract that hosts any amount of the above three kinds of modular functions: execution functions, validation functions, or hooks. +- A module **manifest** is responsible for describing the execution functions, validation functions, and hooks that will be configured on the MSCA during installation, as well as the module’s metadata, dependency requirements, and permissions. ### Overview @@ -74,12 +74,12 @@ A call to the smart contract account can be broken down into the steps as shown ![diagram showing call flow within an modular account](../assets/eip-6900/Modular_Account_Call_Flow.svg) -The following diagram shows permitted plugin execution flows. During a plugin's execution step from the above diagram, the plugin may perform a "Plugin Execution Function", using either `executeFromPlugin` or `executeFromPluginExternal`. These can be used by plugins to execute using the account's context. +The following diagram shows permitted module execution flows. During a module's execution step from the above diagram, the module may perform a "Module Execution Function", using either `executeFromModule` or `executeFromModuleExternal`. These can be used by modules to execute using the account's context. -- `executeFromPlugin` handles calls to other installed plugin's execution function on the modular account. -- `executeFromPluginExternal` handles calls to external addresses. +- `executeFromModule` handles calls to other installed module's execution function on the modular account. +- `executeFromModuleExternal` handles calls to external addresses. -![diagram showing a plugin execution flow](../assets/eip-6900/Plugin_Execution_Flow.svg) +![diagram showing a module execution flow](../assets/eip-6900/Module_Execution_Flow.svg) Each step is modular, supporting different implementations for each execution function, and composable, supporting multiple steps through hooks. Combined, these allow for open-ended programmable accounts. @@ -88,52 +88,52 @@ Each step is modular, supporting different implementations for each execution fu **Modular Smart Contract Accounts** **MUST** implement - `IAccount.sol` from [ERC-4337](./eip-4337.md). -- `IPluginManager.sol` to support installing and uninstalling plugins. -- `IStandardExecutor.sol` to support open-ended execution. **Calls to plugins through this SHOULD revert.** -- `IPluginExecutor.sol` to support execution from plugins. **Calls to plugins through `executeFromPluginExternal` SHOULD revert.** +- `IModuleManager.sol` to support installing and uninstalling modules. +- `IStandardExecutor.sol` to support open-ended execution. **Calls to modules through this SHOULD revert.** +- `IModuleExecutor.sol` to support execution from modules. **Calls to modules through `executeFromModuleExternal` SHOULD revert.** **Modular Smart Contract Accounts** **MAY** implement -- `IAccountLoupe.sol` to support visibility in plugin configuration on-chain. +- `IAccountLoupe.sol` to support visibility in module configuration on-chain. -**Plugins** **MUST** implement +**Modules** **MUST** implement -- `IPlugin.sol` described below and implement [ERC-165](./eip-165.md) for `IPlugin`. +- `IModule.sol` described below and implement [ERC-165](./eip-165.md) for `IModule`. -#### `IPluginManager.sol` +#### `IModuleManager.sol` -Plugin manager interface. Modular Smart Contract Accounts **MUST** implement this interface to support installing and uninstalling plugins. +Module manager interface. Modular Smart Contract Accounts **MUST** implement this interface to support installing and uninstalling modules. ```solidity // Treats the first 20 bytes as an address, and the last byte as a function identifier. -type PluginEntity is bytes21; +type ModuleEntity is bytes21; -interface IPluginManager { - event PluginInstalled(address indexed plugin, bytes32 manifestHash, PluginEntity[] dependencies); +interface IModuleManager { + event ModuleInstalled(address indexed module, bytes32 manifestHash, ModuleEntity[] dependencies); - event PluginUninstalled(address indexed plugin, bool indexed onUninstallSucceeded); + event ModuleUninstalled(address indexed module, bool indexed onUninstallSucceeded); - /// @notice Install a plugin to the modular account. - /// @param plugin The plugin to install. - /// @param manifestHash The hash of the plugin manifest. - /// @param pluginInstallData Optional data to be decoded and used by the plugin to setup initial plugin data + /// @notice Install a module to the modular account. + /// @param module The module to install. + /// @param manifestHash The hash of the module manifest. + /// @param moduleInstallData Optional data to be decoded and used by the module to setup initial module data /// for the modular account. - /// @param dependencies The dependencies of the plugin, as described in the manifest. Each PluginEntity - /// MUST be composed of an installed plugin's address and a function ID of its validation function. - function installPlugin( - address plugin, + /// @param dependencies The dependencies of the module, as described in the manifest. Each ModuleEntity + /// MUST be composed of an installed module's address and a function ID of its validation function. + function installModule( + address module, bytes32 manifestHash, - bytes calldata pluginInstallData, - PluginEntity[] calldata dependencies + bytes calldata moduleInstallData, + ModuleEntity[] calldata dependencies ) external; - /// @notice Uninstall a plugin from the modular account. - /// @param plugin The plugin to uninstall. + /// @notice Uninstall a module from the modular account. + /// @param module The module to uninstall. /// @param config An optional, implementation-specific field that accounts may use to ensure consistency /// guarantees. - /// @param pluginUninstallData Optional data to be decoded and used by the plugin to clear plugin data for the + /// @param moduleUninstallData Optional data to be decoded and used by the module to clear module data for the /// modular account. - function uninstallPlugin(address plugin, bytes calldata config, bytes calldata pluginUninstallData) external; + function uninstallModule(address module, bytes calldata config, bytes calldata moduleUninstallData) external; } ``` @@ -142,9 +142,9 @@ interface IPluginManager { Standard execute interface. Modular Smart Contract Accounts **MUST** implement this interface to support open-ended execution. -Standard execute functions SHOULD check whether the call's target implements the `IPlugin` interface via ERC-165. +Standard execute functions SHOULD check whether the call's target implements the `IModule` interface via ERC-165. -**If the target is a plugin, the call SHOULD revert.** This prevents accidental misconfiguration or misuse of plugins (both installed and uninstalled). +**If the target is a module, the call SHOULD revert.** This prevents accidental misconfiguration or misuse of modules (both installed and uninstalled). ```solidity struct Call { @@ -158,7 +158,7 @@ struct Call { interface IStandardExecutor { /// @notice Standard execute method. - /// @dev If the target is a plugin, the call SHOULD revert. + /// @dev If the target is a module, the call SHOULD revert. /// @param target The target address for account to call. /// @param value The value to send with the call. /// @param data The calldata for the call. @@ -166,7 +166,7 @@ interface IStandardExecutor { function execute(address target, uint256 value, bytes calldata data) external payable returns (bytes memory); /// @notice Standard executeBatch method. - /// @dev If the target is a plugin, the call SHOULD revert. If any of the calls revert, the entire batch MUST + /// @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. @@ -174,32 +174,32 @@ interface IStandardExecutor { } ``` -#### `IPluginExecutor.sol` +#### `IModuleExecutor.sol` -Execution interface for calls made from plugins. Modular Smart Contract Accounts **MUST** implement this interface to support execution from plugins. +Execution interface for calls made from modules. Modular Smart Contract Accounts **MUST** implement this interface to support execution from modules. -The `executeFromPluginExternal` function SHOULD check whether the call's target implements the `IPlugin` interface via ERC-165. +The `executeFromModuleExternal` function SHOULD check whether the call's target implements the `IModule` interface via ERC-165. -**If the target of `executeFromPluginExternal` function is a plugin, the call SHOULD revert.** +**If the target of `executeFromModuleExternal` function is a module, the call SHOULD revert.** -This prevents accidental misconfiguration or misuse of plugins (both installed and uninstalled). Installed plugins MAY interact with other installed plugins via the `executeFromPlugin` function. +This prevents accidental misconfiguration or misuse of modules (both installed and uninstalled). Installed modules MAY interact with other installed modules via the `executeFromModule` function. ```solidity -interface IPluginExecutor { - /// @notice Execute a call from a plugin through the account. - /// @dev Permissions must be granted to the calling plugin for the call to go through. +interface IModuleExecutor { + /// @notice Execute a call from a module through the account. + /// @dev Permissions must be granted to the calling module for the call to go through. /// @param data The calldata to send to the account. /// @return The return data from the call. - function executeFromPlugin(bytes calldata data) external payable returns (bytes memory); + function executeFromModule(bytes calldata data) external payable returns (bytes memory); - /// @notice Execute a call from a plugin to a non-plugin address. - /// @dev If the target is a plugin, the call SHOULD revert. Permissions must be granted to the calling plugin + /// @notice Execute a call from a module to a non-module address. + /// @dev If the target is a module, the call SHOULD revert. Permissions must be granted to the calling module /// for the call to go through. /// @param target The address to be called. /// @param value The value to send with the call. /// @param data The calldata to send to the target. /// @return The return data from the call. - function executeFromPluginExternal(address target, uint256 value, bytes calldata data) + function executeFromModuleExternal(address target, uint256 value, bytes calldata data) external payable returns (bytes memory); @@ -208,26 +208,26 @@ interface IPluginExecutor { #### `IAccountLoupe.sol` -Plugin inspection interface. Modular Smart Contract Accounts **MAY** implement this interface to support visibility in plugin configuration on-chain. +Module inspection interface. Modular Smart Contract Accounts **MAY** implement this interface to support visibility in module configuration on-chain. ```solidity interface IAccountLoupe { /// @notice Config for an execution function, given a selector. struct ExecutionFunctionConfig { - address plugin; - PluginEntity validationFunction; + address module; + ModuleEntity validationFunction; } /// @notice Pre and post hooks for a given selector. /// @dev It's possible for one of either `preExecHook` or `postExecHook` to be empty. struct ExecutionHooks { - PluginEntity hookFunction; + ModuleEntity hookFunction; bool isPreHook; bool isPostHook; } - /// @notice Get the validation functions and plugin address for a selector. - /// @dev If the selector is a native function, the plugin address will be the address of the account. + /// @notice Get the validation functions and module address 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 configuration for. /// @return The configuration for this selector. function getExecutionFunctionConfig(bytes4 selector) external view returns (ExecutionFunctionConfig memory); @@ -243,28 +243,28 @@ interface IAccountLoupe { function getPreValidationHooks(bytes4 selector) external view - returns (PluginEntity[] memory preValidationHooks); + returns (ModuleEntity[] memory preValidationHooks); - /// @notice Get an array of all installed plugins. - /// @return The addresses of all installed plugins. - function getInstalledPlugins() external view returns (address[] memory); + /// @notice Get an array of all installed modules. + /// @return The addresses of all installed modules. + function getInstalledModules() external view returns (address[] memory); } ``` -#### `IPlugin.sol` +#### `IModule.sol` -Plugin interface. Plugins **MUST** implement this interface to support plugin management and interactions with MSCAs. +Module interface. Modules **MUST** implement this interface to support module management and interactions with MSCAs. ```solidity -interface IPlugin { - /// @notice Initialize plugin data for the modular account. - /// @dev Called by the modular account during `installPlugin`. - /// @param data Optional bytes array to be decoded and used by the plugin to setup initial plugin data for the modular account. +interface IModule { + /// @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 plugin data for the modular account. - /// @dev Called by the modular account during `uninstallPlugin`. - /// @param data Optional bytes array to be decoded and used by the plugin to clear plugin data for the modular account. + /// @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 Run the pre user operation validation hook specified by the `entityId`. @@ -318,29 +318,29 @@ interface IPlugin { /// @param preExecHookData The context returned by its associated pre execution hook. function postExecutionHook(uint32 entityId, bytes calldata preExecHookData) external; - /// @notice Describe the contents and intended configuration of the plugin. + /// @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 plugin. - function pluginManifest() external pure returns (PluginManifest memory); + /// @return A manifest describing the contents and intended configuration of the module. + function moduleManifest() external pure returns (ModuleManifest memory); - /// @notice Describe the metadata of the plugin. + /// @notice Describe the metadata of the module. /// @dev This metadata MUST stay constant over time. - /// @return A metadata struct describing the plugin. - function pluginMetadata() external pure returns (PluginMetadata memory); + /// @return A metadata struct describing the module. + function moduleMetadata() external pure returns (ModuleMetadata memory); } ``` -### Plugin manifest +### Module manifest -The plugin manifest is responsible for describing the execution functions, validation functions, and hooks that will be configured on the MSCA during installation, as well as the plugin's metadata, dependencies, and permissions. +The module manifest is responsible for describing the execution functions, validation functions, and hooks that will be configured on the MSCA during installation, as well as the module's metadata, dependencies, and permissions. ```solidity enum ManifestAssociatedFunctionType { // Function is not defined. NONE, - // Function belongs to this plugin. + // Function belongs to this module. SELF, - // Function belongs to an external plugin provided as a dependency during plugin installation. Plugins MAY depend + // Function belongs to an external module provided as a dependency during module installation. Modules MAY depend // on external validation functions. It MUST NOT depend on external hooks, or installation will fail. DEPENDENCY, // Resolves to a magic value to always bypass runtime validation for a given function. @@ -355,8 +355,8 @@ enum ManifestAssociatedFunctionType { PRE_HOOK_ALWAYS_DENY } -/// @dev For functions of type `ManifestAssociatedFunctionType.DEPENDENCY`, the MSCA MUST find the plugin address -/// of the function at `dependencies[dependencyIndex]` during the call to `installPlugin(config)`. +/// @dev For functions of type `ManifestAssociatedFunctionType.DEPENDENCY`, the MSCA MUST find the module address +/// of the function at `dependencies[dependencyIndex]` during the call to `installModule(config)`. struct ManifestFunction { ManifestAssociatedFunctionType functionType; uint8 entityId; @@ -387,37 +387,37 @@ struct SelectorPermission { string permissionDescription; } -/// @dev A struct holding fields to describe the plugin in a purely view context. Intended for front end clients. -struct PluginMetadata { - // A human-readable name of the plugin. +/// @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 plugin, following the semantic versioning scheme. + // 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 plugin. + // 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 plugin. + // functions implemented by this module. SelectorPermission[] permissionDescriptors; } -/// @dev A struct describing how the plugin should be installed on a modular account. -struct PluginManifest { +/// @dev A struct describing how the module should be installed on a modular account. +struct ModuleManifest { // List of ERC-165 interface IDs to add to account to support introspection checks. This MUST NOT include - // IPlugin's interface ID. + // IModule's interface ID. bytes4[] interfaceIds; - // If this plugin depends on other plugins' validation functions, the interface IDs of those plugins MUST be + // If this module depends on other modules' validation functions, the interface IDs of those modules MUST be // provided here, with its position in the array matching the `dependencyIndex` members of `ManifestFunction` // structs used in the manifest. bytes4[] dependencyInterfaceIds; - // Execution functions defined in this plugin to be installed on the MSCA. + // Execution functions defined in this module to be installed on the MSCA. bytes4[] executionFunctions; - // Plugin execution functions already installed on the MSCA that this plugin will be able to call. + // Module execution functions already installed on the MSCA that this module will be able to call. bytes4[] permittedExecutionSelectors; - // Boolean to indicate whether the plugin can call any external address. + // Boolean to indicate whether the module can call any external address. bool permitAnyExternalAddress; - // Boolean to indicate whether the plugin needs access to spend native tokens of the account. If false, the - // plugin MUST still be able to spend up to the balance that it sends to the account in the same call. + // Boolean to indicate whether the module needs access to spend native tokens of the account. If false, the + // module MUST still be able to spend up to the balance that it sends to the account in the same call. bool canSpendNativeToken; ManifestExternalCallPermission[] permittedExternalCalls; ManifestAssociatedFunction[] validationFunctions; @@ -429,34 +429,34 @@ struct PluginManifest { ### Expected behavior -#### Responsibilties of `StandardExecutor` and `PluginExecutor` +#### Responsibilties of `StandardExecutor` and `ModuleExecutor` `StandardExecutor` functions are used for open-ended calls to external addresses. -`PluginExecutor` functions are specifically used by plugins to request the account to execute with account's context. Explicit permissions are required for plugins to use `PluginExecutor`. +`ModuleExecutor` functions are specifically used by modules to request the account to execute with account's context. Explicit permissions are required for modules to use `ModuleExecutor`. The following behavior MUST be followed: -- `StandardExecutor` can NOT call plugin execution functions and/or `PluginExecutor`. This is guaranteed by checking whether the call's target implements the `IPlugin` interface via ERC-165 as required. -- `StandardExecutor` can NOT be called by plugin execution functions and/or `PluginExecutor`. -- Plugin execution functions MUST NOT request access to `StandardExecutor`, they MAY request access to `PluginExecutor`. +- `StandardExecutor` can NOT call module execution functions and/or `ModuleExecutor`. This is guaranteed by checking whether the call's target implements the `IModule` interface via ERC-165 as required. +- `StandardExecutor` can NOT be called by module execution functions and/or `ModuleExecutor`. +- Module execution functions MUST NOT request access to `StandardExecutor`, they MAY request access to `ModuleExecutor`. -#### Calls to `installPlugin` +#### Calls to `installModule` -The function `installPlugin` accepts 3 parameters: the address of the plugin to install, the Keccak-256 hash of the plugin's manifest, ABI-encoded data to pass to the plugin's `onInstall` callback. +The function `installModule` accepts 3 parameters: the address of the module to install, the Keccak-256 hash of the module's manifest, ABI-encoded data to pass to the module's `onInstall` callback. -The function MUST retrieve the plugin's manifest by calling `pluginManifest()` using `staticcall`. +The function MUST retrieve the module's manifest by calling `moduleManifest()` using `staticcall`. The function MUST perform the following preliminary checks: -- Revert if the plugin has already been installed on the modular account. -- Revert if the plugin does not implement ERC-165 or does not support the `IPlugin` interface. -- Revert if `manifestHash` does not match the computed Keccak-256 hash of the plugin's returned manifest. This prevents installation of plugins that attempt to install a different plugin configuration than the one that was approved by the client. +- Revert if the module has already been installed on the modular account. +- Revert if the module does not implement ERC-165 or does not support the `IModule` interface. +- Revert if `manifestHash` does not match the computed Keccak-256 hash of the module's returned manifest. This prevents installation of modules that attempt to install a different module configuration than the one that was approved by the client. - Revert if any address in `dependencies` does not support the interface at its matching index in the manifest's `dependencyInterfaceIds`, or if the two array lengths do not match, or if any of the dependencies are not already installed on the modular account. -The function MUST record the manifest hash and dependencies that were used for the plugin's installation. Each dependency's record MUST also be updated to reflect that it has a new dependent. These records MUST be used to ensure calls to `uninstallPlugin` are comprehensive and undo all edited configuration state from installation. The mechanism by which these records are stored and validated is up to the implementation. +The function MUST record the manifest hash and dependencies that were used for the module's installation. Each dependency's record MUST also be updated to reflect that it has a new dependent. These records MUST be used to ensure calls to `uninstallModule` are comprehensive and undo all edited configuration state from installation. The mechanism by which these records are stored and validated is up to the implementation. -The function MUST store the plugin's permitted function selectors, permitted external calls, and whether it can spend the account's native tokens, to be able to validate calls to `executeFromPlugin` and `executeFromPluginExternal`. +The function MUST store the module's permitted function selectors, permitted external calls, and whether it can spend the account's native tokens, to be able to validate calls to `executeFromModule` and `executeFromModuleExternal`. The function MUST parse through the execution functions, validation functions, and hooks in the manifest and add them to the modular account after resolving each `ManifestFunction` type. @@ -465,36 +465,36 @@ The function MUST parse through the execution functions, validation functions, a The function MAY store the interface IDs provided in the manifest's `interfaceIds` and update its `supportsInterface` behavior accordingly. -Next, the function MUST call the plugin's `onInstall` callback with the data provided in the `pluginInstallData` parameter. This serves to initialize the plugin state for the modular account. If `onInstall` reverts, the `installPlugin` function MUST revert. +Next, the function MUST call the module's `onInstall` callback with the data provided in the `moduleInstallData` parameter. This serves to initialize the module state for the modular account. If `onInstall` reverts, the `installModule` function MUST revert. -Finally, the function MUST emit the event `PluginInstalled` with the plugin's address, the hash of its manifest, and the dependencies that were used. +Finally, the function MUST emit the event `ModuleInstalled` with the module's address, the hash of its manifest, and the dependencies that were used. -> **⚠️ The ability to install and uninstall plugins is very powerful. The security of these functions determines the security of the account. It is critical for modular account implementers to make sure the implementation of the functions in `IPluginManager` have the proper security consideration and access control in place.** +> **⚠️ The ability to install and uninstall modules is very powerful. The security of these functions determines the security of the account. It is critical for modular account implementers to make sure the implementation of the functions in `IModuleManager` have the proper security consideration and access control in place.** -#### Calls to `uninstallPlugin` +#### Calls to `uninstallModule` -The function `uninstallPlugin` accepts 3 parameters: the address of the plugin to uninstall, a bytes field that may have custom requirements or uses by the implementing account, and ABI-encoded data to pass to the plugin's `onUninstall` callback. +The function `uninstallModule` accepts 3 parameters: the address of the module to uninstall, a bytes field that may have custom requirements or uses by the implementing account, and ABI-encoded data to pass to the module's `onUninstall` callback. -The function MUST revert if the plugin is not installed on the modular account. +The function MUST revert if the module is not installed on the modular account. The function SHOULD perform the following checks: -- Revert if the hash of the manifest used at install time does not match the computed Keccak-256 hash of the plugin's current manifest. This prevents unclean removal of plugins that attempt to force a removal of a different plugin configuration than the one that was originally approved by the client for installation. To allow for removal of such plugins, the modular account MAY implement the capability for the manifest to be encoded in the config field as a parameter. -- Revert if there is at least 1 other installed plugin that depends on validation functions added by this plugin. Plugins used as dependencies SHOULD NOT be uninstalled while dependent plugins exist. +- Revert if the hash of the manifest used at install time does not match the computed Keccak-256 hash of the module's current manifest. This prevents unclean removal of modules that attempt to force a removal of a different module configuration than the one that was originally approved by the client for installation. To allow for removal of such modules, the modular account MAY implement the capability for the manifest to be encoded in the config field as a parameter. +- Revert if there is at least 1 other installed module that depends on validation functions added by this module. Modules used as dependencies SHOULD NOT be uninstalled while dependent modules exist. -The function SHOULD update account storage to reflect the uninstall via inspection functions, such as those defined by `IAccountLoupe`. Each dependency's record SHOULD also be updated to reflect that it has no longer has this plugin as a dependent. +The function SHOULD update account storage to reflect the uninstall via inspection functions, such as those defined by `IAccountLoupe`. Each dependency's record SHOULD also be updated to reflect that it has no longer has this module as a dependent. -The function MUST remove records for the plugin's manifest hash, dependencies, permitted function selectors, permitted external calls, and whether it can spend the account's native tokens. +The function MUST remove records for the module's manifest hash, dependencies, permitted function selectors, permitted external calls, and whether it can spend the account's native tokens. -The function MUST parse through the execution functions, validation functions, and hooks in the manifest and remove them from the modular account after resolving each `ManifestFunction` type. If multiple plugins added the same hook, it MUST persist until the last plugin is uninstalled. +The function MUST parse through the execution functions, validation functions, and hooks in the manifest and remove them from the modular account after resolving each `ManifestFunction` type. If multiple modules added the same hook, it MUST persist until the last module is uninstalled. -If the account stored the interface IDs provided in the manifest's `interfaceIds` during installation, it MUST remove them and update its `supportsInterface` behavior accordingly. If multiple plugins added the same interface ID, it MUST persist until the last plugin is uninstalled. +If the account stored the interface IDs provided in the manifest's `interfaceIds` during installation, it MUST remove them and update its `supportsInterface` behavior accordingly. If multiple modules added the same interface ID, it MUST persist until the last module is uninstalled. -Next, the function MUST call the plugin's `onUninstall` callback with the data provided in the `pluginUninstallData` parameter. This serves to clear the plugin state for the modular account. If `onUninstall` reverts, execution SHOULD continue to allow the uninstall to complete. +Next, the function MUST call the module's `onUninstall` callback with the data provided in the `moduleUninstallData` parameter. This serves to clear the module state for the modular account. If `onUninstall` reverts, execution SHOULD continue to allow the uninstall to complete. -Finally, the function MUST emit the event `PluginUninstalled` with the plugin's address and whether the `onUninstall` callback succeeded. +Finally, the function MUST emit the event `ModuleUninstalled` with the module's address and whether the `onUninstall` callback succeeded. -> **⚠️ Incorrectly uninstalled plugins can prevent uninstalls of their dependencies. Therefore, some form of validation that the uninstall step completely and correctly removes the plugin and its usage of dependencies is required.** +> **⚠️ Incorrectly uninstalled modules can prevent uninstalls of their dependencies. Therefore, some form of validation that the uninstall step completely and correctly removes the module and its usage of dependencies is required.** #### Calls to `validateUserOp` @@ -506,26 +506,26 @@ Then, the modular account MUST execute the validation function with the user ope #### Calls to execution functions -When a function other than a native function is called on an modular account, it MUST find the plugin configuration for the corresponding selector added via plugin installation. If no corresponding plugin is found, the modular account MUST revert. Otherwise, the following steps MUST be performed. +When a function other than a native function is called on an modular account, it MUST find the module configuration for the corresponding selector added via module installation. If no corresponding module is found, the modular account MUST revert. Otherwise, the following steps MUST be performed. -Additionally, when the modular account natively implements functions in `IPluginManager` and `IStandardExecutor`, the same following steps MUST be performed for those functions. Other native functions MAY perform these steps. +Additionally, when the modular account natively implements functions in `IModuleManager` and `IStandardExecutor`, the same following steps MUST be performed for those functions. Other native functions MAY perform these steps. The steps to perform are: - If the call is not from the `EntryPoint`, then find an associated runtime validation function. If one does not exist, execution MUST revert. The modular account MUST execute all pre runtime validation hooks, then the runtime validation function, with the `call` opcode. All of these functions MUST receive the caller, value, and execution function's calldata as parameters. If any of these functions revert, execution MUST revert. If any pre execution hooks are set to `PRE_HOOK_ALWAYS_DENY`, execution MUST revert. If the validation function is set to `RUNTIME_VALIDATION_ALWAYS_ALLOW`, the runtime validation function MUST be bypassed. -- If there are pre execution hooks defined for the execution function, execute those hooks with the caller, value, and execution function's calldata as parameters. If any of these hooks returns data, it MUST be preserved until the call to the post execution hook. The operation MUST be done with the `call` opcode. If there are duplicate pre execution hooks (i.e., hooks with identical `PluginEntity`s), run the hook only once. If any of these functions revert, execution MUST revert. +- If there are pre execution hooks defined for the execution function, execute those hooks with the caller, value, and execution function's calldata as parameters. If any of these hooks returns data, it MUST be preserved until the call to the post execution hook. The operation MUST be done with the `call` opcode. If there are duplicate pre execution hooks (i.e., hooks with identical `ModuleEntity`s), run the hook only once. If any of these functions revert, execution MUST revert. - Run the execution function. - If any post execution hooks are defined, run the functions. If a pre execution hook returned data to the account, that data MUST be passed as a parameter to the associated post execution hook. The operation MUST be done with the `call` opcode. If there are duplicate post execution hooks, run them once for each unique associated pre execution hook. For post execution hooks without an associated pre execution hook, run the hook only once. If any of these functions revert, execution MUST revert. -The set of hooks run for a given execution function MUST be the hooks specified by account state at the start of the execution phase. This is relevant for functions like `installPlugin` and `uninstallPlugin`, which modify the account state, and possibly other execution or native functions as well. +The set of hooks run for a given execution function MUST be the hooks specified by account state at the start of the execution phase. This is relevant for functions like `installModule` and `uninstallModule`, which modify the account state, and possibly other execution or native functions as well. -#### Calls made from plugins +#### Calls made from modules -Plugins MAY interact with other plugins and external addresses through the modular account using the functions defined in the `IPluginExecutor` interface. These functions MAY be called without a defined validation function, but the modular account MUST enforce these checks and behaviors: +Modules MAY interact with other modules and external addresses through the modular account using the functions defined in the `IModuleExecutor` interface. These functions MAY be called without a defined validation function, but the modular account MUST enforce these checks and behaviors: -The `executeFromPlugin` function MUST allow plugins to call execution functions installed by plugins on the modular account. Hooks matching the function selector provided in `data` MUST be called. If the calling plugin's manifest did not include the provided function selector within `permittedExecutionSelectors` at the time of installation, execution MUST revert. +The `executeFromModule` function MUST allow modules to call execution functions installed by modules on the modular account. Hooks matching the function selector provided in `data` MUST be called. If the calling module's manifest did not include the provided function selector within `permittedExecutionSelectors` at the time of installation, execution MUST revert. -The `executeFromPluginExternal` function MUST allow plugins to call external addresses as specified by its parameters on behalf of the modular account. If the calling plugin's manifest did not explicitly allow the external call within `permittedExternalCalls` at the time of installation, execution MUST revert. +The `executeFromModuleExternal` function MUST allow modules to call external addresses as specified by its parameters on behalf of the modular account. If the calling module's manifest did not explicitly allow the external call within `permittedExternalCalls` at the time of installation, execution MUST revert. ## Rationale @@ -533,7 +533,7 @@ ERC-4337 compatible accounts must implement the `IAccount` interface, which cons The function routing pattern of ERC-2535 is the logical starting point for achieving this extension into multi-functional accounts. It also meets our other primary design rationale of generalizing execution calls across multiple implementing contracts. However, a strict diamond pattern is constrained by its inability to customize validation schemes for specific execution functions in the context of `validateUserOp`, and its requirement of `delegatecall`. -This proposal includes several interfaces that build on ERC-4337 and are inspired by ERC-2535. First, we standardize a set of modular functions that allow smart contract developers greater flexibility in bundling validation, execution, and hook logic. We also propose interfaces that take inspiration from the diamond standard and provide methods for querying execution functions, validation functions, and hooks on a modular account. The rest of the interfaces describe a plugin's methods for exposing its modular functions and desired configuration, and the modular account's methods for installing and removing plugins and allowing execution across plugins and external addresses. +This proposal includes several interfaces that build on ERC-4337 and are inspired by ERC-2535. First, we standardize a set of modular functions that allow smart contract developers greater flexibility in bundling validation, execution, and hook logic. We also propose interfaces that take inspiration from the diamond standard and provide methods for querying execution functions, validation functions, and hooks on a modular account. The rest of the interfaces describe a module's methods for exposing its modular functions and desired configuration, and the modular account's methods for installing and removing modules and allowing execution across modules and external addresses. ## Backwards Compatibility diff --git a/standard/assets/eip-6900/MSCA_Shared_Components_Diagram.svg b/standard/assets/eip-6900/MSCA_Shared_Components_Diagram.svg index 836a8ad9..e443dd56 100644 --- a/standard/assets/eip-6900/MSCA_Shared_Components_Diagram.svg +++ b/standard/assets/eip-6900/MSCA_Shared_Components_Diagram.svg @@ -14,4 +14,4 @@ - Alice's MSCAUO / TxPluginHooksValidationExecutionPluginHooksValidationExecutionUO / TxBob's MSCA \ No newline at end of file + Alice's MSCAUO / TxModuleHooksValidationExecutionModuleHooksValidationExecutionUO / TxBob's MSCA \ No newline at end of file diff --git a/standard/assets/eip-6900/Modular_Account_Call_Flow.svg b/standard/assets/eip-6900/Modular_Account_Call_Flow.svg index c4112c33..8d7f7611 100644 --- a/standard/assets/eip-6900/Modular_Account_Call_Flow.svg +++ b/standard/assets/eip-6900/Modular_Account_Call_Flow.svg @@ -14,4 +14,4 @@ - Direct Call1 validation2. Execution Modular Account validateUserOpPre User Operation Validation Hook(s)User Operation Validation FunctionPre Execution Hook(s)Native Function / (Plugin) Execution FunctionPost Execution Hook(s)EntryPointEOA / SCRuntime Validation FunctionModular Account Call FlowPre Runtime Validation Hook(s) \ No newline at end of file + Direct Call1 validation2. Execution Modular Account validateUserOpPre User Operation Validation Hook(s)User Operation Validation FunctionPre Execution Hook(s)Native Function / (Module) Execution FunctionPost Execution Hook(s)EntryPointEOA / SCRuntime Validation FunctionModular Account Call FlowPre Runtime Validation Hook(s) \ No newline at end of file diff --git a/standard/assets/eip-6900/Plugin_Execution_Flow.svg b/standard/assets/eip-6900/Plugin_Execution_Flow.svg index 3a94b512..ed1ff580 100644 --- a/standard/assets/eip-6900/Plugin_Execution_Flow.svg +++ b/standard/assets/eip-6900/Plugin_Execution_Flow.svg @@ -18,4 +18,4 @@ - Permitted External Contracts & Methods executeFromPluginPre Execution Hook(s)Permitted Plugin Execution FunctionPost Execution Hook(s)PluginsPlugin Permission Check executeFromPluginExternal(Plugin) Execution Function1 calls plugin 2.2 calls external contracts through executeFromPluginExternalPlugin Permission CheckModular AccountPlugin Execution Flow2.1 calls other installed plugin through executeFromPluginPre executeFromPluginExternal Hook(s)Post executeFromPluginExternal Hook(s) \ No newline at end of file + Permitted External Contracts & Methods executeFromModulePre Execution Hook(s)Permitted Module Execution FunctionPost Execution Hook(s)ModulesModule Permission Check executeFromModuleExternal(Module) Execution Function1 calls module 2.2 calls external contracts through executeFromModuleExternalModule Permission CheckModular AccountModule Execution Flow2.1 calls other installed module through executeFromModulePre executeFromModuleExternal Hook(s)Post executeFromModuleExternal Hook(s) \ No newline at end of file diff --git a/test/account/AccountExecHooks.t.sol b/test/account/AccountExecHooks.t.sol index ba3a2098..052cea4e 100644 --- a/test/account/AccountExecHooks.t.sol +++ b/test/account/AccountExecHooks.t.sol @@ -3,17 +3,17 @@ pragma solidity ^0.8.19; import {IExecutionHook} from "../../src/interfaces/IExecutionHook.sol"; import { - IPlugin, + IModule, ManifestExecutionFunction, ManifestExecutionHook, - PluginManifest -} from "../../src/interfaces/IPlugin.sol"; + ModuleManifest +} from "../../src/interfaces/IModule.sol"; -import {MockPlugin} from "../mocks/MockPlugin.sol"; +import {MockModule} from "../mocks/MockModule.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; contract AccountExecHooksTest is AccountTestBase { - MockPlugin public mockPlugin1; + MockModule public mockModule1; bytes32 public manifestHash1; bytes32 public manifestHash2; @@ -22,11 +22,11 @@ contract AccountExecHooksTest is AccountTestBase { uint32 internal constant _POST_HOOK_FUNCTION_ID_2 = 2; uint32 internal constant _BOTH_HOOKS_FUNCTION_ID_3 = 3; - PluginManifest internal _m1; + ModuleManifest internal _m1; - event PluginInstalled(address indexed plugin, bytes32 manifestHash); - event PluginUninstalled(address indexed plugin, bool indexed callbacksSucceeded); - // emitted by MockPlugin + event ModuleInstalled(address indexed module, bytes32 manifestHash); + event ModuleUninstalled(address indexed module, bool indexed callbacksSucceeded); + // emitted by MockModule event ReceivedCall(bytes msgData, uint256 msgValue); function setUp() public { @@ -42,7 +42,7 @@ contract AccountExecHooksTest is AccountTestBase { } function test_preExecHook_install() public { - _installPlugin1WithHooks( + _installModule1WithHooks( ManifestExecutionHook({ executionSelector: _EXEC_SELECTOR, entityId: _PRE_HOOK_FUNCTION_ID_1, @@ -52,7 +52,7 @@ contract AccountExecHooksTest is AccountTestBase { ); } - /// @dev Plugin 1 hook pair: [1, null] + /// @dev Module 1 hook pair: [1, null] /// Expected execution: [1, null] function test_preExecHook_run() public { test_preExecHook_install(); @@ -66,7 +66,7 @@ contract AccountExecHooksTest is AccountTestBase { uint256(0), // msg.value in call to account abi.encodeWithSelector(_EXEC_SELECTOR) ), - 0 // msg value in call to plugin + 0 // msg value in call to module ); (bool success,) = address(account1).call(abi.encodeWithSelector(_EXEC_SELECTOR)); @@ -76,11 +76,11 @@ contract AccountExecHooksTest is AccountTestBase { function test_preExecHook_uninstall() public { test_preExecHook_install(); - _uninstallPlugin(mockPlugin1); + _uninstallModule(mockModule1); } function test_execHookPair_install() public { - _installPlugin1WithHooks( + _installModule1WithHooks( ManifestExecutionHook({ executionSelector: _EXEC_SELECTOR, entityId: _BOTH_HOOKS_FUNCTION_ID_3, @@ -90,7 +90,7 @@ contract AccountExecHooksTest is AccountTestBase { ); } - /// @dev Plugin 1 hook pair: [1, 2] + /// @dev Module 1 hook pair: [1, 2] /// Expected execution: [1, 2] function test_execHookPair_run() public { test_execHookPair_install(); @@ -105,7 +105,7 @@ contract AccountExecHooksTest is AccountTestBase { uint256(0), // msg.value in call to account abi.encodeWithSelector(_EXEC_SELECTOR) ), - 0 // msg value in call to plugin + 0 // msg value in call to module ); vm.expectEmit(true, true, true, true); // exec call @@ -114,7 +114,7 @@ contract AccountExecHooksTest is AccountTestBase { // post hook call emit ReceivedCall( abi.encodeCall(IExecutionHook.postExecutionHook, (_BOTH_HOOKS_FUNCTION_ID_3, "")), - 0 // msg value in call to plugin + 0 // msg value in call to module ); (bool success,) = address(account1).call(abi.encodeWithSelector(_EXEC_SELECTOR)); @@ -124,11 +124,11 @@ contract AccountExecHooksTest is AccountTestBase { function test_execHookPair_uninstall() public { test_execHookPair_install(); - _uninstallPlugin(mockPlugin1); + _uninstallModule(mockModule1); } function test_postOnlyExecHook_install() public { - _installPlugin1WithHooks( + _installModule1WithHooks( ManifestExecutionHook({ executionSelector: _EXEC_SELECTOR, entityId: _POST_HOOK_FUNCTION_ID_2, @@ -138,7 +138,7 @@ contract AccountExecHooksTest is AccountTestBase { ); } - /// @dev Plugin 1 hook pair: [null, 2] + /// @dev Module 1 hook pair: [null, 2] /// Expected execution: [null, 2] function test_postOnlyExecHook_run() public { test_postOnlyExecHook_install(); @@ -146,7 +146,7 @@ contract AccountExecHooksTest is AccountTestBase { vm.expectEmit(true, true, true, true); emit ReceivedCall( abi.encodeCall(IExecutionHook.postExecutionHook, (_POST_HOOK_FUNCTION_ID_2, "")), - 0 // msg value in call to plugin + 0 // msg value in call to module ); (bool success,) = address(account1).call(abi.encodeWithSelector(_EXEC_SELECTOR)); @@ -156,34 +156,34 @@ contract AccountExecHooksTest is AccountTestBase { function test_postOnlyExecHook_uninstall() public { test_postOnlyExecHook_install(); - _uninstallPlugin(mockPlugin1); + _uninstallModule(mockModule1); } - function _installPlugin1WithHooks(ManifestExecutionHook memory execHooks) internal { + function _installModule1WithHooks(ManifestExecutionHook memory execHooks) internal { _m1.executionHooks.push(execHooks); - mockPlugin1 = new MockPlugin(_m1); - manifestHash1 = keccak256(abi.encode(mockPlugin1.pluginManifest())); + mockModule1 = new MockModule(_m1); + manifestHash1 = keccak256(abi.encode(mockModule1.moduleManifest())); vm.expectEmit(true, true, true, true); - emit ReceivedCall(abi.encodeCall(IPlugin.onInstall, (bytes(""))), 0); + emit ReceivedCall(abi.encodeCall(IModule.onInstall, (bytes(""))), 0); vm.expectEmit(true, true, true, true); - emit PluginInstalled(address(mockPlugin1), manifestHash1); + emit ModuleInstalled(address(mockModule1), manifestHash1); vm.prank(address(entryPoint)); - account1.installPlugin({ - plugin: address(mockPlugin1), + account1.installModule({ + module: address(mockModule1), manifestHash: manifestHash1, - pluginInstallData: bytes("") + moduleInstallData: bytes("") }); } - function _uninstallPlugin(MockPlugin plugin) internal { + function _uninstallModule(MockModule module) internal { vm.expectEmit(true, true, true, true); - emit ReceivedCall(abi.encodeCall(IPlugin.onUninstall, (bytes(""))), 0); + emit ReceivedCall(abi.encodeCall(IModule.onUninstall, (bytes(""))), 0); vm.expectEmit(true, true, true, true); - emit PluginUninstalled(address(plugin), true); + emit ModuleUninstalled(address(module), true); vm.prank(address(entryPoint)); - account1.uninstallPlugin(address(plugin), bytes(""), bytes("")); + account1.uninstallModule(address(module), bytes(""), bytes("")); } } diff --git a/test/account/AccountLoupe.t.sol b/test/account/AccountLoupe.t.sol index f0670848..c4f4abc0 100644 --- a/test/account/AccountLoupe.t.sol +++ b/test/account/AccountLoupe.t.sol @@ -3,38 +3,38 @@ pragma solidity ^0.8.19; import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; -import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; +import {ModuleEntity, ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; -import {IPluginManager} from "../../src/interfaces/IPluginManager.sol"; +import {IModuleManager} from "../../src/interfaces/IModuleManager.sol"; import {IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; -import {ComprehensivePlugin} from "../mocks/plugins/ComprehensivePlugin.sol"; +import {ComprehensiveModule} from "../mocks/modules/ComprehensiveModule.sol"; import {CustomValidationTestBase} from "../utils/CustomValidationTestBase.sol"; contract AccountLoupeTest is CustomValidationTestBase { - ComprehensivePlugin public comprehensivePlugin; + ComprehensiveModule public comprehensiveModule; event ReceivedCall(bytes msgData, uint256 msgValue); function setUp() public { - comprehensivePlugin = new ComprehensivePlugin(); + comprehensiveModule = new ComprehensiveModule(); _customValidationSetup(); - bytes32 manifestHash = keccak256(abi.encode(comprehensivePlugin.pluginManifest())); + bytes32 manifestHash = keccak256(abi.encode(comprehensiveModule.moduleManifest())); vm.prank(address(entryPoint)); - account1.installPlugin(address(comprehensivePlugin), manifestHash, ""); + account1.installModule(address(comprehensiveModule), manifestHash, ""); } - function test_pluginLoupe_getInstalledPlugins_initial() public { - address[] memory plugins = account1.getInstalledPlugins(); + function test_moduleLoupe_getInstalledModules_initial() public { + address[] memory modules = account1.getInstalledModules(); - assertEq(plugins.length, 1); + assertEq(modules.length, 1); - assertEq(plugins[0], address(comprehensivePlugin)); + assertEq(modules[0], address(comprehensiveModule)); } - function test_pluginLoupe_getExecutionFunctionHandler_native() public { + function test_moduleLoupe_getExecutionFunctionHandler_native() public { bytes4[] memory selectorsToCheck = new bytes4[](5); selectorsToCheck[0] = IStandardExecutor.execute.selector; @@ -43,61 +43,61 @@ contract AccountLoupeTest is CustomValidationTestBase { selectorsToCheck[2] = UUPSUpgradeable.upgradeToAndCall.selector; - selectorsToCheck[3] = IPluginManager.installPlugin.selector; + selectorsToCheck[3] = IModuleManager.installModule.selector; - selectorsToCheck[4] = IPluginManager.uninstallPlugin.selector; + selectorsToCheck[4] = IModuleManager.uninstallModule.selector; for (uint256 i = 0; i < selectorsToCheck.length; i++) { - address plugin = account1.getExecutionFunctionHandler(selectorsToCheck[i]); + address module = account1.getExecutionFunctionHandler(selectorsToCheck[i]); - assertEq(plugin, address(account1)); + assertEq(module, address(account1)); } } - function test_pluginLoupe_getExecutionFunctionConfig_plugin() public { + function test_moduleLoupe_getExecutionFunctionConfig_module() public { bytes4[] memory selectorsToCheck = new bytes4[](1); - address[] memory expectedPluginAddress = new address[](1); + address[] memory expectedModuleAddress = new address[](1); - selectorsToCheck[0] = comprehensivePlugin.foo.selector; - expectedPluginAddress[0] = address(comprehensivePlugin); + selectorsToCheck[0] = comprehensiveModule.foo.selector; + expectedModuleAddress[0] = address(comprehensiveModule); for (uint256 i = 0; i < selectorsToCheck.length; i++) { - address plugin = account1.getExecutionFunctionHandler(selectorsToCheck[i]); + address module = account1.getExecutionFunctionHandler(selectorsToCheck[i]); - assertEq(plugin, expectedPluginAddress[i]); + assertEq(module, expectedModuleAddress[i]); } } - function test_pluginLoupe_getSelectors() public { - PluginEntity comprehensivePluginValidation = - PluginEntityLib.pack(address(comprehensivePlugin), uint32(ComprehensivePlugin.EntityId.VALIDATION)); + function test_moduleLoupe_getSelectors() public { + ModuleEntity comprehensiveModuleValidation = + ModuleEntityLib.pack(address(comprehensiveModule), uint32(ComprehensiveModule.EntityId.VALIDATION)); - bytes4[] memory selectors = account1.getSelectors(comprehensivePluginValidation); + bytes4[] memory selectors = account1.getSelectors(comprehensiveModuleValidation); assertEq(selectors.length, 1); - assertEq(selectors[0], comprehensivePlugin.foo.selector); + assertEq(selectors[0], comprehensiveModule.foo.selector); } - function test_pluginLoupe_getExecutionHooks() public { - ExecutionHook[] memory hooks = account1.getExecutionHooks(comprehensivePlugin.foo.selector); + function test_moduleLoupe_getExecutionHooks() public { + ExecutionHook[] memory hooks = account1.getExecutionHooks(comprehensiveModule.foo.selector); ExecutionHook[3] memory expectedHooks = [ ExecutionHook({ - hookFunction: PluginEntityLib.pack( - address(comprehensivePlugin), uint32(ComprehensivePlugin.EntityId.BOTH_EXECUTION_HOOKS) + hookFunction: ModuleEntityLib.pack( + address(comprehensiveModule), uint32(ComprehensiveModule.EntityId.BOTH_EXECUTION_HOOKS) ), isPreHook: true, isPostHook: true }), ExecutionHook({ - hookFunction: PluginEntityLib.pack( - address(comprehensivePlugin), uint32(ComprehensivePlugin.EntityId.PRE_EXECUTION_HOOK) + hookFunction: ModuleEntityLib.pack( + address(comprehensiveModule), uint32(ComprehensiveModule.EntityId.PRE_EXECUTION_HOOK) ), isPreHook: true, isPostHook: false }), ExecutionHook({ - hookFunction: PluginEntityLib.pack( - address(comprehensivePlugin), uint32(ComprehensivePlugin.EntityId.POST_EXECUTION_HOOK) + hookFunction: ModuleEntityLib.pack( + address(comprehensiveModule), uint32(ComprehensiveModule.EntityId.POST_EXECUTION_HOOK) ), isPreHook: false, isPostHook: true @@ -107,30 +107,30 @@ contract AccountLoupeTest is CustomValidationTestBase { assertEq(hooks.length, 3); for (uint256 i = 0; i < hooks.length; i++) { assertEq( - PluginEntity.unwrap(hooks[i].hookFunction), PluginEntity.unwrap(expectedHooks[i].hookFunction) + ModuleEntity.unwrap(hooks[i].hookFunction), ModuleEntity.unwrap(expectedHooks[i].hookFunction) ); assertEq(hooks[i].isPreHook, expectedHooks[i].isPreHook); assertEq(hooks[i].isPostHook, expectedHooks[i].isPostHook); } } - function test_pluginLoupe_getValidationHooks() public { - PluginEntity[] memory hooks = account1.getPreValidationHooks(_signerValidation); + function test_moduleLoupe_getValidationHooks() public { + ModuleEntity[] memory hooks = account1.getPreValidationHooks(_signerValidation); assertEq(hooks.length, 2); assertEq( - PluginEntity.unwrap(hooks[0]), - PluginEntity.unwrap( - PluginEntityLib.pack( - address(comprehensivePlugin), uint32(ComprehensivePlugin.EntityId.PRE_VALIDATION_HOOK_1) + ModuleEntity.unwrap(hooks[0]), + ModuleEntity.unwrap( + ModuleEntityLib.pack( + address(comprehensiveModule), uint32(ComprehensiveModule.EntityId.PRE_VALIDATION_HOOK_1) ) ) ); assertEq( - PluginEntity.unwrap(hooks[1]), - PluginEntity.unwrap( - PluginEntityLib.pack( - address(comprehensivePlugin), uint32(ComprehensivePlugin.EntityId.PRE_VALIDATION_HOOK_2) + ModuleEntity.unwrap(hooks[1]), + ModuleEntity.unwrap( + ModuleEntityLib.pack( + address(comprehensiveModule), uint32(ComprehensiveModule.EntityId.PRE_VALIDATION_HOOK_2) ) ) ); @@ -142,14 +142,14 @@ contract AccountLoupeTest is CustomValidationTestBase { internal virtual override - returns (PluginEntity, bool, bool, bytes4[] memory, bytes memory, bytes memory, bytes memory) + returns (ModuleEntity, bool, bool, bytes4[] memory, bytes memory, bytes memory, bytes memory) { - PluginEntity[] memory preValidationHooks = new PluginEntity[](2); - preValidationHooks[0] = PluginEntityLib.pack( - address(comprehensivePlugin), uint32(ComprehensivePlugin.EntityId.PRE_VALIDATION_HOOK_1) + ModuleEntity[] memory preValidationHooks = new ModuleEntity[](2); + preValidationHooks[0] = ModuleEntityLib.pack( + address(comprehensiveModule), uint32(ComprehensiveModule.EntityId.PRE_VALIDATION_HOOK_1) ); - preValidationHooks[1] = PluginEntityLib.pack( - address(comprehensivePlugin), uint32(ComprehensivePlugin.EntityId.PRE_VALIDATION_HOOK_2) + preValidationHooks[1] = ModuleEntityLib.pack( + address(comprehensiveModule), uint32(ComprehensiveModule.EntityId.PRE_VALIDATION_HOOK_2) ); bytes[] memory installDatas = new bytes[](2); diff --git a/test/account/AccountReturnData.t.sol b/test/account/AccountReturnData.t.sol index fa8bdf86..febaa54b 100644 --- a/test/account/AccountReturnData.t.sol +++ b/test/account/AccountReturnData.t.sol @@ -1,51 +1,51 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; +import {ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {Call} from "../../src/interfaces/IStandardExecutor.sol"; import { RegularResultContract, - ResultConsumerPlugin, - ResultCreatorPlugin -} from "../mocks/plugins/ReturnDataPluginMocks.sol"; + ResultConsumerModule, + ResultCreatorModule +} from "../mocks/modules/ReturnDataModuleMocks.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; import {TEST_DEFAULT_VALIDATION_ENTITY_ID} from "../utils/TestConstants.sol"; -// Tests all the different ways that return data can be read from plugins through an account +// Tests all the different ways that return data can be read from modules through an account contract AccountReturnDataTest is AccountTestBase { RegularResultContract public regularResultContract; - ResultCreatorPlugin public resultCreatorPlugin; - ResultConsumerPlugin public resultConsumerPlugin; + ResultCreatorModule public resultCreatorModule; + ResultConsumerModule public resultConsumerModule; function setUp() public { _transferOwnershipToTest(); regularResultContract = new RegularResultContract(); - resultCreatorPlugin = new ResultCreatorPlugin(); - resultConsumerPlugin = new ResultConsumerPlugin(resultCreatorPlugin, regularResultContract); + resultCreatorModule = new ResultCreatorModule(); + resultConsumerModule = new ResultConsumerModule(resultCreatorModule, regularResultContract); - // Add the result creator plugin to the account - bytes32 resultCreatorManifestHash = keccak256(abi.encode(resultCreatorPlugin.pluginManifest())); + // Add the result creator module to the account + bytes32 resultCreatorManifestHash = keccak256(abi.encode(resultCreatorModule.moduleManifest())); vm.prank(address(entryPoint)); - account1.installPlugin({ - plugin: address(resultCreatorPlugin), + account1.installModule({ + module: address(resultCreatorModule), manifestHash: resultCreatorManifestHash, - pluginInstallData: "" + moduleInstallData: "" }); - // Add the result consumer plugin to the account - bytes32 resultConsumerManifestHash = keccak256(abi.encode(resultConsumerPlugin.pluginManifest())); + // Add the result consumer module to the account + bytes32 resultConsumerManifestHash = keccak256(abi.encode(resultConsumerModule.moduleManifest())); vm.prank(address(entryPoint)); - account1.installPlugin({ - plugin: address(resultConsumerPlugin), + account1.installModule({ + module: address(resultConsumerModule), manifestHash: resultConsumerManifestHash, - pluginInstallData: "" + moduleInstallData: "" }); } - // Tests the ability to read the result of plugin execution functions via the account's fallback + // Tests the ability to read the result of module execution functions via the account's fallback function test_returnData_fallback() public { - bytes32 result = ResultCreatorPlugin(address(account1)).foo(); + bytes32 result = ResultCreatorModule(address(account1)).foo(); assertEq(result, keccak256("bar")); } @@ -58,7 +58,7 @@ contract AccountReturnDataTest is AccountTestBase { (address(regularResultContract), 0, abi.encodeCall(RegularResultContract.foo, ())) ), _encodeSignature( - PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), + ModuleEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, "" ) @@ -86,7 +86,7 @@ contract AccountReturnDataTest is AccountTestBase { bytes memory retData = account1.executeWithAuthorization( abi.encodeCall(account1.executeBatch, (calls)), _encodeSignature( - PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), + ModuleEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, "" ) @@ -102,15 +102,15 @@ contract AccountReturnDataTest is AccountTestBase { } // Tests the ability to read data via routing to fallback functions - function test_returnData_execFromPlugin_fallback() public { - bool result = ResultConsumerPlugin(address(account1)).checkResultFallback(keccak256("bar")); + function test_returnData_execFromModule_fallback() public { + bool result = ResultConsumerModule(address(account1)).checkResultFallback(keccak256("bar")); assertTrue(result); } // Tests the ability to read data via executeWithAuthorization function test_returnData_authorized_exec() public { - bool result = ResultConsumerPlugin(address(account1)).checkResultExecuteWithAuthorization( + bool result = ResultConsumerModule(address(account1)).checkResultExecuteWithAuthorization( address(regularResultContract), keccak256("bar") ); diff --git a/test/account/DirectCallsFromPlugin.t.sol b/test/account/DirectCallsFromModule.t.sol similarity index 59% rename from test/account/DirectCallsFromPlugin.t.sol rename to test/account/DirectCallsFromModule.t.sol index 9e56739a..b8d39728 100644 --- a/test/account/DirectCallsFromPlugin.t.sol +++ b/test/account/DirectCallsFromModule.t.sol @@ -1,53 +1,53 @@ pragma solidity ^0.8.19; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; +import {ModuleEntity, ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {ValidationConfig, ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; import {Call, IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; -import {DirectCallPlugin} from "../mocks/plugins/DirectCallPlugin.sol"; +import {DirectCallModule} from "../mocks/modules/DirectCallModule.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; -contract DirectCallsFromPluginTest is AccountTestBase { +contract DirectCallsFromModuleTest is AccountTestBase { using ValidationConfigLib for ValidationConfig; - DirectCallPlugin internal _plugin; - PluginEntity internal _pluginEntity; + DirectCallModule internal _module; + ModuleEntity internal _moduleEntity; function setUp() public { - _plugin = new DirectCallPlugin(); - assertFalse(_plugin.preHookRan()); - assertFalse(_plugin.postHookRan()); - _pluginEntity = PluginEntityLib.pack(address(_plugin), type(uint32).max); + _module = new DirectCallModule(); + assertFalse(_module.preHookRan()); + assertFalse(_module.postHookRan()); + _moduleEntity = ModuleEntityLib.pack(address(_module), type(uint32).max); } /* -------------------------------------------------------------------------- */ /* Negatives */ /* -------------------------------------------------------------------------- */ - function test_Fail_DirectCallPluginNotInstalled() external { - vm.prank(address(_plugin)); + function test_Fail_DirectCallModuleNotInstalled() external { + vm.prank(address(_module)); vm.expectRevert(_buildDirectCallDisallowedError(IStandardExecutor.execute.selector)); account1.execute(address(0), 0, ""); } - function test_Fail_DirectCallPluginUninstalled() external { - _installPlugin(); + function test_Fail_DirectCallModuleUninstalled() external { + _installModule(); - _uninstallPlugin(); + _uninstallModule(); - vm.prank(address(_plugin)); + vm.prank(address(_module)); vm.expectRevert(_buildDirectCallDisallowedError(IStandardExecutor.execute.selector)); account1.execute(address(0), 0, ""); } - function test_Fail_DirectCallPluginCallOtherSelector() external { - _installPlugin(); + function test_Fail_DirectCallModuleCallOtherSelector() external { + _installModule(); Call[] memory calls = new Call[](0); - vm.prank(address(_plugin)); + vm.prank(address(_module)); vm.expectRevert(_buildDirectCallDisallowedError(IStandardExecutor.executeBatch.selector)); account1.executeBatch(calls); } @@ -56,46 +56,46 @@ contract DirectCallsFromPluginTest is AccountTestBase { /* Positives */ /* -------------------------------------------------------------------------- */ - function test_Pass_DirectCallFromPluginPrank() external { - _installPlugin(); + function test_Pass_DirectCallFromModulePrank() external { + _installModule(); - vm.prank(address(_plugin)); + vm.prank(address(_module)); account1.execute(address(0), 0, ""); - assertTrue(_plugin.preHookRan()); - assertTrue(_plugin.postHookRan()); + assertTrue(_module.preHookRan()); + assertTrue(_module.postHookRan()); } - function test_Pass_DirectCallFromPluginCallback() external { - _installPlugin(); + function test_Pass_DirectCallFromModuleCallback() external { + _installModule(); - bytes memory encodedCall = abi.encodeCall(DirectCallPlugin.directCall, ()); + bytes memory encodedCall = abi.encodeCall(DirectCallModule.directCall, ()); vm.prank(address(entryPoint)); - bytes memory result = account1.execute(address(_plugin), 0, encodedCall); + bytes memory result = account1.execute(address(_module), 0, encodedCall); - assertTrue(_plugin.preHookRan()); - assertTrue(_plugin.postHookRan()); + assertTrue(_module.preHookRan()); + assertTrue(_module.postHookRan()); - // the directCall() function in the _plugin calls back into `execute()` with an encoded call back into the - // _plugin's getData() function. - assertEq(abi.decode(result, (bytes)), abi.encode(_plugin.getData())); + // the directCall() function in the _module calls back into `execute()` with an encoded call back into the + // _module's getData() function. + assertEq(abi.decode(result, (bytes)), abi.encode(_module.getData())); } - function test_Flow_DirectCallFromPluginSequence() external { + function test_Flow_DirectCallFromModuleSequence() external { // Install => Succeesfully call => uninstall => fail to call - _installPlugin(); + _installModule(); - vm.prank(address(_plugin)); + vm.prank(address(_module)); account1.execute(address(0), 0, ""); - assertTrue(_plugin.preHookRan()); - assertTrue(_plugin.postHookRan()); + assertTrue(_module.preHookRan()); + assertTrue(_module.postHookRan()); - _uninstallPlugin(); + _uninstallModule(); - vm.prank(address(_plugin)); + vm.prank(address(_module)); vm.expectRevert(_buildDirectCallDisallowedError(IStandardExecutor.execute.selector)); account1.execute(address(0), 0, ""); } @@ -104,27 +104,27 @@ contract DirectCallsFromPluginTest is AccountTestBase { /* Internals */ /* -------------------------------------------------------------------------- */ - function _installPlugin() internal { + function _installModule() internal { bytes4[] memory selectors = new bytes4[](1); selectors[0] = IStandardExecutor.execute.selector; ExecutionHook[] memory permissionHooks = new ExecutionHook[](1); bytes[] memory permissionHookInitDatas = new bytes[](1); - permissionHooks[0] = ExecutionHook({hookFunction: _pluginEntity, isPreHook: true, isPostHook: true}); + permissionHooks[0] = ExecutionHook({hookFunction: _moduleEntity, isPreHook: true, isPostHook: true}); bytes memory encodedPermissionHooks = abi.encode(permissionHooks, permissionHookInitDatas); vm.prank(address(entryPoint)); - ValidationConfig validationConfig = ValidationConfigLib.pack(_pluginEntity, false, false); + ValidationConfig validationConfig = ValidationConfigLib.pack(_moduleEntity, false, false); account1.installValidation(validationConfig, selectors, "", "", encodedPermissionHooks); } - function _uninstallPlugin() internal { + function _uninstallModule() internal { vm.prank(address(entryPoint)); - account1.uninstallValidation(_pluginEntity, "", abi.encode(new bytes[](0)), abi.encode(new bytes[](1))); + account1.uninstallValidation(_moduleEntity, "", abi.encode(new bytes[](0)), abi.encode(new bytes[](1))); } function _buildDirectCallDisallowedError(bytes4 selector) internal pure returns (bytes memory) { diff --git a/test/account/GlobalValidationTest.t.sol b/test/account/GlobalValidationTest.t.sol index 7ef7fdc5..b22d68eb 100644 --- a/test/account/GlobalValidationTest.t.sol +++ b/test/account/GlobalValidationTest.t.sol @@ -5,7 +5,7 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; +import {ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; @@ -27,7 +27,7 @@ contract GlobalValidationTest is AccountTestBase { vm.deal(address(account2), 100 ether); _signerValidation = - PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID); + ModuleEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID); ethRecipient = makeAddr("ethRecipient"); vm.deal(ethRecipient, 1 wei); diff --git a/test/account/MultiValidation.t.sol b/test/account/MultiValidation.t.sol index afb84f1a..772011bf 100644 --- a/test/account/MultiValidation.t.sol +++ b/test/account/MultiValidation.t.sol @@ -9,11 +9,11 @@ import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntry import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; +import {ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; -import {PluginEntity} from "../../src/interfaces/IPluginManager.sol"; +import {ModuleEntity} from "../../src/interfaces/IModuleManager.sol"; import {IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; -import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; +import {SingleSignerValidation} from "../../src/modules/validation/SingleSignerValidation.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; import {TEST_DEFAULT_VALIDATION_ENTITY_ID} from "../utils/TestConstants.sol"; @@ -43,9 +43,9 @@ contract MultiValidationTest is AccountTestBase { "" ); - PluginEntity[] memory validations = new PluginEntity[](2); - validations[0] = PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID); - validations[1] = PluginEntityLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID); + ModuleEntity[] memory validations = new ModuleEntity[](2); + validations[0] = ModuleEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID); + validations[1] = ModuleEntityLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID); bytes4[] memory selectors0 = account1.getSelectors(validations[0]); bytes4[] memory selectors1 = account1.getSelectors(validations[1]); @@ -72,7 +72,7 @@ contract MultiValidationTest is AccountTestBase { account1.executeWithAuthorization( abi.encodeCall(IStandardExecutor.execute, (address(0), 0, "")), _encodeSignature( - PluginEntityLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, "" + ModuleEntityLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, "" ) ); @@ -80,7 +80,7 @@ contract MultiValidationTest is AccountTestBase { account1.executeWithAuthorization( abi.encodeCall(IStandardExecutor.execute, (address(0), 0, "")), _encodeSignature( - PluginEntityLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, "" + ModuleEntityLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, "" ) ); } @@ -106,7 +106,7 @@ contract MultiValidationTest is AccountTestBase { bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); userOp.signature = _encodeSignature( - PluginEntityLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID), + ModuleEntityLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, abi.encodePacked(r, s, v) ); @@ -121,7 +121,7 @@ contract MultiValidationTest is AccountTestBase { userOp.nonce = 1; (v, r, s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); userOp.signature = _encodeSignature( - PluginEntityLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID), + ModuleEntityLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, abi.encodePacked(r, s, v) ); diff --git a/test/account/PerHookData.t.sol b/test/account/PerHookData.t.sol index c4405d38..15cefbd9 100644 --- a/test/account/PerHookData.t.sol +++ b/test/account/PerHookData.t.sol @@ -6,23 +6,23 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; +import {ModuleEntity, ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {Counter} from "../mocks/Counter.sol"; -import {MockAccessControlHookPlugin} from "../mocks/plugins/MockAccessControlHookPlugin.sol"; +import {MockAccessControlHookModule} from "../mocks/modules/MockAccessControlHookModule.sol"; import {CustomValidationTestBase} from "../utils/CustomValidationTestBase.sol"; contract PerHookDataTest is CustomValidationTestBase { using MessageHashUtils for bytes32; - MockAccessControlHookPlugin internal _accessControlHookPlugin; + MockAccessControlHookModule internal _accessControlHookModule; Counter internal _counter; function setUp() public { _counter = new Counter(); - _accessControlHookPlugin = new MockAccessControlHookPlugin(); + _accessControlHookModule = new MockAccessControlHookModule(); _customValidationSetup(); } @@ -217,8 +217,8 @@ contract PerHookDataTest is CustomValidationTestBase { vm.expectRevert( abi.encodeWithSelector( UpgradeableModularAccount.PreRuntimeValidationHookFailed.selector, - _accessControlHookPlugin, - uint32(MockAccessControlHookPlugin.EntityId.PRE_VALIDATION_HOOK), + _accessControlHookModule, + uint32(MockAccessControlHookModule.EntityId.PRE_VALIDATION_HOOK), abi.encodeWithSignature("Error(string)", "Proof doesn't match target") ) ); @@ -236,8 +236,8 @@ contract PerHookDataTest is CustomValidationTestBase { vm.expectRevert( abi.encodeWithSelector( UpgradeableModularAccount.PreRuntimeValidationHookFailed.selector, - _accessControlHookPlugin, - uint32(MockAccessControlHookPlugin.EntityId.PRE_VALIDATION_HOOK), + _accessControlHookModule, + uint32(MockAccessControlHookModule.EntityId.PRE_VALIDATION_HOOK), abi.encodeWithSignature("Error(string)", "Proof doesn't match target") ) ); @@ -278,8 +278,8 @@ contract PerHookDataTest is CustomValidationTestBase { vm.expectRevert( abi.encodeWithSelector( UpgradeableModularAccount.PreRuntimeValidationHookFailed.selector, - _accessControlHookPlugin, - uint32(MockAccessControlHookPlugin.EntityId.PRE_VALIDATION_HOOK), + _accessControlHookModule, + uint32(MockAccessControlHookModule.EntityId.PRE_VALIDATION_HOOK), abi.encodeWithSignature("Error(string)", "Target not allowed") ) ); @@ -330,13 +330,13 @@ contract PerHookDataTest is CustomValidationTestBase { internal virtual override - returns (PluginEntity, bool, bool, bytes4[] memory, bytes memory, bytes memory, bytes memory) + returns (ModuleEntity, bool, bool, bytes4[] memory, bytes memory, bytes memory, bytes memory) { - PluginEntity accessControlHook = PluginEntityLib.pack( - address(_accessControlHookPlugin), uint32(MockAccessControlHookPlugin.EntityId.PRE_VALIDATION_HOOK) + ModuleEntity accessControlHook = ModuleEntityLib.pack( + address(_accessControlHookModule), uint32(MockAccessControlHookModule.EntityId.PRE_VALIDATION_HOOK) ); - PluginEntity[] memory preValidationHooks = new PluginEntity[](1); + ModuleEntity[] memory preValidationHooks = new ModuleEntity[](1); preValidationHooks[0] = accessControlHook; bytes[] memory preValidationHookData = new bytes[](1); diff --git a/test/account/PermittedCallPermissions.t.sol b/test/account/PermittedCallPermissions.t.sol index 607c50b4..1c378e9e 100644 --- a/test/account/PermittedCallPermissions.t.sol +++ b/test/account/PermittedCallPermissions.t.sol @@ -3,43 +3,43 @@ pragma solidity ^0.8.19; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {PermittedCallerPlugin} from "../mocks/plugins/PermittedCallMocks.sol"; -import {ResultCreatorPlugin} from "../mocks/plugins/ReturnDataPluginMocks.sol"; +import {PermittedCallerModule} from "../mocks/modules/PermittedCallMocks.sol"; +import {ResultCreatorModule} from "../mocks/modules/ReturnDataModuleMocks.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; contract PermittedCallPermissionsTest is AccountTestBase { - ResultCreatorPlugin public resultCreatorPlugin; + ResultCreatorModule public resultCreatorModule; - PermittedCallerPlugin public permittedCallerPlugin; + PermittedCallerModule public permittedCallerModule; function setUp() public { _transferOwnershipToTest(); - resultCreatorPlugin = new ResultCreatorPlugin(); + resultCreatorModule = new ResultCreatorModule(); - // Initialize the permitted caller plugins, which will attempt to use the permissions system to authorize + // Initialize the permitted caller modules, which will attempt to use the permissions system to authorize // calls. - permittedCallerPlugin = new PermittedCallerPlugin(); + permittedCallerModule = new PermittedCallerModule(); - // Add the result creator plugin to the account - bytes32 resultCreatorManifestHash = keccak256(abi.encode(resultCreatorPlugin.pluginManifest())); + // Add the result creator module to the account + bytes32 resultCreatorManifestHash = keccak256(abi.encode(resultCreatorModule.moduleManifest())); vm.prank(address(entryPoint)); - account1.installPlugin({ - plugin: address(resultCreatorPlugin), + account1.installModule({ + module: address(resultCreatorModule), manifestHash: resultCreatorManifestHash, - pluginInstallData: "" + moduleInstallData: "" }); - // Add the permitted caller plugin to the account - bytes32 permittedCallerManifestHash = keccak256(abi.encode(permittedCallerPlugin.pluginManifest())); + // Add the permitted caller module to the account + bytes32 permittedCallerManifestHash = keccak256(abi.encode(permittedCallerModule.moduleManifest())); vm.prank(address(entryPoint)); - account1.installPlugin({ - plugin: address(permittedCallerPlugin), + account1.installModule({ + module: address(permittedCallerModule), manifestHash: permittedCallerManifestHash, - pluginInstallData: "" + moduleInstallData: "" }); } function test_permittedCall_Allowed() public { - bytes memory result = PermittedCallerPlugin(address(account1)).usePermittedCallAllowed(); + bytes memory result = PermittedCallerModule(address(account1)).usePermittedCallAllowed(); bytes32 actual = abi.decode(result, (bytes32)); assertEq(actual, keccak256("bar")); @@ -48,9 +48,9 @@ contract PermittedCallPermissionsTest is AccountTestBase { function test_permittedCall_NotAllowed() public { vm.expectRevert( abi.encodeWithSelector( - UpgradeableModularAccount.ValidationFunctionMissing.selector, ResultCreatorPlugin.bar.selector + UpgradeableModularAccount.ValidationFunctionMissing.selector, ResultCreatorModule.bar.selector ) ); - PermittedCallerPlugin(address(account1)).usePermittedCallNotAllowed(); + PermittedCallerModule(address(account1)).usePermittedCallNotAllowed(); } } diff --git a/test/account/SelfCallAuthorization.t.sol b/test/account/SelfCallAuthorization.t.sol index 743a0241..aae886eb 100644 --- a/test/account/SelfCallAuthorization.t.sol +++ b/test/account/SelfCallAuthorization.t.sol @@ -7,41 +7,41 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; +import {ModuleEntity, ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import {Call, IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; -import {ComprehensivePlugin} from "../mocks/plugins/ComprehensivePlugin.sol"; +import {ComprehensiveModule} from "../mocks/modules/ComprehensiveModule.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; contract SelfCallAuthorizationTest is AccountTestBase { - ComprehensivePlugin public comprehensivePlugin; + ComprehensiveModule public comprehensiveModule; - PluginEntity public comprehensivePluginValidation; + ModuleEntity public comprehensiveModuleValidation; function setUp() public { - // install the comprehensive plugin to get new exec functions with different validations configured. + // install the comprehensive module to get new exec functions with different validations configured. - comprehensivePlugin = new ComprehensivePlugin(); + comprehensiveModule = new ComprehensiveModule(); - bytes32 manifestHash = keccak256(abi.encode(comprehensivePlugin.pluginManifest())); + bytes32 manifestHash = keccak256(abi.encode(comprehensiveModule.moduleManifest())); vm.prank(address(entryPoint)); - account1.installPlugin(address(comprehensivePlugin), manifestHash, ""); + account1.installModule(address(comprehensiveModule), manifestHash, ""); - comprehensivePluginValidation = - PluginEntityLib.pack(address(comprehensivePlugin), uint32(ComprehensivePlugin.EntityId.VALIDATION)); + comprehensiveModuleValidation = + ModuleEntityLib.pack(address(comprehensiveModule), uint32(ComprehensiveModule.EntityId.VALIDATION)); } function test_selfCallFails_userOp() public { // Uses global validation _runUserOp( - abi.encodeCall(ComprehensivePlugin.foo, ()), + abi.encodeCall(ComprehensiveModule.foo, ()), abi.encodeWithSelector( IEntryPoint.FailedOpWithRevert.selector, 0, "AA23 reverted", abi.encodeWithSelector( - UpgradeableModularAccount.ValidationFunctionMissing.selector, ComprehensivePlugin.foo.selector + UpgradeableModularAccount.ValidationFunctionMissing.selector, ComprehensiveModule.foo.selector ) ) ); @@ -50,13 +50,13 @@ contract SelfCallAuthorizationTest is AccountTestBase { function test_selfCallFails_execUserOp() public { // Uses global validation _runUserOp( - abi.encodePacked(IAccountExecute.executeUserOp.selector, abi.encodeCall(ComprehensivePlugin.foo, ())), + abi.encodePacked(IAccountExecute.executeUserOp.selector, abi.encodeCall(ComprehensiveModule.foo, ())), abi.encodeWithSelector( IEntryPoint.FailedOpWithRevert.selector, 0, "AA23 reverted", abi.encodeWithSelector( - UpgradeableModularAccount.ValidationFunctionMissing.selector, ComprehensivePlugin.foo.selector + UpgradeableModularAccount.ValidationFunctionMissing.selector, ComprehensiveModule.foo.selector ) ) ); @@ -65,19 +65,19 @@ contract SelfCallAuthorizationTest is AccountTestBase { function test_selfCallFails_runtime() public { // Uses global validation _runtimeCall( - abi.encodeCall(ComprehensivePlugin.foo, ()), + abi.encodeCall(ComprehensiveModule.foo, ()), abi.encodeWithSelector( - UpgradeableModularAccount.ValidationFunctionMissing.selector, ComprehensivePlugin.foo.selector + UpgradeableModularAccount.ValidationFunctionMissing.selector, ComprehensiveModule.foo.selector ) ); } function test_selfCallPrivilegeEscalation_prevented_userOp() public { - // Using global validation, self-call bypasses custom validation needed for ComprehensivePlugin.foo + // Using global validation, self-call bypasses custom validation needed for ComprehensiveModule.foo _runUserOp( abi.encodeCall( UpgradeableModularAccount.execute, - (address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())) + (address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())) ), abi.encodeWithSelector( IEntryPoint.FailedOpWithRevert.selector, @@ -88,7 +88,7 @@ contract SelfCallAuthorizationTest is AccountTestBase { ); Call[] memory calls = new Call[](1); - calls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())); + calls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())); _runUserOp( abi.encodeCall(IStandardExecutor.executeBatch, (calls)), @@ -97,20 +97,20 @@ contract SelfCallAuthorizationTest is AccountTestBase { 0, "AA23 reverted", abi.encodeWithSelector( - UpgradeableModularAccount.ValidationFunctionMissing.selector, ComprehensivePlugin.foo.selector + UpgradeableModularAccount.ValidationFunctionMissing.selector, ComprehensiveModule.foo.selector ) ) ); } function test_selfCallPrivilegeEscalation_prevented_execUserOp() public { - // Using global validation, self-call bypasses custom validation needed for ComprehensivePlugin.foo + // Using global validation, self-call bypasses custom validation needed for ComprehensiveModule.foo _runUserOp( abi.encodePacked( IAccountExecute.executeUserOp.selector, abi.encodeCall( UpgradeableModularAccount.execute, - (address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())) + (address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())) ) ), abi.encodeWithSelector( @@ -122,7 +122,7 @@ contract SelfCallAuthorizationTest is AccountTestBase { ); Call[] memory calls = new Call[](1); - calls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())); + calls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())); _runUserOp( abi.encodePacked( @@ -133,29 +133,29 @@ contract SelfCallAuthorizationTest is AccountTestBase { 0, "AA23 reverted", abi.encodeWithSelector( - UpgradeableModularAccount.ValidationFunctionMissing.selector, ComprehensivePlugin.foo.selector + UpgradeableModularAccount.ValidationFunctionMissing.selector, ComprehensiveModule.foo.selector ) ) ); } function test_selfCallPrivilegeEscalation_prevented_runtime() public { - // Using global validation, self-call bypasses custom validation needed for ComprehensivePlugin.foo + // Using global validation, self-call bypasses custom validation needed for ComprehensiveModule.foo _runtimeCall( abi.encodeCall( UpgradeableModularAccount.execute, - (address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())) + (address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())) ), abi.encodeWithSelector(UpgradeableModularAccount.SelfCallRecursionDepthExceeded.selector) ); Call[] memory calls = new Call[](1); - calls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())); + calls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())); _runtimeExecBatchExpFail( calls, abi.encodeWithSelector( - UpgradeableModularAccount.ValidationFunctionMissing.selector, ComprehensivePlugin.foo.selector + UpgradeableModularAccount.ValidationFunctionMissing.selector, ComprehensiveModule.foo.selector ) ); } @@ -164,17 +164,17 @@ contract SelfCallAuthorizationTest is AccountTestBase { _enableBatchValidation(); Call[] memory calls = new Call[](2); - calls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())); - calls[1] = Call(address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())); + calls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())); + calls[1] = Call(address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())); - PackedUserOperation memory userOp = _generateUserOpWithComprehensivePluginValidation( + PackedUserOperation memory userOp = _generateUserOpWithComprehensiveModuleValidation( abi.encodeCall(IStandardExecutor.executeBatch, (calls)) ); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; - vm.expectCall(address(comprehensivePlugin), abi.encodeCall(ComprehensivePlugin.foo, ()), 2); + vm.expectCall(address(comprehensiveModule), abi.encodeCall(ComprehensiveModule.foo, ()), 2); entryPoint.handleOps(userOps, beneficiary); } @@ -182,10 +182,10 @@ contract SelfCallAuthorizationTest is AccountTestBase { _enableBatchValidation(); Call[] memory calls = new Call[](2); - calls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())); - calls[1] = Call(address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())); + calls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())); + calls[1] = Call(address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())); - PackedUserOperation memory userOp = _generateUserOpWithComprehensivePluginValidation( + PackedUserOperation memory userOp = _generateUserOpWithComprehensiveModuleValidation( abi.encodePacked( IAccountExecute.executeUserOp.selector, abi.encodeCall(IStandardExecutor.executeBatch, (calls)) ) @@ -194,7 +194,7 @@ contract SelfCallAuthorizationTest is AccountTestBase { PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; - vm.expectCall(address(comprehensivePlugin), abi.encodeCall(ComprehensivePlugin.foo, ()), 2); + vm.expectCall(address(comprehensiveModule), abi.encodeCall(ComprehensiveModule.foo, ()), 2); entryPoint.handleOps(userOps, beneficiary); } @@ -202,13 +202,13 @@ contract SelfCallAuthorizationTest is AccountTestBase { _enableBatchValidation(); Call[] memory calls = new Call[](2); - calls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())); - calls[1] = Call(address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())); + calls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())); + calls[1] = Call(address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())); - vm.expectCall(address(comprehensivePlugin), abi.encodeCall(ComprehensivePlugin.foo, ()), 2); + vm.expectCall(address(comprehensiveModule), abi.encodeCall(ComprehensiveModule.foo, ()), 2); account1.executeWithAuthorization( abi.encodeCall(IStandardExecutor.executeBatch, (calls)), - _encodeSignature(comprehensivePluginValidation, SELECTOR_ASSOCIATED_VALIDATION, "") + _encodeSignature(comprehensiveModuleValidation, SELECTOR_ASSOCIATED_VALIDATION, "") ); } @@ -216,12 +216,12 @@ contract SelfCallAuthorizationTest is AccountTestBase { _enableBatchValidation(); Call[] memory innerCalls = new Call[](1); - innerCalls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())); + innerCalls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())); Call[] memory outerCalls = new Call[](1); outerCalls[0] = Call(address(account1), 0, abi.encodeCall(IStandardExecutor.executeBatch, (innerCalls))); - PackedUserOperation memory userOp = _generateUserOpWithComprehensivePluginValidation( + PackedUserOperation memory userOp = _generateUserOpWithComprehensiveModuleValidation( abi.encodeCall(IStandardExecutor.executeBatch, (outerCalls)) ); @@ -243,12 +243,12 @@ contract SelfCallAuthorizationTest is AccountTestBase { _enableBatchValidation(); Call[] memory innerCalls = new Call[](1); - innerCalls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())); + innerCalls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())); Call[] memory outerCalls = new Call[](1); outerCalls[0] = Call(address(account1), 0, abi.encodeCall(IStandardExecutor.executeBatch, (innerCalls))); - PackedUserOperation memory userOp = _generateUserOpWithComprehensivePluginValidation( + PackedUserOperation memory userOp = _generateUserOpWithComprehensiveModuleValidation( abi.encodePacked( IAccountExecute.executeUserOp.selector, abi.encodeCall(IStandardExecutor.executeBatch, (outerCalls)) @@ -273,7 +273,7 @@ contract SelfCallAuthorizationTest is AccountTestBase { _enableBatchValidation(); Call[] memory innerCalls = new Call[](1); - innerCalls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())); + innerCalls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())); Call[] memory outerCalls = new Call[](1); outerCalls[0] = Call(address(account1), 0, abi.encodeCall(IStandardExecutor.executeBatch, (innerCalls))); @@ -281,12 +281,12 @@ contract SelfCallAuthorizationTest is AccountTestBase { vm.expectRevert(abi.encodeWithSelector(UpgradeableModularAccount.SelfCallRecursionDepthExceeded.selector)); account1.executeWithAuthorization( abi.encodeCall(IStandardExecutor.executeBatch, (outerCalls)), - _encodeSignature(comprehensivePluginValidation, SELECTOR_ASSOCIATED_VALIDATION, "") + _encodeSignature(comprehensiveModuleValidation, SELECTOR_ASSOCIATED_VALIDATION, "") ); } function _enableBatchValidation() internal { - // Extend ComprehensivePlugin's validation function to also validate `executeBatch`, to allow the + // Extend ComprehensiveModule's validation function to also validate `executeBatch`, to allow the // self-call. bytes4[] memory selectors = new bytes4[](1); @@ -296,13 +296,13 @@ contract SelfCallAuthorizationTest is AccountTestBase { account1.executeWithAuthorization( abi.encodeCall( UpgradeableModularAccount.installValidation, - (ValidationConfigLib.pack(comprehensivePluginValidation, false, false), selectors, "", "", "") + (ValidationConfigLib.pack(comprehensiveModuleValidation, false, false), selectors, "", "", "") ), _encodeSignature(_signerValidation, GLOBAL_VALIDATION, "") ); } - function _generateUserOpWithComprehensivePluginValidation(bytes memory callData) + function _generateUserOpWithComprehensiveModuleValidation(bytes memory callData) internal view returns (PackedUserOperation memory) @@ -318,9 +318,9 @@ contract SelfCallAuthorizationTest is AccountTestBase { gasFees: _encodeGas(1, 1), paymasterAndData: hex"", signature: _encodeSignature( - comprehensivePluginValidation, + comprehensiveModuleValidation, SELECTOR_ASSOCIATED_VALIDATION, - // Comprehensive plugin's validation function doesn't actually check anything, so we don't need to + // Comprehensive module's validation function doesn't actually check anything, so we don't need to // sign anything. "" ) diff --git a/test/account/UpgradeableModularAccount.t.sol b/test/account/UpgradeableModularAccount.t.sol index 8b77332f..76a1c4f0 100644 --- a/test/account/UpgradeableModularAccount.t.sol +++ b/test/account/UpgradeableModularAccount.t.sol @@ -9,21 +9,21 @@ import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; -import {PluginManagerInternals} from "../../src/account/PluginManagerInternals.sol"; +import {ModuleManagerInternals} from "../../src/account/ModuleManagerInternals.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {IAccountLoupe} from "../../src/interfaces/IAccountLoupe.sol"; -import {PluginManifest} from "../../src/interfaces/IPlugin.sol"; -import {IPluginManager} from "../../src/interfaces/IPluginManager.sol"; +import {ModuleManifest} from "../../src/interfaces/IModule.sol"; +import {IModuleManager} from "../../src/interfaces/IModuleManager.sol"; import {Call} from "../../src/interfaces/IStandardExecutor.sol"; -import {TokenReceiverPlugin} from "../../src/plugins/TokenReceiverPlugin.sol"; -import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; +import {TokenReceiverModule} from "../../src/modules/TokenReceiverModule.sol"; +import {SingleSignerValidation} from "../../src/modules/validation/SingleSignerValidation.sol"; import {Counter} from "../mocks/Counter.sol"; -import {MockPlugin} from "../mocks/MockPlugin.sol"; -import {ComprehensivePlugin} from "../mocks/plugins/ComprehensivePlugin.sol"; +import {MockModule} from "../mocks/MockModule.sol"; +import {ComprehensiveModule} from "../mocks/modules/ComprehensiveModule.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; import {TEST_DEFAULT_VALIDATION_ENTITY_ID} from "../utils/TestConstants.sol"; @@ -31,7 +31,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { using ECDSA for bytes32; using MessageHashUtils for bytes32; - TokenReceiverPlugin public tokenReceiverPlugin; + TokenReceiverModule public tokenReceiverModule; // A separate account and owner that isn't deployed yet, used to test initcode address public owner2; @@ -40,14 +40,14 @@ contract UpgradeableModularAccountTest is AccountTestBase { address public ethRecipient; Counter public counter; - PluginManifest internal _manifest; + ModuleManifest internal _manifest; - event PluginInstalled(address indexed plugin, bytes32 manifestHash); - event PluginUninstalled(address indexed plugin, bool indexed callbacksSucceeded); + event ModuleInstalled(address indexed module, bytes32 manifestHash); + event ModuleUninstalled(address indexed module, bool indexed callbacksSucceeded); event ReceivedCall(bytes msgData, uint256 msgValue); function setUp() public { - tokenReceiverPlugin = _deployTokenReceiverPlugin(); + tokenReceiverModule = _deployTokenReceiverModule(); (owner2, owner2Key) = makeAddrAndKey("owner2"); @@ -236,163 +236,163 @@ contract UpgradeableModularAccountTest is AccountTestBase { assertEq(ethRecipient.balance, 2 wei); } - function test_installPlugin() public { + function test_installModule() public { vm.startPrank(address(entryPoint)); - bytes32 manifestHash = keccak256(abi.encode(tokenReceiverPlugin.pluginManifest())); + bytes32 manifestHash = keccak256(abi.encode(tokenReceiverModule.moduleManifest())); vm.expectEmit(true, true, true, true); - emit PluginInstalled(address(tokenReceiverPlugin), manifestHash); - IPluginManager(account1).installPlugin({ - plugin: address(tokenReceiverPlugin), + emit ModuleInstalled(address(tokenReceiverModule), manifestHash); + IModuleManager(account1).installModule({ + module: address(tokenReceiverModule), manifestHash: manifestHash, - pluginInstallData: abi.encode(uint48(1 days)) + moduleInstallData: abi.encode(uint48(1 days)) }); - address[] memory plugins = IAccountLoupe(account1).getInstalledPlugins(); - assertEq(plugins.length, 1); - assertEq(plugins[0], address(tokenReceiverPlugin)); + address[] memory modules = IAccountLoupe(account1).getInstalledModules(); + assertEq(modules.length, 1); + assertEq(modules[0], address(tokenReceiverModule)); } - function test_installPlugin_PermittedCallSelectorNotInstalled() public { + function test_installModule_PermittedCallSelectorNotInstalled() public { vm.startPrank(address(entryPoint)); - PluginManifest memory m; + ModuleManifest memory m; - MockPlugin mockPluginWithBadPermittedExec = new MockPlugin(m); - bytes32 manifestHash = keccak256(abi.encode(mockPluginWithBadPermittedExec.pluginManifest())); + MockModule mockModuleWithBadPermittedExec = new MockModule(m); + bytes32 manifestHash = keccak256(abi.encode(mockModuleWithBadPermittedExec.moduleManifest())); - IPluginManager(account1).installPlugin({ - plugin: address(mockPluginWithBadPermittedExec), + IModuleManager(account1).installModule({ + module: address(mockModuleWithBadPermittedExec), manifestHash: manifestHash, - pluginInstallData: "" + moduleInstallData: "" }); } - function test_installPlugin_invalidManifest() public { + function test_installModule_invalidManifest() public { vm.startPrank(address(entryPoint)); - vm.expectRevert(abi.encodeWithSelector(PluginManagerInternals.InvalidPluginManifest.selector)); - IPluginManager(account1).installPlugin({ - plugin: address(tokenReceiverPlugin), + vm.expectRevert(abi.encodeWithSelector(ModuleManagerInternals.InvalidModuleManifest.selector)); + IModuleManager(account1).installModule({ + module: address(tokenReceiverModule), manifestHash: bytes32(0), - pluginInstallData: abi.encode(uint48(1 days)) + moduleInstallData: abi.encode(uint48(1 days)) }); } - function test_installPlugin_interfaceNotSupported() public { + function test_installModule_interfaceNotSupported() public { vm.startPrank(address(entryPoint)); - address badPlugin = address(1); + address badModule = address(1); vm.expectRevert( - abi.encodeWithSelector(PluginManagerInternals.PluginInterfaceNotSupported.selector, address(badPlugin)) + abi.encodeWithSelector(ModuleManagerInternals.ModuleInterfaceNotSupported.selector, address(badModule)) ); - IPluginManager(account1).installPlugin({ - plugin: address(badPlugin), + IModuleManager(account1).installModule({ + module: address(badModule), manifestHash: bytes32(0), - pluginInstallData: "" + moduleInstallData: "" }); } - function test_installPlugin_alreadyInstalled() public { + function test_installModule_alreadyInstalled() public { vm.startPrank(address(entryPoint)); - bytes32 manifestHash = keccak256(abi.encode(tokenReceiverPlugin.pluginManifest())); - IPluginManager(account1).installPlugin({ - plugin: address(tokenReceiverPlugin), + bytes32 manifestHash = keccak256(abi.encode(tokenReceiverModule.moduleManifest())); + IModuleManager(account1).installModule({ + module: address(tokenReceiverModule), manifestHash: manifestHash, - pluginInstallData: abi.encode(uint48(1 days)) + moduleInstallData: abi.encode(uint48(1 days)) }); vm.expectRevert( abi.encodeWithSelector( - PluginManagerInternals.PluginAlreadyInstalled.selector, address(tokenReceiverPlugin) + ModuleManagerInternals.ModuleAlreadyInstalled.selector, address(tokenReceiverModule) ) ); - IPluginManager(account1).installPlugin({ - plugin: address(tokenReceiverPlugin), + IModuleManager(account1).installModule({ + module: address(tokenReceiverModule), manifestHash: manifestHash, - pluginInstallData: abi.encode(uint48(1 days)) + moduleInstallData: abi.encode(uint48(1 days)) }); } - function test_uninstallPlugin_default() public { + function test_uninstallModule_default() public { vm.startPrank(address(entryPoint)); - ComprehensivePlugin plugin = new ComprehensivePlugin(); - bytes32 manifestHash = keccak256(abi.encode(plugin.pluginManifest())); - IPluginManager(account1).installPlugin({ - plugin: address(plugin), + ComprehensiveModule module = new ComprehensiveModule(); + bytes32 manifestHash = keccak256(abi.encode(module.moduleManifest())); + IModuleManager(account1).installModule({ + module: address(module), manifestHash: manifestHash, - pluginInstallData: "" + moduleInstallData: "" }); vm.expectEmit(true, true, true, true); - emit PluginUninstalled(address(plugin), true); - IPluginManager(account1).uninstallPlugin({plugin: address(plugin), config: "", pluginUninstallData: ""}); - address[] memory plugins = IAccountLoupe(account1).getInstalledPlugins(); - assertEq(plugins.length, 0); + emit ModuleUninstalled(address(module), true); + IModuleManager(account1).uninstallModule({module: address(module), config: "", moduleUninstallData: ""}); + address[] memory modules = IAccountLoupe(account1).getInstalledModules(); + assertEq(modules.length, 0); } - function test_uninstallPlugin_manifestParameter() public { + function test_uninstallModule_manifestParameter() public { vm.startPrank(address(entryPoint)); - ComprehensivePlugin plugin = new ComprehensivePlugin(); - bytes memory serializedManifest = abi.encode(plugin.pluginManifest()); + ComprehensiveModule module = new ComprehensiveModule(); + bytes memory serializedManifest = abi.encode(module.moduleManifest()); bytes32 manifestHash = keccak256(serializedManifest); - IPluginManager(account1).installPlugin({ - plugin: address(plugin), + IModuleManager(account1).installModule({ + module: address(module), manifestHash: manifestHash, - pluginInstallData: "" + moduleInstallData: "" }); vm.expectEmit(true, true, true, true); - emit PluginUninstalled(address(plugin), true); - IPluginManager(account1).uninstallPlugin({ - plugin: address(plugin), + emit ModuleUninstalled(address(module), true); + IModuleManager(account1).uninstallModule({ + module: address(module), config: serializedManifest, - pluginUninstallData: "" + moduleUninstallData: "" }); - address[] memory plugins = IAccountLoupe(account1).getInstalledPlugins(); - assertEq(plugins.length, 0); + address[] memory modules = IAccountLoupe(account1).getInstalledModules(); + assertEq(modules.length, 0); } - function test_uninstallPlugin_invalidManifestFails() public { + function test_uninstallModule_invalidManifestFails() public { vm.startPrank(address(entryPoint)); - ComprehensivePlugin plugin = new ComprehensivePlugin(); - bytes memory serializedManifest = abi.encode(plugin.pluginManifest()); + ComprehensiveModule module = new ComprehensiveModule(); + bytes memory serializedManifest = abi.encode(module.moduleManifest()); bytes32 manifestHash = keccak256(serializedManifest); - IPluginManager(account1).installPlugin({ - plugin: address(plugin), + IModuleManager(account1).installModule({ + module: address(module), manifestHash: manifestHash, - pluginInstallData: "" + moduleInstallData: "" }); // Attempt to uninstall with a blank _manifest - PluginManifest memory blankManifest; + ModuleManifest memory blankManifest; - vm.expectRevert(abi.encodeWithSelector(PluginManagerInternals.InvalidPluginManifest.selector)); - IPluginManager(account1).uninstallPlugin({ - plugin: address(plugin), + vm.expectRevert(abi.encodeWithSelector(ModuleManagerInternals.InvalidModuleManifest.selector)); + IModuleManager(account1).uninstallModule({ + module: address(module), config: abi.encode(blankManifest), - pluginUninstallData: "" + moduleUninstallData: "" }); - address[] memory plugins = IAccountLoupe(account1).getInstalledPlugins(); - assertEq(plugins.length, 1); - assertEq(plugins[0], address(plugin)); + address[] memory modules = IAccountLoupe(account1).getInstalledModules(); + assertEq(modules.length, 1); + assertEq(modules[0], address(module)); } - function _installPluginWithExecHooks() internal returns (MockPlugin plugin) { + function _installModuleWithExecHooks() internal returns (MockModule module) { vm.startPrank(address(entryPoint)); - plugin = new MockPlugin(_manifest); - bytes32 manifestHash = keccak256(abi.encode(plugin.pluginManifest())); + module = new MockModule(_manifest); + bytes32 manifestHash = keccak256(abi.encode(module.moduleManifest())); - IPluginManager(account1).installPlugin({ - plugin: address(plugin), + IModuleManager(account1).installModule({ + module: address(module), manifestHash: manifestHash, - pluginInstallData: "" + moduleInstallData: "" }); vm.stopPrank(); diff --git a/test/account/ValidationIntersection.t.sol b/test/account/ValidationIntersection.t.sol index 3bdaa1b6..82a8fa7f 100644 --- a/test/account/ValidationIntersection.t.sol +++ b/test/account/ValidationIntersection.t.sol @@ -4,65 +4,65 @@ pragma solidity ^0.8.19; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; +import {ModuleEntity, ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import { - MockBaseUserOpValidationPlugin, - MockUserOpValidation1HookPlugin, - MockUserOpValidation2HookPlugin, - MockUserOpValidationPlugin -} from "../mocks/plugins/ValidationPluginMocks.sol"; + MockBaseUserOpValidationModule, + MockUserOpValidation1HookModule, + MockUserOpValidation2HookModule, + MockUserOpValidationModule +} from "../mocks/modules/ValidationModuleMocks.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; contract ValidationIntersectionTest is AccountTestBase { uint256 internal constant _SIG_VALIDATION_FAILED = 1; - MockUserOpValidationPlugin public noHookPlugin; - MockUserOpValidation1HookPlugin public oneHookPlugin; - MockUserOpValidation2HookPlugin public twoHookPlugin; + MockUserOpValidationModule public noHookModule; + MockUserOpValidation1HookModule public oneHookModule; + MockUserOpValidation2HookModule public twoHookModule; - PluginEntity public noHookValidation; - PluginEntity public oneHookValidation; - PluginEntity public twoHookValidation; + ModuleEntity public noHookValidation; + ModuleEntity public oneHookValidation; + ModuleEntity public twoHookValidation; function setUp() public { - noHookPlugin = new MockUserOpValidationPlugin(); - oneHookPlugin = new MockUserOpValidation1HookPlugin(); - twoHookPlugin = new MockUserOpValidation2HookPlugin(); + noHookModule = new MockUserOpValidationModule(); + oneHookModule = new MockUserOpValidation1HookModule(); + twoHookModule = new MockUserOpValidation2HookModule(); - noHookValidation = PluginEntityLib.pack({ - addr: address(noHookPlugin), - entityId: uint32(MockBaseUserOpValidationPlugin.EntityId.USER_OP_VALIDATION) + noHookValidation = ModuleEntityLib.pack({ + addr: address(noHookModule), + entityId: uint32(MockBaseUserOpValidationModule.EntityId.USER_OP_VALIDATION) }); - oneHookValidation = PluginEntityLib.pack({ - addr: address(oneHookPlugin), - entityId: uint32(MockBaseUserOpValidationPlugin.EntityId.USER_OP_VALIDATION) + oneHookValidation = ModuleEntityLib.pack({ + addr: address(oneHookModule), + entityId: uint32(MockBaseUserOpValidationModule.EntityId.USER_OP_VALIDATION) }); - twoHookValidation = PluginEntityLib.pack({ - addr: address(twoHookPlugin), - entityId: uint32(MockBaseUserOpValidationPlugin.EntityId.USER_OP_VALIDATION) + twoHookValidation = ModuleEntityLib.pack({ + addr: address(twoHookModule), + entityId: uint32(MockBaseUserOpValidationModule.EntityId.USER_OP_VALIDATION) }); vm.startPrank(address(entryPoint)); - account1.installPlugin({ - plugin: address(noHookPlugin), - manifestHash: keccak256(abi.encode(noHookPlugin.pluginManifest())), - pluginInstallData: "" + account1.installModule({ + module: address(noHookModule), + manifestHash: keccak256(abi.encode(noHookModule.moduleManifest())), + moduleInstallData: "" }); - account1.installPlugin({ - plugin: address(oneHookPlugin), - manifestHash: keccak256(abi.encode(oneHookPlugin.pluginManifest())), - pluginInstallData: "" + account1.installModule({ + module: address(oneHookModule), + manifestHash: keccak256(abi.encode(oneHookModule.moduleManifest())), + moduleInstallData: "" }); // TODO: change with new install flow // temporary fix to add the pre-validation hook - PluginEntity[] memory preValidationHooks = new PluginEntity[](1); - preValidationHooks[0] = PluginEntityLib.pack({ - addr: address(oneHookPlugin), - entityId: uint32(MockBaseUserOpValidationPlugin.EntityId.PRE_VALIDATION_HOOK_1) + ModuleEntity[] memory preValidationHooks = new ModuleEntity[](1); + preValidationHooks[0] = ModuleEntityLib.pack({ + addr: address(oneHookModule), + entityId: uint32(MockBaseUserOpValidationModule.EntityId.PRE_VALIDATION_HOOK_1) }); bytes[] memory installDatas = new bytes[](1); account1.installValidation( @@ -72,20 +72,20 @@ contract ValidationIntersectionTest is AccountTestBase { abi.encode(preValidationHooks, installDatas), bytes("") ); - account1.installPlugin({ - plugin: address(twoHookPlugin), - manifestHash: keccak256(abi.encode(twoHookPlugin.pluginManifest())), - pluginInstallData: "" + account1.installModule({ + module: address(twoHookModule), + manifestHash: keccak256(abi.encode(twoHookModule.moduleManifest())), + moduleInstallData: "" }); // temporary fix to add the pre-validation hook - preValidationHooks = new PluginEntity[](2); - preValidationHooks[0] = PluginEntityLib.pack({ - addr: address(twoHookPlugin), - entityId: uint32(MockBaseUserOpValidationPlugin.EntityId.PRE_VALIDATION_HOOK_1) + preValidationHooks = new ModuleEntity[](2); + preValidationHooks[0] = ModuleEntityLib.pack({ + addr: address(twoHookModule), + entityId: uint32(MockBaseUserOpValidationModule.EntityId.PRE_VALIDATION_HOOK_1) }); - preValidationHooks[1] = PluginEntityLib.pack({ - addr: address(twoHookPlugin), - entityId: uint32(MockBaseUserOpValidationPlugin.EntityId.PRE_VALIDATION_HOOK_2) + preValidationHooks[1] = ModuleEntityLib.pack({ + addr: address(twoHookModule), + entityId: uint32(MockBaseUserOpValidationModule.EntityId.PRE_VALIDATION_HOOK_2) }); installDatas = new bytes[](2); account1.installValidation( @@ -99,10 +99,10 @@ contract ValidationIntersectionTest is AccountTestBase { } function testFuzz_validationIntersect_single(uint256 validationData) public { - noHookPlugin.setValidationData(validationData); + noHookModule.setValidationData(validationData); PackedUserOperation memory userOp; - userOp.callData = bytes.concat(noHookPlugin.foo.selector); + userOp.callData = bytes.concat(noHookModule.foo.selector); userOp.signature = _encodeSignature(noHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); @@ -113,13 +113,13 @@ contract ValidationIntersectionTest is AccountTestBase { } function test_validationIntersect_authorizer_sigfail_validationFunction() public { - oneHookPlugin.setValidationData( + oneHookModule.setValidationData( _SIG_VALIDATION_FAILED, 0 // returns OK ); PackedUserOperation memory userOp; - userOp.callData = bytes.concat(oneHookPlugin.bar.selector); + userOp.callData = bytes.concat(oneHookModule.bar.selector); userOp.signature = _encodeSignature(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); @@ -131,13 +131,13 @@ contract ValidationIntersectionTest is AccountTestBase { } function test_validationIntersect_authorizer_sigfail_hook() public { - oneHookPlugin.setValidationData( + oneHookModule.setValidationData( 0, // returns OK _SIG_VALIDATION_FAILED ); PackedUserOperation memory userOp; - userOp.callData = bytes.concat(oneHookPlugin.bar.selector); + userOp.callData = bytes.concat(oneHookModule.bar.selector); userOp.signature = _encodeSignature(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); @@ -155,12 +155,12 @@ contract ValidationIntersectionTest is AccountTestBase { uint48 start2 = uint48(15); uint48 end2 = uint48(25); - oneHookPlugin.setValidationData( + oneHookModule.setValidationData( _packValidationRes(address(0), start1, end1), _packValidationRes(address(0), start2, end2) ); PackedUserOperation memory userOp; - userOp.callData = bytes.concat(oneHookPlugin.bar.selector); + userOp.callData = bytes.concat(oneHookModule.bar.selector); userOp.signature = _encodeSignature(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); @@ -177,12 +177,12 @@ contract ValidationIntersectionTest is AccountTestBase { uint48 start2 = uint48(15); uint48 end2 = uint48(25); - oneHookPlugin.setValidationData( + oneHookModule.setValidationData( _packValidationRes(address(0), start2, end2), _packValidationRes(address(0), start1, end1) ); PackedUserOperation memory userOp; - userOp.callData = bytes.concat(oneHookPlugin.bar.selector); + userOp.callData = bytes.concat(oneHookModule.bar.selector); userOp.signature = _encodeSignature(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); @@ -195,14 +195,14 @@ contract ValidationIntersectionTest is AccountTestBase { function test_validationIntersect_revert_unexpectedAuthorizer() public { address badAuthorizer = makeAddr("badAuthorizer"); - oneHookPlugin.setValidationData( + oneHookModule.setValidationData( 0, // returns OK uint256(uint160(badAuthorizer)) // returns an aggregator, which preValidation hooks are not allowed to // do. ); PackedUserOperation memory userOp; - userOp.callData = bytes.concat(oneHookPlugin.bar.selector); + userOp.callData = bytes.concat(oneHookModule.bar.selector); userOp.signature = _encodeSignature(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); @@ -210,8 +210,8 @@ contract ValidationIntersectionTest is AccountTestBase { vm.expectRevert( abi.encodeWithSelector( UpgradeableModularAccount.UnexpectedAggregator.selector, - address(oneHookPlugin), - MockBaseUserOpValidationPlugin.EntityId.PRE_VALIDATION_HOOK_1, + address(oneHookModule), + MockBaseUserOpValidationModule.EntityId.PRE_VALIDATION_HOOK_1, badAuthorizer ) ); @@ -221,13 +221,13 @@ contract ValidationIntersectionTest is AccountTestBase { function test_validationIntersect_validAuthorizer() public { address goodAuthorizer = makeAddr("goodAuthorizer"); - oneHookPlugin.setValidationData( + oneHookModule.setValidationData( uint256(uint160(goodAuthorizer)), // returns a valid aggregator 0 // returns OK ); PackedUserOperation memory userOp; - userOp.callData = bytes.concat(oneHookPlugin.bar.selector); + userOp.callData = bytes.concat(oneHookModule.bar.selector); userOp.signature = _encodeSignature(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); @@ -246,12 +246,12 @@ contract ValidationIntersectionTest is AccountTestBase { address goodAuthorizer = makeAddr("goodAuthorizer"); - oneHookPlugin.setValidationData( + oneHookModule.setValidationData( _packValidationRes(goodAuthorizer, start1, end1), _packValidationRes(address(0), start2, end2) ); PackedUserOperation memory userOp; - userOp.callData = bytes.concat(oneHookPlugin.bar.selector); + userOp.callData = bytes.concat(oneHookModule.bar.selector); userOp.signature = _encodeSignature(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); @@ -268,14 +268,14 @@ contract ValidationIntersectionTest is AccountTestBase { uint48 start2 = uint48(15); uint48 end2 = uint48(25); - twoHookPlugin.setValidationData( + twoHookModule.setValidationData( 0, // returns OK _packValidationRes(address(0), start1, end1), _packValidationRes(address(0), start2, end2) ); PackedUserOperation memory userOp; - userOp.callData = bytes.concat(twoHookPlugin.baz.selector); + userOp.callData = bytes.concat(twoHookModule.baz.selector); userOp.signature = _encodeSignature(twoHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); @@ -286,14 +286,14 @@ contract ValidationIntersectionTest is AccountTestBase { } function test_validationIntersect_multiplePreValidationHooksSigFail() public { - twoHookPlugin.setValidationData( + twoHookModule.setValidationData( 0, // returns OK 0, // returns OK _SIG_VALIDATION_FAILED ); PackedUserOperation memory userOp; - userOp.callData = bytes.concat(twoHookPlugin.baz.selector); + userOp.callData = bytes.concat(twoHookModule.baz.selector); userOp.signature = _encodeSignature(twoHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); diff --git a/test/libraries/KnowSelectors.t.sol b/test/libraries/KnowSelectors.t.sol index 7ce0910d..48bd17e9 100644 --- a/test/libraries/KnowSelectors.t.sol +++ b/test/libraries/KnowSelectors.t.sol @@ -6,7 +6,7 @@ import {IPaymaster} from "@eth-infinitism/account-abstraction/interfaces/IPaymas import {Test} from "forge-std/Test.sol"; import {KnownSelectors} from "../../src/helpers/KnownSelectors.sol"; -import {IPlugin} from "../../src/interfaces/IPlugin.sol"; +import {IModule} from "../../src/interfaces/IModule.sol"; contract KnownSelectorsTest is Test { function test_isNativeFunction() public { @@ -17,7 +17,7 @@ contract KnownSelectorsTest is Test { assertTrue(KnownSelectors.isErc4337Function(IPaymaster.validatePaymasterUserOp.selector)); } - function test_isIPluginFunction() public { - assertTrue(KnownSelectors.isIPluginFunction(IPlugin.pluginMetadata.selector)); + function test_isIModuleFunction() public { + assertTrue(KnownSelectors.isIModuleFunction(IModule.moduleMetadata.selector)); } } diff --git a/test/libraries/PluginEntityLib.t.sol b/test/libraries/ModuleEntityLib.t.sol similarity index 56% rename from test/libraries/PluginEntityLib.t.sol rename to test/libraries/ModuleEntityLib.t.sol index c93e1b61..205e53b9 100644 --- a/test/libraries/PluginEntityLib.t.sol +++ b/test/libraries/ModuleEntityLib.t.sol @@ -3,29 +3,29 @@ pragma solidity ^0.8.19; import {Test} from "forge-std/Test.sol"; -import {PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; -import {PluginEntity} from "../../src/interfaces/IPluginManager.sol"; +import {ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; +import {ModuleEntity} from "../../src/interfaces/IModuleManager.sol"; -contract PluginEntityLibTest is Test { - using PluginEntityLib for PluginEntity; +contract ModuleEntityLibTest is Test { + using ModuleEntityLib for ModuleEntity; - function testFuzz_pluginEntity_packing(address addr, uint32 entityId) public { + function testFuzz_moduleEntity_packing(address addr, uint32 entityId) public { // console.log("addr: ", addr); // console.log("entityId: ", vm.toString(entityId)); - PluginEntity fr = PluginEntityLib.pack(addr, entityId); - // console.log("packed: ", vm.toString(PluginEntity.unwrap(fr))); - (address addr2, uint32 entityId2) = PluginEntityLib.unpack(fr); + 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_pluginEntity_operators(PluginEntity a, PluginEntity b) public { + function testFuzz_moduleEntity_operators(ModuleEntity a, ModuleEntity b) public { assertTrue(a.eq(a)); assertTrue(b.eq(b)); - if (PluginEntity.unwrap(a) == PluginEntity.unwrap(b)) { + if (ModuleEntity.unwrap(a) == ModuleEntity.unwrap(b)) { assertTrue(a.eq(b)); assertTrue(b.eq(a)); assertFalse(a.notEq(b)); diff --git a/test/mocks/MockPlugin.sol b/test/mocks/MockModule.sol similarity index 75% rename from test/mocks/MockPlugin.sol rename to test/mocks/MockModule.sol index ef79c018..c6866bd4 100644 --- a/test/mocks/MockPlugin.sol +++ b/test/mocks/MockModule.sol @@ -4,10 +4,10 @@ pragma solidity ^0.8.19; import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; import {IExecutionHook} from "../../src/interfaces/IExecutionHook.sol"; -import {IPlugin, PluginManifest, PluginMetadata} from "../../src/interfaces/IPlugin.sol"; +import {IModule, ModuleManifest, ModuleMetadata} from "../../src/interfaces/IModule.sol"; import {IValidation} from "../../src/interfaces/IValidation.sol"; -contract MockPlugin is ERC165 { +contract MockModule is ERC165 { // It's super inefficient to hold the entire abi-encoded manifest in storage, but this is fine since it's // just a mock. Note that the reason we do this is to allow copying the entire contents of the manifest // into storage in a single line, since solidity fails to compile with memory -> storage copying of nested @@ -17,41 +17,41 @@ contract MockPlugin is ERC165 { // struct ManifestAssociatedFunction memory[] memory to storage not yet supported. bytes internal _manifest; - string internal constant _NAME = "Mock Plugin Modifiable"; + string internal constant _NAME = "Mock Module Modifiable"; string internal constant _VERSION = "1.0.0"; string internal constant _AUTHOR = "ERC-6900 Authors"; event ReceivedCall(bytes msgData, uint256 msgValue); // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Plugin interface functions ┃ + // ┃ Module interface functions ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - constructor(PluginManifest memory _pluginManifest) { - _manifest = abi.encode(_pluginManifest); + constructor(ModuleManifest memory _moduleManifest) { + _manifest = abi.encode(_moduleManifest); } - function _getManifest() internal view returns (PluginManifest memory) { - PluginManifest memory m = abi.decode(_manifest, (PluginManifest)); + function _getManifest() internal view returns (ModuleManifest memory) { + ModuleManifest memory m = abi.decode(_manifest, (ModuleManifest)); return m; } - function _castToPure(function() internal view returns (PluginManifest memory) fnIn) + function _castToPure(function() internal view returns (ModuleManifest memory) fnIn) internal pure - returns (function() internal pure returns (PluginManifest memory) fnOut) + returns (function() internal pure returns (ModuleManifest memory) fnOut) { assembly ("memory-safe") { fnOut := fnIn } } - function pluginManifest() external pure returns (PluginManifest memory) { + function moduleManifest() external pure returns (ModuleManifest memory) { return _castToPure(_getManifest)(); } - function pluginMetadata() external pure returns (PluginMetadata memory) { - PluginMetadata memory metadata; + function moduleMetadata() external pure returns (ModuleMetadata memory) { + ModuleMetadata memory metadata; metadata.name = _NAME; metadata.version = _VERSION; metadata.author = _AUTHOR; @@ -65,14 +65,14 @@ contract MockPlugin is ERC165 { /// /// This function call must use less than 30 000 gas. /// - /// Supporting the IPlugin interface is a requirement for plugin installation. This is also used + /// Supporting the IModule interface is a requirement for module installation. This is also used /// by the modular account to prevent standard execution functions `execute` and `executeBatch` from - /// making calls to plugins. + /// making calls to modules. /// @param interfaceId The interface ID to check for support. /// @return True if the contract supports `interfaceId`. function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { - return interfaceId == type(IPlugin).interfaceId || super.supportsInterface(interfaceId); + return interfaceId == type(IModule).interfaceId || super.supportsInterface(interfaceId); } receive() external payable {} diff --git a/test/mocks/SingleSignerFactoryFixture.sol b/test/mocks/SingleSignerFactoryFixture.sol index b751d440..98dfc0b8 100644 --- a/test/mocks/SingleSignerFactoryFixture.sol +++ b/test/mocks/SingleSignerFactoryFixture.sol @@ -7,7 +7,7 @@ import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; -import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; +import {SingleSignerValidation} from "../../src/modules/validation/SingleSignerValidation.sol"; import {OptimizedTest} from "../utils/OptimizedTest.sol"; import {TEST_DEFAULT_VALIDATION_ENTITY_ID} from "../utils/TestConstants.sol"; @@ -45,17 +45,17 @@ contract SingleSignerFactoryFixture is OptimizedTest { // short circuit if exists if (addr.code.length == 0) { - bytes memory pluginInstallData = abi.encode(TEST_DEFAULT_VALIDATION_ENTITY_ID, owner); + bytes memory moduleInstallData = abi.encode(TEST_DEFAULT_VALIDATION_ENTITY_ID, owner); // not necessary to check return addr since next call will fail if so new ERC1967Proxy{salt: getSalt(owner, salt)}(address(accountImplementation), ""); - // point proxy to actual implementation and init plugins + // point proxy to actual implementation and init modules UpgradeableModularAccount(payable(addr)).initializeWithValidation( ValidationConfigLib.pack( address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID, true, true ), new bytes4[](0), - pluginInstallData, + moduleInstallData, "", "" ); diff --git a/test/mocks/plugins/ComprehensivePlugin.sol b/test/mocks/modules/ComprehensiveModule.sol similarity index 89% rename from test/mocks/plugins/ComprehensivePlugin.sol rename to test/mocks/modules/ComprehensiveModule.sol index e4233cf2..bb73b5db 100644 --- a/test/mocks/plugins/ComprehensivePlugin.sol +++ b/test/mocks/modules/ComprehensiveModule.sol @@ -8,16 +8,16 @@ import { ManifestExecutionFunction, ManifestExecutionHook, ManifestValidation, - PluginManifest, - PluginMetadata -} from "../../../src/interfaces/IPlugin.sol"; -import {PluginManifest} from "../../../src/interfaces/IPlugin.sol"; + ModuleManifest, + ModuleMetadata +} from "../../../src/interfaces/IModule.sol"; +import {ModuleManifest} from "../../../src/interfaces/IModule.sol"; import {IValidation} from "../../../src/interfaces/IValidation.sol"; import {IValidationHook} from "../../../src/interfaces/IValidationHook.sol"; -import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; +import {BaseModule} from "../../../src/modules/BaseModule.sol"; -contract ComprehensivePlugin is IValidation, IValidationHook, IExecutionHook, BasePlugin { +contract ComprehensiveModule is IValidation, IValidationHook, IExecutionHook, BaseModule { enum EntityId { PRE_VALIDATION_HOOK_1, PRE_VALIDATION_HOOK_2, @@ -28,7 +28,7 @@ contract ComprehensivePlugin is IValidation, IValidationHook, IExecutionHook, Ba SIG_VALIDATION } - string internal constant _NAME = "Comprehensive Plugin"; + string internal constant _NAME = "Comprehensive Module"; string internal constant _VERSION = "1.0.0"; string internal constant _AUTHOR = "ERC-6900 Authors"; @@ -39,7 +39,7 @@ contract ComprehensivePlugin is IValidation, IValidationHook, IExecutionHook, Ba function foo() external {} // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Plugin interface functions ┃ + // ┃ Module interface functions ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ function onInstall(bytes calldata) external override {} @@ -130,8 +130,8 @@ contract ComprehensivePlugin is IValidation, IValidationHook, IExecutionHook, Ba revert NotImplemented(); } - function pluginManifest() external pure override returns (PluginManifest memory) { - PluginManifest memory manifest; + function moduleManifest() external pure override returns (ModuleManifest memory) { + ModuleManifest memory manifest; manifest.executionFunctions = new ManifestExecutionFunction[](1); manifest.executionFunctions[0] = ManifestExecutionFunction({ @@ -174,8 +174,8 @@ contract ComprehensivePlugin is IValidation, IValidationHook, IExecutionHook, Ba return manifest; } - function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { - PluginMetadata memory metadata; + function moduleMetadata() external pure virtual override returns (ModuleMetadata memory) { + ModuleMetadata memory metadata; metadata.name = _NAME; metadata.version = _VERSION; metadata.author = _AUTHOR; diff --git a/test/mocks/plugins/DirectCallPlugin.sol b/test/mocks/modules/DirectCallModule.sol similarity index 77% rename from test/mocks/plugins/DirectCallPlugin.sol rename to test/mocks/modules/DirectCallModule.sol index 7ccda4f0..9e0f611d 100644 --- a/test/mocks/plugins/DirectCallPlugin.sol +++ b/test/mocks/modules/DirectCallModule.sol @@ -2,12 +2,12 @@ pragma solidity ^0.8.19; import {IExecutionHook} from "../../../src/interfaces/IExecutionHook.sol"; -import {PluginManifest, PluginMetadata} from "../../../src/interfaces/IPlugin.sol"; +import {ModuleManifest, ModuleMetadata} from "../../../src/interfaces/IModule.sol"; import {IStandardExecutor} from "../../../src/interfaces/IStandardExecutor.sol"; -import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; +import {BaseModule} from "../../../src/modules/BaseModule.sol"; -contract DirectCallPlugin is BasePlugin, IExecutionHook { +contract DirectCallModule is BaseModule, IExecutionHook { bool public preHookRan = false; bool public postHookRan = false; @@ -15,7 +15,7 @@ contract DirectCallPlugin is BasePlugin, IExecutionHook { function onUninstall(bytes calldata) external override {} - function pluginManifest() external pure override returns (PluginManifest memory) {} + function moduleManifest() external pure override returns (ModuleManifest memory) {} function directCall() external returns (bytes memory) { return IStandardExecutor(msg.sender).execute(address(this), 0, abi.encodeCall(this.getData, ())); @@ -25,7 +25,7 @@ contract DirectCallPlugin is BasePlugin, IExecutionHook { return hex"04546b"; } - function pluginMetadata() external pure override returns (PluginMetadata memory) {} + function moduleMetadata() external pure override returns (ModuleMetadata memory) {} function preExecutionHook(uint32, address sender, uint256, bytes calldata) external diff --git a/test/mocks/plugins/MockAccessControlHookPlugin.sol b/test/mocks/modules/MockAccessControlHookModule.sol similarity index 85% rename from test/mocks/plugins/MockAccessControlHookPlugin.sol rename to test/mocks/modules/MockAccessControlHookModule.sol index 8fa49f8e..e3f47b86 100644 --- a/test/mocks/plugins/MockAccessControlHookPlugin.sol +++ b/test/mocks/modules/MockAccessControlHookModule.sol @@ -3,17 +3,17 @@ pragma solidity ^0.8.25; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; -import {PluginManifest, PluginMetadata} from "../../../src/interfaces/IPlugin.sol"; +import {ModuleManifest, ModuleMetadata} from "../../../src/interfaces/IModule.sol"; import {IStandardExecutor} from "../../../src/interfaces/IStandardExecutor.sol"; import {IValidationHook} from "../../../src/interfaces/IValidationHook.sol"; -import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; +import {BaseModule} from "../../../src/modules/BaseModule.sol"; -// A pre validaiton hook plugin that uses per-hook data. +// A pre validaiton hook module that uses per-hook data. // This example enforces that the target of an `execute` call must only be the previously specified address. // This is just a mock - it does not enforce this over `executeBatch` and other methods of making calls, and should // not be used in production.. -contract MockAccessControlHookPlugin is IValidationHook, BasePlugin { +contract MockAccessControlHookModule is IValidationHook, BaseModule { enum EntityId { PRE_VALIDATION_HOOK } @@ -73,7 +73,7 @@ contract MockAccessControlHookPlugin is IValidationHook, BasePlugin { revert NotImplemented(); } - function pluginMetadata() external pure override returns (PluginMetadata memory) {} + function moduleMetadata() external pure override returns (ModuleMetadata memory) {} - function pluginManifest() external pure override returns (PluginManifest memory) {} + function moduleManifest() external pure override returns (ModuleManifest memory) {} } diff --git a/test/mocks/plugins/PermittedCallMocks.sol b/test/mocks/modules/PermittedCallMocks.sol similarity index 53% rename from test/mocks/plugins/PermittedCallMocks.sol rename to test/mocks/modules/PermittedCallMocks.sol index 77548225..bea02e78 100644 --- a/test/mocks/plugins/PermittedCallMocks.sol +++ b/test/mocks/modules/PermittedCallMocks.sol @@ -1,18 +1,18 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {ManifestExecutionFunction, PluginManifest, PluginMetadata} from "../../../src/interfaces/IPlugin.sol"; +import {ManifestExecutionFunction, ModuleManifest, ModuleMetadata} from "../../../src/interfaces/IModule.sol"; -import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; -import {ResultCreatorPlugin} from "./ReturnDataPluginMocks.sol"; +import {BaseModule} from "../../../src/modules/BaseModule.sol"; +import {ResultCreatorModule} from "./ReturnDataModuleMocks.sol"; -contract PermittedCallerPlugin is BasePlugin { +contract PermittedCallerModule is BaseModule { function onInstall(bytes calldata) external override {} function onUninstall(bytes calldata) external override {} - function pluginManifest() external pure override returns (PluginManifest memory) { - PluginManifest memory manifest; + function moduleManifest() external pure override returns (ModuleManifest memory) { + ModuleManifest memory manifest; manifest.executionFunctions = new ManifestExecutionFunction[](2); manifest.executionFunctions[0].executionSelector = this.usePermittedCallAllowed.selector; @@ -25,15 +25,15 @@ contract PermittedCallerPlugin is BasePlugin { return manifest; } - function pluginMetadata() external pure override returns (PluginMetadata memory) {} + function moduleMetadata() external pure override returns (ModuleMetadata memory) {} - // The manifest requested access to use the plugin-defined method "foo" + // The manifest requested access to use the module-defined method "foo" function usePermittedCallAllowed() external view returns (bytes memory) { - return abi.encode(ResultCreatorPlugin(msg.sender).foo()); + return abi.encode(ResultCreatorModule(msg.sender).foo()); } - // The manifest has not requested access to use the plugin-defined method "bar", so this should revert. + // The manifest has not requested access to use the module-defined method "bar", so this should revert. function usePermittedCallNotAllowed() external view returns (bytes memory) { - return abi.encode(ResultCreatorPlugin(msg.sender).bar()); + return abi.encode(ResultCreatorModule(msg.sender).bar()); } } diff --git a/test/mocks/plugins/ReturnDataPluginMocks.sol b/test/mocks/modules/ReturnDataModuleMocks.sol similarity index 84% rename from test/mocks/plugins/ReturnDataPluginMocks.sol rename to test/mocks/modules/ReturnDataModuleMocks.sol index 96a77ffe..8fe3241d 100644 --- a/test/mocks/plugins/ReturnDataPluginMocks.sol +++ b/test/mocks/modules/ReturnDataModuleMocks.sol @@ -6,14 +6,14 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import { ManifestExecutionFunction, ManifestValidation, - PluginManifest, - PluginMetadata -} from "../../../src/interfaces/IPlugin.sol"; + ModuleManifest, + ModuleMetadata +} from "../../../src/interfaces/IModule.sol"; import {IStandardExecutor} from "../../../src/interfaces/IStandardExecutor.sol"; import {IValidation} from "../../../src/interfaces/IValidation.sol"; -import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; +import {BaseModule} from "../../../src/modules/BaseModule.sol"; contract RegularResultContract { function foo() external pure returns (bytes32) { @@ -25,7 +25,7 @@ contract RegularResultContract { } } -contract ResultCreatorPlugin is BasePlugin { +contract ResultCreatorModule is BaseModule { function onInstall(bytes calldata) external override {} function onUninstall(bytes calldata) external override {} @@ -38,8 +38,8 @@ contract ResultCreatorPlugin is BasePlugin { return keccak256("foo"); } - function pluginManifest() external pure override returns (PluginManifest memory) { - PluginManifest memory manifest; + function moduleManifest() external pure override returns (ModuleManifest memory) { + ModuleManifest memory manifest; manifest.executionFunctions = new ManifestExecutionFunction[](2); manifest.executionFunctions[0] = ManifestExecutionFunction({ @@ -56,16 +56,16 @@ contract ResultCreatorPlugin is BasePlugin { return manifest; } - function pluginMetadata() external pure override returns (PluginMetadata memory) {} + function moduleMetadata() external pure override returns (ModuleMetadata memory) {} } -contract ResultConsumerPlugin is BasePlugin, IValidation { - ResultCreatorPlugin public immutable RESULT_CREATOR; +contract ResultConsumerModule is BaseModule, IValidation { + ResultCreatorModule public immutable RESULT_CREATOR; RegularResultContract public immutable REGULAR_RESULT_CONTRACT; error NotAuthorized(); - constructor(ResultCreatorPlugin _resultCreator, RegularResultContract _regularResultContract) { + constructor(ResultCreatorModule _resultCreator, RegularResultContract _regularResultContract) { RESULT_CREATOR = _resultCreator; REGULAR_RESULT_CONTRACT = _regularResultContract; } @@ -93,7 +93,7 @@ contract ResultConsumerPlugin is BasePlugin, IValidation { // Check the return data through the fallback function checkResultFallback(bytes32 expected) external view returns (bool) { // This result should be allowed based on the manifest permission request - bytes32 actual = ResultCreatorPlugin(msg.sender).foo(); + bytes32 actual = ResultCreatorModule(msg.sender).foo(); return actual == expected; } @@ -116,8 +116,8 @@ contract ResultConsumerPlugin is BasePlugin, IValidation { function onUninstall(bytes calldata) external override {} - function pluginManifest() external pure override returns (PluginManifest memory) { - PluginManifest memory manifest; + function moduleManifest() external pure override returns (ModuleManifest memory) { + ModuleManifest memory manifest; // todo: this is the exact workflow that would benefit from a "permiteed call" setup in the manifest. bytes4[] memory validationSelectors = new bytes4[](1); @@ -146,5 +146,5 @@ contract ResultConsumerPlugin is BasePlugin, IValidation { return manifest; } - function pluginMetadata() external pure override returns (PluginMetadata memory) {} + function moduleMetadata() external pure override returns (ModuleMetadata memory) {} } diff --git a/test/mocks/plugins/ValidationPluginMocks.sol b/test/mocks/modules/ValidationModuleMocks.sol similarity index 86% rename from test/mocks/plugins/ValidationPluginMocks.sol rename to test/mocks/modules/ValidationModuleMocks.sol index 3cfe1d09..ed152af2 100644 --- a/test/mocks/plugins/ValidationPluginMocks.sol +++ b/test/mocks/modules/ValidationModuleMocks.sol @@ -6,14 +6,14 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import { ManifestExecutionFunction, ManifestValidation, - PluginManifest, - PluginMetadata -} from "../../../src/interfaces/IPlugin.sol"; + ModuleManifest, + ModuleMetadata +} from "../../../src/interfaces/IModule.sol"; import {IValidation} from "../../../src/interfaces/IValidation.sol"; import {IValidationHook} from "../../../src/interfaces/IValidationHook.sol"; -import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; +import {BaseModule} from "../../../src/modules/BaseModule.sol"; -abstract contract MockBaseUserOpValidationPlugin is IValidation, IValidationHook, BasePlugin { +abstract contract MockBaseUserOpValidationModule is IValidation, IValidationHook, BaseModule { enum EntityId { USER_OP_VALIDATION, PRE_VALIDATION_HOOK_1, @@ -25,7 +25,7 @@ abstract contract MockBaseUserOpValidationPlugin is IValidation, IValidationHook uint256 internal _preUserOpValidationHook2Data; // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Plugin interface functions ┃ + // ┃ Module interface functions ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ function onInstall(bytes calldata) external override {} @@ -68,7 +68,7 @@ abstract contract MockBaseUserOpValidationPlugin is IValidation, IValidationHook } // Empty stubs - function pluginMetadata() external pure override returns (PluginMetadata memory) {} + function moduleMetadata() external pure override returns (ModuleMetadata memory) {} function preRuntimeValidationHook(uint32, address, uint256, bytes calldata, bytes calldata) external @@ -87,7 +87,7 @@ abstract contract MockBaseUserOpValidationPlugin is IValidation, IValidationHook } } -contract MockUserOpValidationPlugin is MockBaseUserOpValidationPlugin { +contract MockUserOpValidationModule is MockBaseUserOpValidationModule { function setValidationData(uint256 userOpValidationFunctionData) external { _userOpValidationFunctionData = userOpValidationFunctionData; } @@ -99,11 +99,11 @@ contract MockUserOpValidationPlugin is MockBaseUserOpValidationPlugin { function foo() external {} // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Plugin interface functions ┃ + // ┃ Module interface functions ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - function pluginManifest() external pure override returns (PluginManifest memory) { - PluginManifest memory manifest; + function moduleManifest() external pure override returns (ModuleManifest memory) { + ModuleManifest memory manifest; manifest.executionFunctions = new ManifestExecutionFunction[](1); manifest.executionFunctions[0] = ManifestExecutionFunction({ @@ -127,7 +127,7 @@ contract MockUserOpValidationPlugin is MockBaseUserOpValidationPlugin { } } -contract MockUserOpValidation1HookPlugin is MockBaseUserOpValidationPlugin { +contract MockUserOpValidation1HookModule is MockBaseUserOpValidationModule { function setValidationData(uint256 userOpValidationFunctionData, uint256 preUserOpValidationHook1Data) external { @@ -142,11 +142,11 @@ contract MockUserOpValidation1HookPlugin is MockBaseUserOpValidationPlugin { function bar() external {} // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Plugin interface functions ┃ + // ┃ Module interface functions ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - function pluginManifest() external pure override returns (PluginManifest memory) { - PluginManifest memory manifest; + function moduleManifest() external pure override returns (ModuleManifest memory) { + ModuleManifest memory manifest; manifest.executionFunctions = new ManifestExecutionFunction[](1); manifest.executionFunctions[0] = ManifestExecutionFunction({ @@ -170,7 +170,7 @@ contract MockUserOpValidation1HookPlugin is MockBaseUserOpValidationPlugin { } } -contract MockUserOpValidation2HookPlugin is MockBaseUserOpValidationPlugin { +contract MockUserOpValidation2HookModule is MockBaseUserOpValidationModule { function setValidationData( uint256 userOpValidationFunctionData, uint256 preUserOpValidationHook1Data, @@ -188,11 +188,11 @@ contract MockUserOpValidation2HookPlugin is MockBaseUserOpValidationPlugin { function baz() external {} // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Plugin interface functions ┃ + // ┃ Module interface functions ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - function pluginManifest() external pure override returns (PluginManifest memory) { - PluginManifest memory manifest; + function moduleManifest() external pure override returns (ModuleManifest memory) { + ModuleManifest memory manifest; manifest.executionFunctions = new ManifestExecutionFunction[](1); manifest.executionFunctions[0] = ManifestExecutionFunction({ diff --git a/test/plugin/ERC20TokenLimitPlugin.t.sol b/test/module/ERC20TokenLimitModule.t.sol similarity index 75% rename from test/plugin/ERC20TokenLimitPlugin.t.sol rename to test/module/ERC20TokenLimitModule.t.sol index cf7b422a..e88a4527 100644 --- a/test/plugin/ERC20TokenLimitPlugin.t.sol +++ b/test/module/ERC20TokenLimitModule.t.sol @@ -6,33 +6,33 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {PluginEntity} from "../../src/helpers/PluginEntityLib.sol"; +import {ModuleEntity} from "../../src/helpers/ModuleEntityLib.sol"; -import {PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; +import {ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; -import {PluginManifest} from "../../src/interfaces/IPlugin.sol"; +import {ModuleManifest} from "../../src/interfaces/IModule.sol"; import {Call, IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; -import {ERC20TokenLimitPlugin} from "../../src/plugins/ERC20TokenLimitPlugin.sol"; -import {MockPlugin} from "../mocks/MockPlugin.sol"; +import {ERC20TokenLimitModule} from "../../src/modules/ERC20TokenLimitModule.sol"; +import {MockModule} from "../mocks/MockModule.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; -contract ERC20TokenLimitPluginTest is AccountTestBase { +contract ERC20TokenLimitModuleTest is AccountTestBase { address public recipient = address(1); MockERC20 public erc20; address payable public bundler = payable(address(2)); - PluginManifest internal _m; - MockPlugin public validationPlugin = new MockPlugin(_m); - PluginEntity public validationFunction; + ModuleManifest internal _m; + MockModule public validationModule = new MockModule(_m); + ModuleEntity public validationFunction; UpgradeableModularAccount public acct; - ERC20TokenLimitPlugin public plugin = new ERC20TokenLimitPlugin(); + ERC20TokenLimitModule public module = new ERC20TokenLimitModule(); uint256 public spendLimit = 10 ether; function setUp() public { - // Set up a validator with hooks from the erc20 spend limit plugin attached + // Set up a validator with hooks from the erc20 spend limit module attached acct = factory.createAccount(address(this), 0); erc20 = new MockERC20(); @@ -40,7 +40,7 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { ExecutionHook[] memory permissionHooks = new ExecutionHook[](1); permissionHooks[0] = ExecutionHook({ - hookFunction: PluginEntityLib.pack(address(plugin), 0), + hookFunction: ModuleEntityLib.pack(address(module), 0), isPreHook: true, isPostHook: false }); @@ -49,22 +49,22 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { uint256[] memory limits = new uint256[](1); limits[0] = spendLimit; - ERC20TokenLimitPlugin.ERC20SpendLimit[] memory limit = new ERC20TokenLimitPlugin.ERC20SpendLimit[](1); - limit[0] = ERC20TokenLimitPlugin.ERC20SpendLimit({token: address(erc20), limits: limits}); + ERC20TokenLimitModule.ERC20SpendLimit[] memory limit = new ERC20TokenLimitModule.ERC20SpendLimit[](1); + limit[0] = ERC20TokenLimitModule.ERC20SpendLimit({token: address(erc20), limits: limits}); bytes[] memory permissionInitDatas = new bytes[](1); permissionInitDatas[0] = abi.encode(uint8(0), limit); vm.prank(address(acct)); acct.installValidation( - ValidationConfigLib.pack(address(validationPlugin), 0, true, true), + ValidationConfigLib.pack(address(validationModule), 0, true, true), new bytes4[](0), new bytes(0), new bytes(0), abi.encode(permissionHooks, permissionInitDatas) ); - validationFunction = PluginEntityLib.pack(address(validationPlugin), 0); + validationFunction = ModuleEntityLib.pack(address(validationModule), 0); } function _getPackedUO(bytes memory callData) internal view returns (PackedUserOperation memory uo) { @@ -77,7 +77,7 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { preVerificationGas: 200_000, gasFees: bytes32(uint256(uint128(0))), paymasterAndData: "", - signature: _encodeSignature(PluginEntityLib.pack(address(validationPlugin), 0), 1, "") + signature: _encodeSignature(ModuleEntityLib.pack(address(validationModule), 0), 1, "") }); } @@ -90,9 +90,9 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { function test_userOp_executeLimit() public { vm.startPrank(address(entryPoint)); - assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether); + assertEq(module.limits(0, address(erc20), address(acct)), 10 ether); acct.executeUserOp(_getPackedUO(_getExecuteWithSpend(5 ether)), bytes32(0)); - assertEq(plugin.limits(0, address(erc20), address(acct)), 5 ether); + assertEq(module.limits(0, address(erc20), address(acct)), 5 ether); } function test_userOp_executeBatchLimit() public { @@ -108,9 +108,9 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { }); vm.startPrank(address(entryPoint)); - assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether); + assertEq(module.limits(0, address(erc20), address(acct)), 10 ether); acct.executeUserOp(_getPackedUO(abi.encodeCall(IStandardExecutor.executeBatch, (calls))), bytes32(0)); - assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether - 6 ether - 100_001); + assertEq(module.limits(0, address(erc20), address(acct)), 10 ether - 6 ether - 100_001); } function test_userOp_executeBatch_approveAndTransferLimit() public { @@ -126,9 +126,9 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { }); vm.startPrank(address(entryPoint)); - assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether); + assertEq(module.limits(0, address(erc20), address(acct)), 10 ether); acct.executeUserOp(_getPackedUO(abi.encodeCall(IStandardExecutor.executeBatch, (calls))), bytes32(0)); - assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether - 6 ether - 100_001); + assertEq(module.limits(0, address(erc20), address(acct)), 10 ether - 6 ether - 100_001); } function test_userOp_executeBatch_approveAndTransferLimit_fail() public { @@ -144,21 +144,21 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { }); vm.startPrank(address(entryPoint)); - assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether); + assertEq(module.limits(0, address(erc20), address(acct)), 10 ether); PackedUserOperation[] memory uos = new PackedUserOperation[](1); uos[0] = _getPackedUO(abi.encodeCall(IStandardExecutor.executeBatch, (calls))); entryPoint.handleOps(uos, bundler); // no spend consumed - assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether); + assertEq(module.limits(0, address(erc20), address(acct)), 10 ether); } function test_runtime_executeLimit() public { - assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether); + assertEq(module.limits(0, address(erc20), address(acct)), 10 ether); acct.executeWithAuthorization( _getExecuteWithSpend(5 ether), - _encodeSignature(PluginEntityLib.pack(address(validationPlugin), 0), 1, "") + _encodeSignature(ModuleEntityLib.pack(address(validationModule), 0), 1, "") ); - assertEq(plugin.limits(0, address(erc20), address(acct)), 5 ether); + assertEq(module.limits(0, address(erc20), address(acct)), 5 ether); } function test_runtime_executeBatchLimit() public { @@ -173,11 +173,11 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { data: abi.encodeCall(IERC20.approve, (recipient, 5 ether + 100_000)) }); - assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether); + assertEq(module.limits(0, address(erc20), address(acct)), 10 ether); acct.executeWithAuthorization( abi.encodeCall(IStandardExecutor.executeBatch, (calls)), - _encodeSignature(PluginEntityLib.pack(address(validationPlugin), 0), 1, "") + _encodeSignature(ModuleEntityLib.pack(address(validationModule), 0), 1, "") ); - assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether - 6 ether - 100_001); + assertEq(module.limits(0, address(erc20), address(acct)), 10 ether - 6 ether - 100_001); } } diff --git a/test/plugin/NativeTokenLimitPlugin.t.sol b/test/module/NativeTokenLimitModule.t.sol similarity index 74% rename from test/plugin/NativeTokenLimitPlugin.t.sol rename to test/module/NativeTokenLimitModule.t.sol index 0ae9ae40..a5e20389 100644 --- a/test/plugin/NativeTokenLimitPlugin.t.sol +++ b/test/module/NativeTokenLimitModule.t.sol @@ -4,43 +4,43 @@ pragma solidity ^0.8.19; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {PluginEntity} from "../../src/helpers/PluginEntityLib.sol"; +import {ModuleEntity} from "../../src/helpers/ModuleEntityLib.sol"; -import {PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; +import {ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; -import {PluginManifest} from "../../src/interfaces/IPlugin.sol"; +import {ModuleManifest} from "../../src/interfaces/IModule.sol"; import {Call, IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; -import {NativeTokenLimitPlugin} from "../../src/plugins/NativeTokenLimitPlugin.sol"; -import {MockPlugin} from "../mocks/MockPlugin.sol"; +import {NativeTokenLimitModule} from "../../src/modules/NativeTokenLimitModule.sol"; +import {MockModule} from "../mocks/MockModule.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; -contract NativeTokenLimitPluginTest is AccountTestBase { +contract NativeTokenLimitModuleTest is AccountTestBase { address public recipient = address(1); address payable public bundler = payable(address(2)); - PluginManifest internal _m; - MockPlugin public validationPlugin = new MockPlugin(_m); - PluginEntity public validationFunction; + ModuleManifest internal _m; + MockModule public validationModule = new MockModule(_m); + ModuleEntity public validationFunction; UpgradeableModularAccount public acct; - NativeTokenLimitPlugin public plugin = new NativeTokenLimitPlugin(); + NativeTokenLimitModule public module = new NativeTokenLimitModule(); uint256 public spendLimit = 10 ether; function setUp() public { - // Set up a validator with hooks from the gas spend limit plugin attached + // Set up a validator with hooks from the gas spend limit module attached acct = factory.createAccount(address(this), 0); vm.deal(address(acct), 10 ether); - PluginEntity[] memory preValidationHooks = new PluginEntity[](1); - preValidationHooks[0] = PluginEntityLib.pack(address(plugin), 0); + ModuleEntity[] memory preValidationHooks = new ModuleEntity[](1); + preValidationHooks[0] = ModuleEntityLib.pack(address(module), 0); ExecutionHook[] memory permissionHooks = new ExecutionHook[](1); permissionHooks[0] = ExecutionHook({ - hookFunction: PluginEntityLib.pack(address(plugin), 0), + hookFunction: ModuleEntityLib.pack(address(module), 0), isPreHook: true, isPostHook: false }); @@ -56,14 +56,14 @@ contract NativeTokenLimitPluginTest is AccountTestBase { vm.prank(address(acct)); acct.installValidation( - ValidationConfigLib.pack(address(validationPlugin), 0, true, true), + ValidationConfigLib.pack(address(validationModule), 0, true, true), new bytes4[](0), new bytes(0), abi.encode(preValidationHooks, preValHooksInitDatas), abi.encode(permissionHooks, permissionInitDatas) ); - validationFunction = PluginEntityLib.pack(address(validationPlugin), 0); + validationFunction = ModuleEntityLib.pack(address(validationModule), 0); } function _getExecuteWithValue(uint256 value) internal view returns (bytes memory) { @@ -84,7 +84,7 @@ contract NativeTokenLimitPluginTest is AccountTestBase { preVerificationGas: gas3, gasFees: bytes32(uint256(uint128(gasPrice))), paymasterAndData: "", - signature: _encodeSignature(PluginEntityLib.pack(address(validationPlugin), 0), 1, "") + signature: _encodeSignature(ModuleEntityLib.pack(address(validationModule), 0), 1, "") }); } @@ -92,17 +92,17 @@ contract NativeTokenLimitPluginTest is AccountTestBase { vm.startPrank(address(entryPoint)); // uses 10e - 200000 of gas - assertEq(plugin.limits(0, address(acct)), 10 ether); + assertEq(module.limits(0, address(acct)), 10 ether); uint256 result = acct.validateUserOp( _getPackedUO(100_000, 100_000, 10 ether - 400_000, 1, _getExecuteWithValue(0)), bytes32(0), 0 ); - assertEq(plugin.limits(0, address(acct)), 200_000); + assertEq(module.limits(0, address(acct)), 200_000); uint256 expected = uint256(type(uint48).max) << 160; assertEq(result, expected); // uses 200k + 1 wei of gas - vm.expectRevert(NativeTokenLimitPlugin.ExceededNativeTokenLimit.selector); + vm.expectRevert(NativeTokenLimitModule.ExceededNativeTokenLimit.selector); result = acct.validateUserOp(_getPackedUO(100_000, 100_000, 1, 1, _getExecuteWithValue(0)), bytes32(0), 0); } @@ -110,18 +110,18 @@ contract NativeTokenLimitPluginTest is AccountTestBase { vm.startPrank(address(entryPoint)); // uses 5e of native tokens - assertEq(plugin.limits(0, address(acct)), 10 ether); + assertEq(module.limits(0, address(acct)), 10 ether); acct.executeUserOp(_getPackedUO(0, 0, 0, 0, _getExecuteWithValue(5 ether)), bytes32(0)); - assertEq(plugin.limits(0, address(acct)), 5 ether); + assertEq(module.limits(0, address(acct)), 5 ether); // uses 5e + 1wei of native tokens vm.expectRevert( abi.encodePacked( UpgradeableModularAccount.PreExecHookReverted.selector, abi.encode( - address(plugin), + address(module), uint8(0), - abi.encodePacked(NativeTokenLimitPlugin.ExceededNativeTokenLimit.selector) + abi.encodePacked(NativeTokenLimitModule.ExceededNativeTokenLimit.selector) ) ) ); @@ -135,21 +135,21 @@ contract NativeTokenLimitPluginTest is AccountTestBase { calls[2] = Call({target: recipient, value: 5 ether + 100_000, data: ""}); vm.startPrank(address(entryPoint)); - assertEq(plugin.limits(0, address(acct)), 10 ether); + assertEq(module.limits(0, address(acct)), 10 ether); acct.executeUserOp( _getPackedUO(0, 0, 0, 0, abi.encodeCall(IStandardExecutor.executeBatch, (calls))), bytes32(0) ); - assertEq(plugin.limits(0, address(acct)), 10 ether - 6 ether - 100_001); + assertEq(module.limits(0, address(acct)), 10 ether - 6 ether - 100_001); assertEq(recipient.balance, 6 ether + 100_001); } function test_userOp_combinedExecLimit_success() public { - assertEq(plugin.limits(0, address(acct)), 10 ether); + assertEq(module.limits(0, address(acct)), 10 ether); PackedUserOperation[] memory uos = new PackedUserOperation[](1); uos[0] = _getPackedUO(200_000, 200_000, 200_000, 1, _getExecuteWithValue(5 ether)); entryPoint.handleOps(uos, bundler); - assertEq(plugin.limits(0, address(acct)), 5 ether - 600_000); + assertEq(module.limits(0, address(acct)), 5 ether - 600_000); assertEq(recipient.balance, 5 ether); } @@ -160,30 +160,30 @@ contract NativeTokenLimitPluginTest is AccountTestBase { calls[2] = Call({target: recipient, value: 5 ether + 100_000, data: ""}); vm.startPrank(address(entryPoint)); - assertEq(plugin.limits(0, address(acct)), 10 ether); + assertEq(module.limits(0, address(acct)), 10 ether); PackedUserOperation[] memory uos = new PackedUserOperation[](1); uos[0] = _getPackedUO(200_000, 200_000, 200_000, 1, abi.encodeCall(IStandardExecutor.executeBatch, (calls))); entryPoint.handleOps(uos, bundler); - assertEq(plugin.limits(0, address(acct)), 10 ether - 6 ether - 700_001); + assertEq(module.limits(0, address(acct)), 10 ether - 6 ether - 700_001); assertEq(recipient.balance, 6 ether + 100_001); } function test_userOp_combinedExecLimit_failExec() public { - assertEq(plugin.limits(0, address(acct)), 10 ether); + assertEq(module.limits(0, address(acct)), 10 ether); PackedUserOperation[] memory uos = new PackedUserOperation[](1); uos[0] = _getPackedUO(200_000, 200_000, 200_000, 1, _getExecuteWithValue(10 ether)); entryPoint.handleOps(uos, bundler); - assertEq(plugin.limits(0, address(acct)), 10 ether - 600_000); + assertEq(module.limits(0, address(acct)), 10 ether - 600_000); assertEq(recipient.balance, 0); } function test_runtime_executeLimit() public { - assertEq(plugin.limits(0, address(acct)), 10 ether); + assertEq(module.limits(0, address(acct)), 10 ether); acct.executeWithAuthorization(_getExecuteWithValue(5 ether), _encodeSignature(validationFunction, 1, "")); - assertEq(plugin.limits(0, address(acct)), 5 ether); + assertEq(module.limits(0, address(acct)), 5 ether); } function test_runtime_executeBatchLimit() public { @@ -192,10 +192,10 @@ contract NativeTokenLimitPluginTest is AccountTestBase { calls[1] = Call({target: recipient, value: 1 ether, data: ""}); calls[2] = Call({target: recipient, value: 5 ether + 100_000, data: ""}); - assertEq(plugin.limits(0, address(acct)), 10 ether); + assertEq(module.limits(0, address(acct)), 10 ether); acct.executeWithAuthorization( abi.encodeCall(IStandardExecutor.executeBatch, (calls)), _encodeSignature(validationFunction, 1, "") ); - assertEq(plugin.limits(0, address(acct)), 4 ether - 100_001); + assertEq(module.limits(0, address(acct)), 4 ether - 100_001); } } diff --git a/test/plugin/TokenReceiverPlugin.t.sol b/test/module/TokenReceiverModule.t.sol similarity index 92% rename from test/plugin/TokenReceiverPlugin.t.sol rename to test/module/TokenReceiverModule.t.sol index 32fa2a9a..ffe85e86 100644 --- a/test/plugin/TokenReceiverPlugin.t.sol +++ b/test/module/TokenReceiverModule.t.sol @@ -7,7 +7,7 @@ import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Re import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {TokenReceiverPlugin} from "../../src/plugins/TokenReceiverPlugin.sol"; +import {TokenReceiverModule} from "../../src/modules/TokenReceiverModule.sol"; import {MockERC1155} from "../mocks/MockERC1155.sol"; import {MockERC721} from "../mocks/MockERC721.sol"; @@ -15,10 +15,10 @@ import {SingleSignerFactoryFixture} from "../mocks/SingleSignerFactoryFixture.so import {OptimizedTest} from "../utils/OptimizedTest.sol"; -contract TokenReceiverPluginTest is OptimizedTest, IERC1155Receiver { +contract TokenReceiverModuleTest is OptimizedTest, IERC1155Receiver { EntryPoint public entryPoint; UpgradeableModularAccount public acct; - TokenReceiverPlugin public plugin; + TokenReceiverModule public module; MockERC721 public t0; MockERC1155 public t1; @@ -39,7 +39,7 @@ contract TokenReceiverPluginTest is OptimizedTest, IERC1155Receiver { new SingleSignerFactoryFixture(entryPoint, _deploySingleSignerValidation()); acct = factory.createAccount(address(this), 0); - plugin = _deployTokenReceiverPlugin(); + module = _deployTokenReceiverModule(); t0 = new MockERC721("t0", "t0"); t0.mint(address(this), _TOKEN_ID); @@ -54,11 +54,11 @@ contract TokenReceiverPluginTest is OptimizedTest, IERC1155Receiver { } } - function _initPlugin() internal { - bytes32 manifestHash = keccak256(abi.encode(plugin.pluginManifest())); + function _initModule() internal { + bytes32 manifestHash = keccak256(abi.encode(module.moduleManifest())); vm.prank(address(entryPoint)); - acct.installPlugin(address(plugin), manifestHash, ""); + acct.installModule(address(module), manifestHash, ""); } function test_failERC721Transfer() public { @@ -73,7 +73,7 @@ contract TokenReceiverPluginTest is OptimizedTest, IERC1155Receiver { } function test_passERC721Transfer() public { - _initPlugin(); + _initModule(); assertEq(t0.ownerOf(_TOKEN_ID), address(this)); t0.safeTransferFrom(address(this), address(acct), _TOKEN_ID); assertEq(t0.ownerOf(_TOKEN_ID), address(acct)); @@ -102,7 +102,7 @@ contract TokenReceiverPluginTest is OptimizedTest, IERC1155Receiver { } function test_passERC1155Transfer() public { - _initPlugin(); + _initModule(); assertEq(t1.balanceOf(address(this), _TOKEN_ID), _TOKEN_AMOUNT); assertEq(t1.balanceOf(address(acct), _TOKEN_ID), 0); @@ -131,7 +131,7 @@ contract TokenReceiverPluginTest is OptimizedTest, IERC1155Receiver { } function test_passIntrospection() public { - _initPlugin(); + _initModule(); bool isSupported; diff --git a/test/samples/AllowlistPlugin.t.sol b/test/samples/AllowlistModule.t.sol similarity index 83% rename from test/samples/AllowlistPlugin.t.sol rename to test/samples/AllowlistModule.t.sol index d2a28f5e..80b91fb5 100644 --- a/test/samples/AllowlistPlugin.t.sol +++ b/test/samples/AllowlistModule.t.sol @@ -4,23 +4,23 @@ pragma solidity ^0.8.25; import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; +import {ModuleEntity, ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {Call} from "../../src/interfaces/IStandardExecutor.sol"; -import {AllowlistPlugin} from "../../src/samples/permissionhooks/AllowlistPlugin.sol"; +import {AllowlistModule} from "../../src/samples/permissionhooks/AllowlistModule.sol"; import {Counter} from "../mocks/Counter.sol"; import {CustomValidationTestBase} from "../utils/CustomValidationTestBase.sol"; -contract AllowlistPluginTest is CustomValidationTestBase { - AllowlistPlugin public allowlistPlugin; +contract AllowlistModuleTest is CustomValidationTestBase { + AllowlistModule public allowlistModule; - AllowlistPlugin.AllowlistInit[] public allowlistInit; + AllowlistModule.AllowlistInit[] public allowlistInit; Counter[] public counters; function setUp() public { - allowlistPlugin = new AllowlistPlugin(); + allowlistModule = new AllowlistModule(); counters = new Counter[](10); @@ -32,7 +32,7 @@ contract AllowlistPluginTest is CustomValidationTestBase { } function testFuzz_allowlistHook_userOp_single(uint256 seed) public { - AllowlistPlugin.AllowlistInit[] memory inits; + AllowlistModule.AllowlistInit[] memory inits; (inits, seed) = _generateRandomizedAllowlistInit(seed); _copyInitToStorage(inits); @@ -46,7 +46,7 @@ contract AllowlistPluginTest is CustomValidationTestBase { } function testFuzz_allowlistHook_userOp_batch(uint256 seed) public { - AllowlistPlugin.AllowlistInit[] memory inits; + AllowlistModule.AllowlistInit[] memory inits; (inits, seed) = _generateRandomizedAllowlistInit(seed); _copyInitToStorage(inits); @@ -60,7 +60,7 @@ contract AllowlistPluginTest is CustomValidationTestBase { } function testFuzz_allowlistHook_runtime_single(uint256 seed) public { - AllowlistPlugin.AllowlistInit[] memory inits; + AllowlistModule.AllowlistInit[] memory inits; (inits, seed) = _generateRandomizedAllowlistInit(seed); _copyInitToStorage(inits); @@ -78,7 +78,7 @@ contract AllowlistPluginTest is CustomValidationTestBase { } function testFuzz_allowlistHook_runtime_batch(uint256 seed) public { - AllowlistPlugin.AllowlistInit[] memory inits; + AllowlistModule.AllowlistInit[] memory inits; (inits, seed) = _generateRandomizedAllowlistInit(seed); _copyInitToStorage(inits); @@ -145,17 +145,17 @@ contract AllowlistPluginTest is CustomValidationTestBase { Call memory call = calls[i]; (bool allowed, bool hasSelectorAllowlist) = - allowlistPlugin.targetAllowlist(call.target, address(account1)); + allowlistModule.targetAllowlist(call.target, address(account1)); if (allowed) { if ( hasSelectorAllowlist - && !allowlistPlugin.selectorAllowlist(call.target, bytes4(call.data), address(account1)) + && !allowlistModule.selectorAllowlist(call.target, bytes4(call.data), address(account1)) ) { return abi.encodeWithSelector( IEntryPoint.FailedOpWithRevert.selector, 0, "AA23 reverted", - abi.encodeWithSelector(AllowlistPlugin.SelectorNotAllowed.selector) + abi.encodeWithSelector(AllowlistModule.SelectorNotAllowed.selector) ); } } else { @@ -163,7 +163,7 @@ contract AllowlistPluginTest is CustomValidationTestBase { IEntryPoint.FailedOpWithRevert.selector, 0, "AA23 reverted", - abi.encodeWithSelector(AllowlistPlugin.TargetNotAllowed.selector) + abi.encodeWithSelector(AllowlistModule.TargetNotAllowed.selector) ); } } @@ -176,30 +176,30 @@ contract AllowlistPluginTest is CustomValidationTestBase { Call memory call = calls[i]; (bool allowed, bool hasSelectorAllowlist) = - allowlistPlugin.targetAllowlist(call.target, address(account1)); + allowlistModule.targetAllowlist(call.target, address(account1)); if (allowed) { if ( hasSelectorAllowlist - && !allowlistPlugin.selectorAllowlist(call.target, bytes4(call.data), address(account1)) + && !allowlistModule.selectorAllowlist(call.target, bytes4(call.data), address(account1)) ) { return abi.encodeWithSelector( UpgradeableModularAccount.PreRuntimeValidationHookFailed.selector, - address(allowlistPlugin), - uint32(AllowlistPlugin.EntityId.PRE_VALIDATION_HOOK), - abi.encodeWithSelector(AllowlistPlugin.SelectorNotAllowed.selector) + address(allowlistModule), + uint32(AllowlistModule.EntityId.PRE_VALIDATION_HOOK), + abi.encodeWithSelector(AllowlistModule.SelectorNotAllowed.selector) ); } } else { return abi.encodeWithSelector( UpgradeableModularAccount.PreRuntimeValidationHookFailed.selector, - address(allowlistPlugin), - uint32(AllowlistPlugin.EntityId.PRE_VALIDATION_HOOK), - abi.encodeWithSelector(AllowlistPlugin.TargetNotAllowed.selector) + address(allowlistModule), + uint32(AllowlistModule.EntityId.PRE_VALIDATION_HOOK), + abi.encodeWithSelector(AllowlistModule.TargetNotAllowed.selector) ); } } - // At this point, we have returned any error that would come from the AllowlistPlugin. + // At this point, we have returned any error that would come from the AllowlistModule. // But, because this is in the runtime path, the Counter itself may throw if it is not a valid selector. for (uint256 i = 0; i < calls.length; i++) { @@ -221,12 +221,12 @@ contract AllowlistPluginTest is CustomValidationTestBase { function _generateRandomizedAllowlistInit(uint256 seed) internal view - returns (AllowlistPlugin.AllowlistInit[] memory, uint256) + returns (AllowlistModule.AllowlistInit[] memory, uint256) { uint256 length = seed % 10; seed = _next(seed); - AllowlistPlugin.AllowlistInit[] memory init = new AllowlistPlugin.AllowlistInit[](length); + AllowlistModule.AllowlistInit[] memory init = new AllowlistModule.AllowlistInit[](length); for (uint256 i = 0; i < length; i++) { // Half the time, the target is a random counter, the other half, it's a random address. @@ -271,7 +271,7 @@ contract AllowlistPluginTest is CustomValidationTestBase { seed = _next(seed); } - init[i] = AllowlistPlugin.AllowlistInit(target, hasSelectorAllowlist, selectors); + init[i] = AllowlistModule.AllowlistInit(target, hasSelectorAllowlist, selectors); } return (init, seed); @@ -291,12 +291,12 @@ contract AllowlistPluginTest is CustomValidationTestBase { internal virtual override - returns (PluginEntity, bool, bool, bytes4[] memory, bytes memory, bytes memory, bytes memory) + returns (ModuleEntity, bool, bool, bytes4[] memory, bytes memory, bytes memory, bytes memory) { - PluginEntity accessControlHook = - PluginEntityLib.pack(address(allowlistPlugin), uint32(AllowlistPlugin.EntityId.PRE_VALIDATION_HOOK)); + ModuleEntity accessControlHook = + ModuleEntityLib.pack(address(allowlistModule), uint32(AllowlistModule.EntityId.PRE_VALIDATION_HOOK)); - PluginEntity[] memory preValidationHooks = new PluginEntity[](1); + ModuleEntity[] memory preValidationHooks = new ModuleEntity[](1); preValidationHooks[0] = accessControlHook; bytes[] memory preValidationHookData = new bytes[](1); @@ -318,7 +318,7 @@ contract AllowlistPluginTest is CustomValidationTestBase { // Unfortunately, this is a feature that solidity has only implemented in via-ir, so we need to do it manually // to be able to run the tests in lite mode. - function _copyInitToStorage(AllowlistPlugin.AllowlistInit[] memory init) internal { + function _copyInitToStorage(AllowlistModule.AllowlistInit[] memory init) internal { for (uint256 i = 0; i < init.length; i++) { allowlistInit.push(init[i]); } diff --git a/test/utils/AccountTestBase.sol b/test/utils/AccountTestBase.sol index 94caaabb..733b93e4 100644 --- a/test/utils/AccountTestBase.sol +++ b/test/utils/AccountTestBase.sol @@ -6,9 +6,9 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; +import {ModuleEntity, ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {Call, IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; -import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; +import {SingleSignerValidation} from "../../src/modules/validation/SingleSignerValidation.sol"; import {OptimizedTest} from "./OptimizedTest.sol"; import {TEST_DEFAULT_VALIDATION_ENTITY_ID as EXT_CONST_TEST_DEFAULT_VALIDATION_ENTITY_ID} from @@ -19,7 +19,7 @@ import {SingleSignerFactoryFixture} from "../mocks/SingleSignerFactoryFixture.so /// @dev This contract handles common boilerplate setup for tests using UpgradeableModularAccount with /// SingleSignerValidation. abstract contract AccountTestBase is OptimizedTest { - using PluginEntityLib for PluginEntity; + using ModuleEntityLib for ModuleEntity; using MessageHashUtils for bytes32; EntryPoint public entryPoint; @@ -32,7 +32,7 @@ abstract contract AccountTestBase is OptimizedTest { uint256 public owner1Key; UpgradeableModularAccount public account1; - PluginEntity internal _signerValidation; + ModuleEntity internal _signerValidation; uint8 public constant SELECTOR_ASSOCIATED_VALIDATION = 0; uint8 public constant GLOBAL_VALIDATION = 1; @@ -60,7 +60,7 @@ abstract contract AccountTestBase is OptimizedTest { vm.deal(address(account1), 100 ether); _signerValidation = - PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID); + ModuleEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID); } function _runExecUserOp(address target, bytes memory callData) internal { @@ -103,7 +103,7 @@ abstract contract AccountTestBase is OptimizedTest { (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); userOp.signature = _encodeSignature( - PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), + ModuleEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, abi.encodePacked(r, s, v) ); @@ -156,7 +156,7 @@ abstract contract AccountTestBase is OptimizedTest { account1.executeWithAuthorization( callData, _encodeSignature( - PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), + ModuleEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, "" ) @@ -171,7 +171,7 @@ abstract contract AccountTestBase is OptimizedTest { account1.executeWithAuthorization( callData, _encodeSignature( - PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), + ModuleEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, "" ) @@ -193,7 +193,7 @@ abstract contract AccountTestBase is OptimizedTest { ) ), _encodeSignature( - PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), + ModuleEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, "" ) @@ -207,7 +207,7 @@ abstract contract AccountTestBase is OptimizedTest { // helper function to encode a signature, according to the per-hook and per-validation data format. function _encodeSignature( - PluginEntity validationFunction, + ModuleEntity validationFunction, uint8 globalOrNot, PreValidationHookData[] memory preValidationHookData, bytes memory validationData @@ -231,7 +231,7 @@ abstract contract AccountTestBase is OptimizedTest { } // overload for the case where there are no pre-validation hooks - function _encodeSignature(PluginEntity validationFunction, uint8 globalOrNot, bytes memory validationData) + function _encodeSignature(ModuleEntity validationFunction, uint8 globalOrNot, bytes memory validationData) internal pure returns (bytes memory) diff --git a/test/utils/CustomValidationTestBase.sol b/test/utils/CustomValidationTestBase.sol index f9e37c27..18fd6ae4 100644 --- a/test/utils/CustomValidationTestBase.sol +++ b/test/utils/CustomValidationTestBase.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.25; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {PluginEntity} from "../../src/helpers/PluginEntityLib.sol"; +import {ModuleEntity} from "../../src/helpers/ModuleEntityLib.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import {AccountTestBase} from "./AccountTestBase.sol"; @@ -16,7 +16,7 @@ import {AccountTestBase} from "./AccountTestBase.sol"; abstract contract CustomValidationTestBase is AccountTestBase { function _customValidationSetup() internal { ( - PluginEntity validationFunction, + ModuleEntity validationFunction, bool isGlobal, bool isSignatureValidation, bytes4[] memory selectors, @@ -44,7 +44,7 @@ abstract contract CustomValidationTestBase is AccountTestBase { internal virtual returns ( - PluginEntity validationFunction, + ModuleEntity validationFunction, bool shared, bool isSignatureValidation, bytes4[] memory selectors, diff --git a/test/utils/OptimizedTest.sol b/test/utils/OptimizedTest.sol index dc94380b..870d416a 100644 --- a/test/utils/OptimizedTest.sol +++ b/test/utils/OptimizedTest.sol @@ -7,8 +7,8 @@ import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntry import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {TokenReceiverPlugin} from "../../src/plugins/TokenReceiverPlugin.sol"; -import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; +import {TokenReceiverModule} from "../../src/modules/TokenReceiverModule.sol"; +import {SingleSignerValidation} from "../../src/modules/validation/SingleSignerValidation.sol"; /// @dev This contract provides functions to deploy optimized (via IR) precompiled contracts. By compiling just /// the source contracts (excluding the test suite) via IR, and using the resulting bytecode within the tests @@ -45,10 +45,10 @@ abstract contract OptimizedTest is Test { : new UpgradeableModularAccount(entryPoint); } - function _deployTokenReceiverPlugin() internal returns (TokenReceiverPlugin) { + function _deployTokenReceiverModule() internal returns (TokenReceiverModule) { return _isOptimizedTest() - ? TokenReceiverPlugin(deployCode("out-optimized/TokenReceiverPlugin.sol/TokenReceiverPlugin.json")) - : new TokenReceiverPlugin(); + ? TokenReceiverModule(deployCode("out-optimized/TokenReceiverModule.sol/TokenReceiverModule.json")) + : new TokenReceiverModule(); } function _deploySingleSignerValidation() internal returns (SingleSignerValidation) { diff --git a/test/validation/SingleSignerValidation.t.sol b/test/validation/SingleSignerValidation.t.sol index e81706fe..ae3be4e5 100644 --- a/test/validation/SingleSignerValidation.t.sol +++ b/test/validation/SingleSignerValidation.t.sol @@ -5,7 +5,7 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; +import {ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import {ContractOwner} from "../mocks/ContractOwner.sol"; @@ -50,7 +50,7 @@ contract SingleSignerValidationTest is AccountTestBase { bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); userOp.signature = _encodeSignature( - PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), + ModuleEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, abi.encodePacked(r, s, v) ); @@ -68,7 +68,7 @@ contract SingleSignerValidationTest is AccountTestBase { account.executeWithAuthorization( abi.encodeCall(UpgradeableModularAccount.execute, (ethRecipient, 1 wei, "")), _encodeSignature( - PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), + ModuleEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, "" ) @@ -91,7 +91,7 @@ contract SingleSignerValidationTest is AccountTestBase { account.executeWithAuthorization( abi.encodeCall(UpgradeableModularAccount.execute, (ethRecipient, 1 wei, "")), _encodeSignature( - PluginEntityLib.pack(address(singleSignerValidation), newEntityId), GLOBAL_VALIDATION, "" + ModuleEntityLib.pack(address(singleSignerValidation), newEntityId), GLOBAL_VALIDATION, "" ) ); assertEq(ethRecipient.balance, 1 wei); From 6abe02cebc26a50e2fbd13e29adcb8e1d3f6c651 Mon Sep 17 00:00:00 2001 From: fangting-alchemy <119372438+fangting-alchemy@users.noreply.github.com> Date: Fri, 19 Jul 2024 10:45:56 -0700 Subject: [PATCH 051/111] fix: organize all modules under modules folder (#107) --- src/{samples => modules}/permissionhooks/AllowlistModule.sol | 0 test/{samples => module}/AllowlistModule.t.sol | 2 +- test/{validation => module}/SingleSignerValidation.t.sol | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename src/{samples => modules}/permissionhooks/AllowlistModule.sol (100%) rename test/{samples => module}/AllowlistModule.t.sol (99%) rename test/{validation => module}/SingleSignerValidation.t.sol (100%) diff --git a/src/samples/permissionhooks/AllowlistModule.sol b/src/modules/permissionhooks/AllowlistModule.sol similarity index 100% rename from src/samples/permissionhooks/AllowlistModule.sol rename to src/modules/permissionhooks/AllowlistModule.sol diff --git a/test/samples/AllowlistModule.t.sol b/test/module/AllowlistModule.t.sol similarity index 99% rename from test/samples/AllowlistModule.t.sol rename to test/module/AllowlistModule.t.sol index 80b91fb5..136ae211 100644 --- a/test/samples/AllowlistModule.t.sol +++ b/test/module/AllowlistModule.t.sol @@ -7,7 +7,7 @@ import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAcc import {ModuleEntity, ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {Call} from "../../src/interfaces/IStandardExecutor.sol"; -import {AllowlistModule} from "../../src/samples/permissionhooks/AllowlistModule.sol"; +import {AllowlistModule} from "../../src/modules/permissionhooks/AllowlistModule.sol"; import {Counter} from "../mocks/Counter.sol"; import {CustomValidationTestBase} from "../utils/CustomValidationTestBase.sol"; diff --git a/test/validation/SingleSignerValidation.t.sol b/test/module/SingleSignerValidation.t.sol similarity index 100% rename from test/validation/SingleSignerValidation.t.sol rename to test/module/SingleSignerValidation.t.sol From 6ca5b96bac62cc5779d0756c2cacd83f415400a9 Mon Sep 17 00:00:00 2001 From: zer0dot Date: Mon, 22 Jul 2024 22:06:48 +0800 Subject: [PATCH 052/111] feat: appended initial validation and data to proxy bytecode --- remappings.txt | 3 +- src/account/AccountStorage.sol | 2 + src/account/UpgradeableModularAccount.sol | 61 +++++++++++++++++-- .../validation/SingleSignerValidation.sol | 39 ++++++++++-- test/account/ImmutableAppend.t.sol | 40 ++++++++++++ test/mocks/SingleSignerFactoryFixture.sol | 35 ++++++----- 6 files changed, 153 insertions(+), 27 deletions(-) create mode 100644 test/account/ImmutableAppend.t.sol diff --git a/remappings.txt b/remappings.txt index bc2ce0be..8d9639cf 100644 --- a/remappings.txt +++ b/remappings.txt @@ -2,4 +2,5 @@ ds-test/=lib/forge-std/lib/ds-test/src/ forge-std/=lib/forge-std/src/ @eth-infinitism/account-abstraction/=lib/account-abstraction/contracts/ @openzeppelin/=lib/openzeppelin-contracts/ -@modular-account-libs/=lib/modular-account-libs/src/ \ No newline at end of file +@modular-account-libs/=lib/modular-account-libs/src/ +solady=lib/solady/src/ diff --git a/src/account/AccountStorage.sol b/src/account/AccountStorage.sol index dbf0d956..40c438c9 100644 --- a/src/account/AccountStorage.sol +++ b/src/account/AccountStorage.sol @@ -31,6 +31,8 @@ struct ValidationData { bool isGlobal; // Whether or not this validation is a signature validator. bool isSignatureValidation; + // Whether, in the case this is an appended bytecode validation, the validation is disabled + bool isAppendedBytecodeValidationDisabled; // The pre validation hooks for this validation function. PluginEntity[] preValidationHooks; // Permission hooks for this validation function. diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 4f64cdd3..5c05d651 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -7,8 +7,10 @@ import {IAccountExecute} from "@eth-infinitism/account-abstraction/interfaces/IA import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; +import {LibClone} from "solady/utils/LibClone.sol"; +import {UUPSUpgradeable} from "solady/utils/UUPSUpgradeable.sol"; + import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; -import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; @@ -247,6 +249,15 @@ contract UpgradeableModularAccount is return returnData; } + // We pass a bool for "enabled" for ease of use, rather than the more efficient "disabled" + // We just negate it later. + function setBytecodeAppendedValidationEnabled(bool enabled) external wrapNativeFunction { + PluginEntity appendedValidation = _getAppendedValidation(); + + getAccountStorage().validationData[appendedValidation].isAppendedBytecodeValidationDisabled = !enabled; + // TODO: event + } + /// @inheritdoc IPluginManager /// @notice May be validated by a global validation. function installPlugin(address plugin, bytes32 manifestHash, bytes calldata pluginInstallData) @@ -332,7 +343,7 @@ contract UpgradeableModularAccount is /// @inheritdoc UUPSUpgradeable /// @notice May be validated by a global validation. - function upgradeToAndCall(address newImplementation, bytes memory data) + function upgradeToAndCall(address newImplementation, bytes calldata data) public payable override @@ -348,7 +359,21 @@ contract UpgradeableModularAccount is PluginEntity sigValidation = PluginEntity.wrap(bytes24(signature)); (address plugin, uint32 entityId) = sigValidation.unpack(); - if (!_storage.validationData[sigValidation].isSignatureValidation) { + // IF, in storage, the validation is not a signature validation THEN + // Is it the appended validation? + // No: revert + // Yes: Is it disabled as an appended bytecode validation? + // No: continue + // Yes: revert + // Written as: + // IF not storage AND (not appended OR appended-disabled) THEN revert ELSE continue + if ( + !_storage.validationData[sigValidation].isSignatureValidation + && ( + !_getAppendedValidation().eq(sigValidation) + || _storage.validationData[sigValidation].isAppendedBytecodeValidationDisabled + ) + ) { revert SignatureValidationInvalid(plugin, entityId); } @@ -733,7 +758,6 @@ contract UpgradeableModularAccount is ) { return true; } - return getAccountStorage().selectorData[selector].allowGlobalValidation; } @@ -745,9 +769,20 @@ contract UpgradeableModularAccount is // Check that the provided validation function is applicable to the selector if (isGlobal) { - if (!_globalValidationAllowed(selector) || !_storage.validationData[validationFunction].isGlobal) { - revert ValidationFunctionMissing(selector); + if (_globalValidationAllowed(selector)) { + if (_storage.validationData[validationFunction].isGlobal) { + return; + } + + if ( + _getAppendedValidation().eq(validationFunction) + && !validationFunction.eq(PluginEntity.wrap(bytes24(0))) + && !_storage.validationData[validationFunction].isAppendedBytecodeValidationDisabled + ) { + return; + } } + revert ValidationFunctionMissing(selector); } else { // Not global validation, but per-selector if (!getAccountStorage().validationData[validationFunction].selectors.contains(toSetValue(selector))) { @@ -755,4 +790,18 @@ contract UpgradeableModularAccount is } } } + + function _getAppendedValidation() internal view returns (PluginEntity) { + bytes memory appendedData = LibClone.argsOnERC1967(address(this)); + // Appended bytecode is under the format abi.encode(pluginEntity, validationArbitraryData) + // Validations will then decode this arbitrary data for whatever information they need if they support + // bytecode-appended validation. + if (appendedData.length > 0) { + // TODO: Evaluate if it's better to somehow pass the data back from here and have it passed to the + // validation instead of having it be read from bytecode by the validation + (PluginEntity appendedValidationFunction,) = abi.decode(appendedData, (PluginEntity, bytes)); + return appendedValidationFunction; + } + return PluginEntity.wrap(bytes24(0)); + } } diff --git a/src/plugins/validation/SingleSignerValidation.sol b/src/plugins/validation/SingleSignerValidation.sol index 6699cb58..1aca83ea 100644 --- a/src/plugins/validation/SingleSignerValidation.sol +++ b/src/plugins/validation/SingleSignerValidation.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.25; +import {LibClone} from "solady/utils/LibClone.sol"; + import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; @@ -11,6 +13,9 @@ import {IValidation} from "../../interfaces/IValidation.sol"; import {BasePlugin} from "../BasePlugin.sol"; import {ISingleSignerValidation} from "./ISingleSignerValidation.sol"; +import {PluginEntityLib} from "../../helpers/PluginEntityLib.sol"; +import {PluginEntity} from "../../interfaces/IPluginManager.sol"; + /// @title ECSDA Validation /// @author ERC-6900 Authors /// @notice This validation enables any ECDSA (secp256k1 curve) signature validation. It handles installation by @@ -26,6 +31,7 @@ import {ISingleSignerValidation} from "./ISingleSignerValidation.sol"; /// to validate partially or fully. contract SingleSignerValidation is ISingleSignerValidation, BasePlugin { using ECDSA for bytes32; + using PluginEntityLib for PluginEntity; using MessageHashUtils for bytes32; string internal constant _NAME = "SingleSigner Validation"; @@ -61,7 +67,7 @@ contract SingleSignerValidation is ISingleSignerValidation, BasePlugin { /// @inheritdoc ISingleSignerValidation function signerOf(uint32 entityId, address account) external view returns (address) { - return signer[entityId][account]; + return _getExpectedSigner(entityId, account); } /// @inheritdoc IValidation @@ -73,7 +79,7 @@ contract SingleSignerValidation is ISingleSignerValidation, BasePlugin { { // Validate the user op signature against the owner. (address sigSigner,,) = (userOpHash.toEthSignedMessageHash()).tryRecover(userOp.signature); - if (sigSigner == address(0) || sigSigner != signer[entityId][userOp.sender]) { + if (sigSigner == address(0) || sigSigner != _getExpectedSigner(entityId, userOp.sender)) { return _SIG_VALIDATION_FAILED; } return _SIG_VALIDATION_PASSED; @@ -88,8 +94,7 @@ contract SingleSignerValidation is ISingleSignerValidation, BasePlugin { bytes calldata, bytes calldata ) external view override { - // Validate that the sender is the owner of the account or self. - if (sender != signer[entityId][account]) { + if (sender != _getExpectedSigner(entityId, account)) { revert NotAuthorized(); } return; @@ -108,7 +113,7 @@ contract SingleSignerValidation is ISingleSignerValidation, BasePlugin { override returns (bytes4) { - if (SignatureChecker.isValidSignatureNow(signer[entityId][account], digest, signature)) { + if (SignatureChecker.isValidSignatureNow(_getExpectedSigner(entityId, account), digest, signature)) { return _1271_MAGIC_VALUE; } return _1271_INVALID; @@ -142,4 +147,28 @@ contract SingleSignerValidation is ISingleSignerValidation, BasePlugin { signer[entityId][msg.sender] = newSigner; emit SignerTransferred(msg.sender, entityId, previousSigner, newSigner); } + + function _getExpectedSigner(uint32 entityId, address account) internal view returns (address) { + address expectedSigner = signer[entityId][account]; + + if (expectedSigner == address(0)) { + // Check clone for expected validation (address(this)); + // Check clone code for expected signer + bytes memory immutables = LibClone.argsOnERC1967(account); + + // Note: Will opaquely revert on incorrect encoding (fun) + // But all appended bytecode should be in this format + (PluginEntity validation, bytes memory appendedData) = abi.decode(immutables, (PluginEntity, bytes)); + + if (!validation.eq(PluginEntityLib.pack(address(this), entityId))) { + revert("Validation incorrect"); + } + + (address decodedSigner) = abi.decode(appendedData, (address)); + + expectedSigner = decodedSigner; + } + + return expectedSigner; + } } diff --git a/test/account/ImmutableAppend.t.sol b/test/account/ImmutableAppend.t.sol new file mode 100644 index 00000000..9b3d5ded --- /dev/null +++ b/test/account/ImmutableAppend.t.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.19; + +import {IEntryPoint, UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; +import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; +import {ValidationConfig, ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; +import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; +import {Call, IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; +import {DirectCallPlugin} from "../mocks/plugins/DirectCallPlugin.sol"; + +import {AccountTestBase} from "../utils/AccountTestBase.sol"; + +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {console} from "forge-std/console.sol"; +import {LibClone} from "solady/utils/LibClone.sol"; + +contract ImmutableAppendTest is AccountTestBase { + using ValidationConfigLib for ValidationConfig; + + /* -------------------------------------------------------------------------- */ + /* Negatives */ + /* -------------------------------------------------------------------------- */ + + /* -------------------------------------------------------------------------- */ + /* Positives */ + /* -------------------------------------------------------------------------- */ + + function test_success_getData() public { + bytes memory expectedArgs = abi.encode( + PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), + abi.encode(singleSignerValidation.signerOf(TEST_DEFAULT_VALIDATION_ENTITY_ID, address(account1))) + ); + + assertEq(keccak256(LibClone.argsOnERC1967(address(account1))), keccak256(expectedArgs)); + } + + /* -------------------------------------------------------------------------- */ + /* Internals */ + /* -------------------------------------------------------------------------- */ +} diff --git a/test/mocks/SingleSignerFactoryFixture.sol b/test/mocks/SingleSignerFactoryFixture.sol index b751d440..139c2433 100644 --- a/test/mocks/SingleSignerFactoryFixture.sol +++ b/test/mocks/SingleSignerFactoryFixture.sol @@ -7,7 +7,11 @@ import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; + import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; +import {LibClone} from "solady/utils/LibClone.sol"; + +import {PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; import {OptimizedTest} from "../utils/OptimizedTest.sol"; import {TEST_DEFAULT_VALIDATION_ENTITY_ID} from "../utils/TestConstants.sol"; @@ -41,23 +45,14 @@ contract SingleSignerFactoryFixture is OptimizedTest { * account creation */ function createAccount(address owner, uint256 salt) public returns (UpgradeableModularAccount) { - address addr = Create2.computeAddress(getSalt(owner, salt), _PROXY_BYTECODE_HASH); + // address addr = Create2.computeAddress(getSalt(owner, salt), _PROXY_BYTECODE_HASH); + address addr = getAddress(owner, salt); // short circuit if exists if (addr.code.length == 0) { - bytes memory pluginInstallData = abi.encode(TEST_DEFAULT_VALIDATION_ENTITY_ID, owner); - // not necessary to check return addr since next call will fail if so - new ERC1967Proxy{salt: getSalt(owner, salt)}(address(accountImplementation), ""); - - // point proxy to actual implementation and init plugins - UpgradeableModularAccount(payable(addr)).initializeWithValidation( - ValidationConfigLib.pack( - address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID, true, true - ), - new bytes4[](0), - pluginInstallData, - "", - "" + + LibClone.createDeterministicERC1967( + address(accountImplementation), _getImmutableArgs(owner), getSalt(owner, salt) ); } @@ -68,7 +63,10 @@ contract SingleSignerFactoryFixture is OptimizedTest { * calculate the counterfactual address of this account as it would be returned by createAccount() */ function getAddress(address owner, uint256 salt) public view returns (address) { - return Create2.computeAddress(getSalt(owner, salt), _PROXY_BYTECODE_HASH); + // return Create2.computeAddress(getSalt(owner, salt), _PROXY_BYTECODE_HASH); + return LibClone.predictDeterministicAddressERC1967( + address(accountImplementation), _getImmutableArgs(owner), getSalt(owner, salt), address(this) + ); } function addStake() external payable { @@ -78,4 +76,11 @@ contract SingleSignerFactoryFixture is OptimizedTest { function getSalt(address owner, uint256 salt) public pure returns (bytes32) { return keccak256(abi.encodePacked(owner, salt)); } + + function _getImmutableArgs(address owner) private view returns (bytes memory) { + return abi.encode( + PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), + abi.encode(owner) + ); + } } From b5c593bab5dc3d467e9fea3787d4830f3a4ea6c8 Mon Sep 17 00:00:00 2001 From: zer0dot Date: Tue, 23 Jul 2024 01:08:41 +0800 Subject: [PATCH 053/111] feat: pack appended bytecode into validation-specific configurations --- src/account/UpgradeableModularAccount.sol | 10 +++++----- src/plugins/validation/SingleSignerValidation.sol | 14 ++++++++------ test/account/ImmutableAppend.t.sol | 5 ++--- test/mocks/SingleSignerFactoryFixture.sol | 5 ++--- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 5c05d651..524f999e 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.25; import {BaseAccount} from "@eth-infinitism/account-abstraction/core/BaseAccount.sol"; - import {IAccountExecute} from "@eth-infinitism/account-abstraction/interfaces/IAccountExecute.sol"; import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; @@ -792,14 +791,15 @@ contract UpgradeableModularAccount is } function _getAppendedValidation() internal view returns (PluginEntity) { - bytes memory appendedData = LibClone.argsOnERC1967(address(this)); - // Appended bytecode is under the format abi.encode(pluginEntity, validationArbitraryData) - // Validations will then decode this arbitrary data for whatever information they need if they support + // Get only the 24 first bytes of appended data + bytes memory appendedData = LibClone.argsOnERC1967(address(this), 0, 24); + // Appended bytecode is under the format abi.encodePacked(pluginEntity, any...) + // Validations will then decode this arbitrary data for whatever information they need // bytecode-appended validation. if (appendedData.length > 0) { // TODO: Evaluate if it's better to somehow pass the data back from here and have it passed to the // validation instead of having it be read from bytecode by the validation - (PluginEntity appendedValidationFunction,) = abi.decode(appendedData, (PluginEntity, bytes)); + PluginEntity appendedValidationFunction = PluginEntity.wrap(bytes24(appendedData)); return appendedValidationFunction; } return PluginEntity.wrap(bytes24(0)); diff --git a/src/plugins/validation/SingleSignerValidation.sol b/src/plugins/validation/SingleSignerValidation.sol index 1aca83ea..b4b8f952 100644 --- a/src/plugins/validation/SingleSignerValidation.sol +++ b/src/plugins/validation/SingleSignerValidation.sol @@ -152,19 +152,21 @@ contract SingleSignerValidation is ISingleSignerValidation, BasePlugin { address expectedSigner = signer[entityId][account]; if (expectedSigner == address(0)) { - // Check clone for expected validation (address(this)); - // Check clone code for expected signer + // Check clone for expected validation (address(this)) and expected signer bytes memory immutables = LibClone.argsOnERC1967(account); + // TODO: Add length check to prevent casting empty bytes, maybe? - // Note: Will opaquely revert on incorrect encoding (fun) - // But all appended bytecode should be in this format - (PluginEntity validation, bytes memory appendedData) = abi.decode(immutables, (PluginEntity, bytes)); + PluginEntity validation = PluginEntity.wrap(bytes24(immutables)); if (!validation.eq(PluginEntityLib.pack(address(this), entityId))) { revert("Validation incorrect"); } - (address decodedSigner) = abi.decode(appendedData, (address)); + address decodedSigner; + assembly { + // shr to make sure the address is right-aligned + decodedSigner := shr(96, mload(add(add(immutables, 0x20), 0x18))) + } expectedSigner = decodedSigner; } diff --git a/test/account/ImmutableAppend.t.sol b/test/account/ImmutableAppend.t.sol index 9b3d5ded..f9b6c86d 100644 --- a/test/account/ImmutableAppend.t.sol +++ b/test/account/ImmutableAppend.t.sol @@ -11,7 +11,6 @@ import {DirectCallPlugin} from "../mocks/plugins/DirectCallPlugin.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import {console} from "forge-std/console.sol"; import {LibClone} from "solady/utils/LibClone.sol"; contract ImmutableAppendTest is AccountTestBase { @@ -26,9 +25,9 @@ contract ImmutableAppendTest is AccountTestBase { /* -------------------------------------------------------------------------- */ function test_success_getData() public { - bytes memory expectedArgs = abi.encode( + bytes memory expectedArgs = abi.encodePacked( PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), - abi.encode(singleSignerValidation.signerOf(TEST_DEFAULT_VALIDATION_ENTITY_ID, address(account1))) + singleSignerValidation.signerOf(TEST_DEFAULT_VALIDATION_ENTITY_ID, address(account1)) ); assertEq(keccak256(LibClone.argsOnERC1967(address(account1))), keccak256(expectedArgs)); diff --git a/test/mocks/SingleSignerFactoryFixture.sol b/test/mocks/SingleSignerFactoryFixture.sol index 139c2433..3f33ae8f 100644 --- a/test/mocks/SingleSignerFactoryFixture.sol +++ b/test/mocks/SingleSignerFactoryFixture.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.19; import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; - import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; @@ -78,9 +77,9 @@ contract SingleSignerFactoryFixture is OptimizedTest { } function _getImmutableArgs(address owner) private view returns (bytes memory) { - return abi.encode( + return abi.encodePacked( PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), - abi.encode(owner) + owner ); } } From ce001a8bcd17ccb1232895d1f431a36f2f6c1ab3 Mon Sep 17 00:00:00 2001 From: Adam Egyed <5456061+adamegyed@users.noreply.github.com> Date: Tue, 23 Jul 2024 12:59:06 -0400 Subject: [PATCH 054/111] feat: [v0.8-develop] User controlled install 1/N (#101) --- src/account/AccountLoupe.sol | 5 - src/account/AccountStorage.sol | 2 - src/account/ModuleManagerInternals.sol | 50 ++------- src/account/UpgradeableModularAccount.sol | 24 ++--- src/helpers/KnownSelectors.sol | 3 +- src/interfaces/IAccountLoupe.sol | 4 - src/interfaces/IModuleManager.sol | 15 +-- test/account/AccountExecHooks.t.sol | 17 ++- test/account/AccountLoupe.t.sol | 14 +-- test/account/AccountReturnData.t.sol | 10 +- test/account/PermittedCallPermissions.t.sol | 10 +- test/account/SelfCallAuthorization.t.sol | 6 +- test/account/UpgradeableModularAccount.t.sol | 105 ++++--------------- test/account/ValidationIntersection.t.sol | 6 +- test/module/TokenReceiverModule.t.sol | 7 +- 15 files changed, 75 insertions(+), 203 deletions(-) diff --git a/src/account/AccountLoupe.sol b/src/account/AccountLoupe.sol index 851c9cfa..f0afc1b2 100644 --- a/src/account/AccountLoupe.sol +++ b/src/account/AccountLoupe.sol @@ -88,9 +88,4 @@ abstract contract AccountLoupe is IAccountLoupe { { preValidationHooks = getAccountStorage().validationData[validationFunction].preValidationHooks; } - - /// @inheritdoc IAccountLoupe - function getInstalledModules() external view override returns (address[] memory moduleAddresses) { - moduleAddresses = getAccountStorage().moduleManifestHashes.keys(); - } } diff --git a/src/account/AccountStorage.sol b/src/account/AccountStorage.sol index 2c480e32..c9845a8e 100644 --- a/src/account/AccountStorage.sol +++ b/src/account/AccountStorage.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.25; -import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; @@ -43,7 +42,6 @@ struct AccountStorage { // AccountStorageInitializable variables uint8 initialized; bool initializing; - EnumerableMap.AddressToUintMap moduleManifestHashes; // Execution functions and their associated functions mapping(bytes4 => SelectorData) selectorData; mapping(ModuleEntity validationFunction => ValidationData) validationData; diff --git a/src/account/ModuleManagerInternals.sol b/src/account/ModuleManagerInternals.sol index 68d6ea1d..a6a275b6 100644 --- a/src/account/ModuleManagerInternals.sol +++ b/src/account/ModuleManagerInternals.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.25; import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; -import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {KnownSelectors} from "../helpers/KnownSelectors.sol"; @@ -15,18 +14,14 @@ import {AccountStorage, SelectorData, getAccountStorage, toSetValue} from "./Acc abstract contract ModuleManagerInternals is IModuleManager { using EnumerableSet for EnumerableSet.Bytes32Set; - using EnumerableMap for EnumerableMap.AddressToUintMap; using ModuleEntityLib for ModuleEntity; error ArrayLengthMismatch(); error Erc4337FunctionNotAllowed(bytes4 selector); error ExecutionFunctionAlreadySet(bytes4 selector); - error InvalidModuleManifest(); error IModuleFunctionNotAllowed(bytes4 selector); error NativeFunctionNotAllowed(bytes4 selector); - error NullModuleEntity(); error NullModule(); - error ModuleAlreadyInstalled(address module); error ModuleInstallCallbackFailed(address module, bytes revertReason); error ModuleInterfaceNotSupported(address module); error ModuleNotInstalled(address module); @@ -138,32 +133,21 @@ abstract contract ModuleManagerInternals is IModuleManager { ); } - function _installModule(address module, bytes32 manifestHash, bytes memory moduleInstallData) internal { + function _installModule(address module, ModuleManifest calldata manifest, bytes memory moduleInstallData) + internal + { AccountStorage storage _storage = getAccountStorage(); if (module == address(0)) { revert NullModule(); } - // Check if the module exists. - if (_storage.moduleManifestHashes.contains(module)) { - revert ModuleAlreadyInstalled(module); - } - + // TODO: do we need this check? Or switch to a non-165 checking function? // Check that the module supports the IModule interface. if (!ERC165Checker.supportsInterface(module, type(IModule).interfaceId)) { revert ModuleInterfaceNotSupported(module); } - // Check manifest hash. - ModuleManifest memory manifest = IModule(module).moduleManifest(); - if (!_isValidModuleManifest(manifest, manifestHash)) { - revert InvalidModuleManifest(); - } - - // Add the module metadata to the account - _storage.moduleManifestHashes.set(module, uint256(manifestHash)); - // Update components according to the manifest. uint256 length = manifest.executionFunctions.length; for (uint256 i = 0; i < length; ++i) { @@ -200,25 +184,14 @@ abstract contract ModuleManagerInternals is IModuleManager { revert ModuleInstallCallbackFailed(module, revertReason); } - emit ModuleInstalled(module, manifestHash); + emit ModuleInstalled(module); } - function _uninstallModule(address module, ModuleManifest memory manifest, bytes memory uninstallData) + function _uninstallModule(address module, ModuleManifest calldata manifest, bytes memory uninstallData) internal { AccountStorage storage _storage = getAccountStorage(); - // Check if the module exists. - if (!_storage.moduleManifestHashes.contains(module)) { - revert ModuleNotInstalled(module); - } - - // Check manifest hash. - bytes32 manifestHash = bytes32(_storage.moduleManifestHashes.get(module)); - if (!_isValidModuleManifest(manifest, manifestHash)) { - revert InvalidModuleManifest(); - } - // Remove components according to the manifest, in reverse order (by component type) of their installation. uint256 length = manifest.executionHooks.length; @@ -245,9 +218,6 @@ abstract contract ModuleManagerInternals is IModuleManager { _storage.supportedIfaces[manifest.interfaceIds[i]] -= 1; } - // Remove the module metadata from the account. - _storage.moduleManifestHashes.remove(module); - // Clear the module storage for the account. bool onUninstallSuccess = true; // solhint-disable-next-line no-empty-blocks @@ -258,12 +228,4 @@ abstract contract ModuleManagerInternals is IModuleManager { emit ModuleUninstalled(module, onUninstallSuccess); } - - function _isValidModuleManifest(ModuleManifest memory manifest, bytes32 manifestHash) - internal - pure - returns (bool) - { - return manifestHash == keccak256(abi.encode(manifest)); - } } diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 9f102313..2f1da16d 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -19,7 +19,7 @@ import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; import {_coalescePreValidation, _coalesceValidation} from "../helpers/ValidationResHelpers.sol"; import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; -import {IModule, ModuleManifest} from "../interfaces/IModule.sol"; +import {ModuleManifest} from "../interfaces/IModule.sol"; import {IModuleManager, ModuleEntity, ValidationConfig} from "../interfaces/IModuleManager.sol"; import {Call, IStandardExecutor} from "../interfaces/IStandardExecutor.sol"; import {IValidation} from "../interfaces/IValidation.sol"; @@ -109,21 +109,21 @@ contract UpgradeableModularAccount is /// @notice Initializes the account with a set of modules /// @param modules The modules to install - /// @param manifestHashes The manifest hashes of the modules to install + /// @param manifests The manifests of the modules to install /// @param moduleInstallDatas The module install datas of the modules to install function initialize( address[] memory modules, - bytes32[] memory manifestHashes, + ModuleManifest[] calldata manifests, bytes[] memory moduleInstallDatas ) external initializer { uint256 length = modules.length; - if (length != manifestHashes.length || length != moduleInstallDatas.length) { + if (length != manifests.length || length != moduleInstallDatas.length) { revert ArrayLengthMismatch(); } for (uint256 i = 0; i < length; ++i) { - _installModule(modules[i], manifestHashes[i], moduleInstallDatas[i]); + _installModule(modules[i], manifests[i], moduleInstallDatas[i]); } emit ModularAccountInitialized(_ENTRY_POINT); @@ -249,29 +249,21 @@ contract UpgradeableModularAccount is /// @inheritdoc IModuleManager /// @notice May be validated by a global validation. - function installModule(address module, bytes32 manifestHash, bytes calldata moduleInstallData) + function installModule(address module, ModuleManifest calldata manifest, bytes calldata moduleInstallData) external override wrapNativeFunction { - _installModule(module, manifestHash, moduleInstallData); + _installModule(module, manifest, moduleInstallData); } /// @inheritdoc IModuleManager /// @notice May be validated by a global validation. - function uninstallModule(address module, bytes calldata config, bytes calldata moduleUninstallData) + function uninstallModule(address module, ModuleManifest calldata manifest, bytes calldata moduleUninstallData) external override wrapNativeFunction { - ModuleManifest memory manifest; - - if (config.length > 0) { - manifest = abi.decode(config, (ModuleManifest)); - } else { - manifest = IModule(module).moduleManifest(); - } - _uninstallModule(module, manifest, moduleUninstallData); } diff --git a/src/helpers/KnownSelectors.sol b/src/helpers/KnownSelectors.sol index 811f2f5d..ec3b6e6f 100644 --- a/src/helpers/KnownSelectors.sol +++ b/src/helpers/KnownSelectors.sol @@ -35,8 +35,7 @@ library KnownSelectors { // check against IAccountLoupe methods || selector == IAccountLoupe.getExecutionFunctionHandler.selector || selector == IAccountLoupe.getSelectors.selector || selector == IAccountLoupe.getExecutionHooks.selector - || selector == IAccountLoupe.getPreValidationHooks.selector - || selector == IAccountLoupe.getInstalledModules.selector; + || selector == IAccountLoupe.getPreValidationHooks.selector; } function isErc4337Function(bytes4 selector) internal pure returns (bool) { diff --git a/src/interfaces/IAccountLoupe.sol b/src/interfaces/IAccountLoupe.sol index 01cca512..f076de61 100644 --- a/src/interfaces/IAccountLoupe.sol +++ b/src/interfaces/IAccountLoupe.sol @@ -40,8 +40,4 @@ interface IAccountLoupe { external view returns (ModuleEntity[] memory preValidationHooks); - - /// @notice Get an array of all installed modules. - /// @return The addresses of all installed modules. - function getInstalledModules() external view returns (address[] memory); } diff --git a/src/interfaces/IModuleManager.sol b/src/interfaces/IModuleManager.sol index eb2db1ea..9923bd34 100644 --- a/src/interfaces/IModuleManager.sol +++ b/src/interfaces/IModuleManager.sol @@ -1,21 +1,24 @@ // SPDX-License-Identifier: CC0-1.0 pragma solidity ^0.8.25; +import {ModuleManifest} from "./IModule.sol"; + type ModuleEntity is bytes24; type ValidationConfig is bytes26; interface IModuleManager { - event ModuleInstalled(address indexed module, bytes32 manifestHash); + event ModuleInstalled(address indexed module); event ModuleUninstalled(address indexed module, bool indexed onUninstallSucceeded); /// @notice Install a module to the modular account. /// @param module The module to install. - /// @param manifestHash The hash of the module manifest. + /// @param manifest the manifest describing functions to install /// @param moduleInstallData Optional data to be decoded and used by the module to setup initial module data /// for the modular account. - function installModule(address module, bytes32 manifestHash, bytes calldata moduleInstallData) external; + function installModule(address module, ModuleManifest calldata manifest, bytes calldata moduleInstallData) + external; /// @notice Temporary install function - pending a different user-supplied install config & manifest validation /// path. @@ -53,9 +56,9 @@ interface IModuleManager { /// @notice Uninstall a module from the modular account. /// @param module The module to uninstall. - /// @param config An optional, implementation-specific field that accounts may use to ensure consistency - /// guarantees. + /// @param manifest the manifest describing functions to uninstall. /// @param moduleUninstallData Optional data to be decoded and used by the module to clear module data for the /// modular account. - function uninstallModule(address module, bytes calldata config, bytes calldata moduleUninstallData) external; + function uninstallModule(address module, ModuleManifest calldata manifest, bytes calldata moduleUninstallData) + external; } diff --git a/test/account/AccountExecHooks.t.sol b/test/account/AccountExecHooks.t.sol index 052cea4e..434ad0e6 100644 --- a/test/account/AccountExecHooks.t.sol +++ b/test/account/AccountExecHooks.t.sol @@ -14,8 +14,6 @@ import {AccountTestBase} from "../utils/AccountTestBase.sol"; contract AccountExecHooksTest is AccountTestBase { MockModule public mockModule1; - bytes32 public manifestHash1; - bytes32 public manifestHash2; bytes4 internal constant _EXEC_SELECTOR = bytes4(uint32(1)); uint32 internal constant _PRE_HOOK_FUNCTION_ID_1 = 1; @@ -24,7 +22,7 @@ contract AccountExecHooksTest is AccountTestBase { ModuleManifest internal _m1; - event ModuleInstalled(address indexed module, bytes32 manifestHash); + event ModuleInstalled(address indexed module); event ModuleUninstalled(address indexed module, bool indexed callbacksSucceeded); // emitted by MockModule event ReceivedCall(bytes msgData, uint256 msgValue); @@ -162,19 +160,19 @@ contract AccountExecHooksTest is AccountTestBase { function _installModule1WithHooks(ManifestExecutionHook memory execHooks) internal { _m1.executionHooks.push(execHooks); mockModule1 = new MockModule(_m1); - manifestHash1 = keccak256(abi.encode(mockModule1.moduleManifest())); vm.expectEmit(true, true, true, true); emit ReceivedCall(abi.encodeCall(IModule.onInstall, (bytes(""))), 0); vm.expectEmit(true, true, true, true); - emit ModuleInstalled(address(mockModule1), manifestHash1); + emit ModuleInstalled(address(mockModule1)); - vm.prank(address(entryPoint)); + vm.startPrank(address(entryPoint)); account1.installModule({ module: address(mockModule1), - manifestHash: manifestHash1, + manifest: mockModule1.moduleManifest(), moduleInstallData: bytes("") }); + vm.stopPrank(); } function _uninstallModule(MockModule module) internal { @@ -183,7 +181,8 @@ contract AccountExecHooksTest is AccountTestBase { vm.expectEmit(true, true, true, true); emit ModuleUninstalled(address(module), true); - vm.prank(address(entryPoint)); - account1.uninstallModule(address(module), bytes(""), bytes("")); + vm.startPrank(address(entryPoint)); + account1.uninstallModule(address(module), module.moduleManifest(), bytes("")); + vm.stopPrank(); } } diff --git a/test/account/AccountLoupe.t.sol b/test/account/AccountLoupe.t.sol index c4f4abc0..c10540fb 100644 --- a/test/account/AccountLoupe.t.sol +++ b/test/account/AccountLoupe.t.sol @@ -21,17 +21,9 @@ contract AccountLoupeTest is CustomValidationTestBase { _customValidationSetup(); - bytes32 manifestHash = keccak256(abi.encode(comprehensiveModule.moduleManifest())); - vm.prank(address(entryPoint)); - account1.installModule(address(comprehensiveModule), manifestHash, ""); - } - - function test_moduleLoupe_getInstalledModules_initial() public { - address[] memory modules = account1.getInstalledModules(); - - assertEq(modules.length, 1); - - assertEq(modules[0], address(comprehensiveModule)); + vm.startPrank(address(entryPoint)); + account1.installModule(address(comprehensiveModule), comprehensiveModule.moduleManifest(), ""); + vm.stopPrank(); } function test_moduleLoupe_getExecutionFunctionHandler_native() public { diff --git a/test/account/AccountReturnData.t.sol b/test/account/AccountReturnData.t.sol index febaa54b..7d51e9d1 100644 --- a/test/account/AccountReturnData.t.sol +++ b/test/account/AccountReturnData.t.sol @@ -26,21 +26,19 @@ contract AccountReturnDataTest is AccountTestBase { resultConsumerModule = new ResultConsumerModule(resultCreatorModule, regularResultContract); // Add the result creator module to the account - bytes32 resultCreatorManifestHash = keccak256(abi.encode(resultCreatorModule.moduleManifest())); - vm.prank(address(entryPoint)); + vm.startPrank(address(entryPoint)); account1.installModule({ module: address(resultCreatorModule), - manifestHash: resultCreatorManifestHash, + manifest: resultCreatorModule.moduleManifest(), moduleInstallData: "" }); // Add the result consumer module to the account - bytes32 resultConsumerManifestHash = keccak256(abi.encode(resultConsumerModule.moduleManifest())); - vm.prank(address(entryPoint)); account1.installModule({ module: address(resultConsumerModule), - manifestHash: resultConsumerManifestHash, + manifest: resultConsumerModule.moduleManifest(), moduleInstallData: "" }); + vm.stopPrank(); } // Tests the ability to read the result of module execution functions via the account's fallback diff --git a/test/account/PermittedCallPermissions.t.sol b/test/account/PermittedCallPermissions.t.sol index 1c378e9e..0b1007f7 100644 --- a/test/account/PermittedCallPermissions.t.sol +++ b/test/account/PermittedCallPermissions.t.sol @@ -21,21 +21,19 @@ contract PermittedCallPermissionsTest is AccountTestBase { permittedCallerModule = new PermittedCallerModule(); // Add the result creator module to the account - bytes32 resultCreatorManifestHash = keccak256(abi.encode(resultCreatorModule.moduleManifest())); - vm.prank(address(entryPoint)); + vm.startPrank(address(entryPoint)); account1.installModule({ module: address(resultCreatorModule), - manifestHash: resultCreatorManifestHash, + manifest: resultCreatorModule.moduleManifest(), moduleInstallData: "" }); // Add the permitted caller module to the account - bytes32 permittedCallerManifestHash = keccak256(abi.encode(permittedCallerModule.moduleManifest())); - vm.prank(address(entryPoint)); account1.installModule({ module: address(permittedCallerModule), - manifestHash: permittedCallerManifestHash, + manifest: permittedCallerModule.moduleManifest(), moduleInstallData: "" }); + vm.stopPrank(); } function test_permittedCall_Allowed() public { diff --git a/test/account/SelfCallAuthorization.t.sol b/test/account/SelfCallAuthorization.t.sol index aae886eb..a9f61905 100644 --- a/test/account/SelfCallAuthorization.t.sol +++ b/test/account/SelfCallAuthorization.t.sol @@ -24,9 +24,9 @@ contract SelfCallAuthorizationTest is AccountTestBase { comprehensiveModule = new ComprehensiveModule(); - bytes32 manifestHash = keccak256(abi.encode(comprehensiveModule.moduleManifest())); - vm.prank(address(entryPoint)); - account1.installModule(address(comprehensiveModule), manifestHash, ""); + vm.startPrank(address(entryPoint)); + account1.installModule(address(comprehensiveModule), comprehensiveModule.moduleManifest(), ""); + vm.stopPrank(); comprehensiveModuleValidation = ModuleEntityLib.pack(address(comprehensiveModule), uint32(ComprehensiveModule.EntityId.VALIDATION)); diff --git a/test/account/UpgradeableModularAccount.t.sol b/test/account/UpgradeableModularAccount.t.sol index 76a1c4f0..847ab552 100644 --- a/test/account/UpgradeableModularAccount.t.sol +++ b/test/account/UpgradeableModularAccount.t.sol @@ -42,7 +42,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { Counter public counter; ModuleManifest internal _manifest; - event ModuleInstalled(address indexed module, bytes32 manifestHash); + event ModuleInstalled(address indexed module); event ModuleUninstalled(address indexed module, bool indexed callbacksSucceeded); event ReceivedCall(bytes msgData, uint256 msgValue); @@ -239,19 +239,17 @@ contract UpgradeableModularAccountTest is AccountTestBase { function test_installModule() public { vm.startPrank(address(entryPoint)); - bytes32 manifestHash = keccak256(abi.encode(tokenReceiverModule.moduleManifest())); - vm.expectEmit(true, true, true, true); - emit ModuleInstalled(address(tokenReceiverModule), manifestHash); + emit ModuleInstalled(address(tokenReceiverModule)); IModuleManager(account1).installModule({ module: address(tokenReceiverModule), - manifestHash: manifestHash, + manifest: tokenReceiverModule.moduleManifest(), moduleInstallData: abi.encode(uint48(1 days)) }); - address[] memory modules = IAccountLoupe(account1).getInstalledModules(); - assertEq(modules.length, 1); - assertEq(modules[0], address(tokenReceiverModule)); + address handler = + IAccountLoupe(account1).getExecutionFunctionHandler(TokenReceiverModule.onERC721Received.selector); + assertEq(handler, address(tokenReceiverModule)); } function test_installModule_PermittedCallSelectorNotInstalled() public { @@ -260,26 +258,14 @@ contract UpgradeableModularAccountTest is AccountTestBase { ModuleManifest memory m; MockModule mockModuleWithBadPermittedExec = new MockModule(m); - bytes32 manifestHash = keccak256(abi.encode(mockModuleWithBadPermittedExec.moduleManifest())); IModuleManager(account1).installModule({ module: address(mockModuleWithBadPermittedExec), - manifestHash: manifestHash, + manifest: mockModuleWithBadPermittedExec.moduleManifest(), moduleInstallData: "" }); } - function test_installModule_invalidManifest() public { - vm.startPrank(address(entryPoint)); - - vm.expectRevert(abi.encodeWithSelector(ModuleManagerInternals.InvalidModuleManifest.selector)); - IModuleManager(account1).installModule({ - module: address(tokenReceiverModule), - manifestHash: bytes32(0), - moduleInstallData: abi.encode(uint48(1 days)) - }); - } - function test_installModule_interfaceNotSupported() public { vm.startPrank(address(entryPoint)); @@ -287,31 +273,32 @@ contract UpgradeableModularAccountTest is AccountTestBase { vm.expectRevert( abi.encodeWithSelector(ModuleManagerInternals.ModuleInterfaceNotSupported.selector, address(badModule)) ); - IModuleManager(account1).installModule({ - module: address(badModule), - manifestHash: bytes32(0), - moduleInstallData: "" - }); + + ModuleManifest memory m; + + IModuleManager(account1).installModule({module: address(badModule), manifest: m, moduleInstallData: ""}); } function test_installModule_alreadyInstalled() public { - vm.startPrank(address(entryPoint)); + ModuleManifest memory m = tokenReceiverModule.moduleManifest(); - bytes32 manifestHash = keccak256(abi.encode(tokenReceiverModule.moduleManifest())); + vm.prank(address(entryPoint)); IModuleManager(account1).installModule({ module: address(tokenReceiverModule), - manifestHash: manifestHash, + manifest: m, moduleInstallData: abi.encode(uint48(1 days)) }); + vm.prank(address(entryPoint)); vm.expectRevert( abi.encodeWithSelector( - ModuleManagerInternals.ModuleAlreadyInstalled.selector, address(tokenReceiverModule) + ModuleManagerInternals.ExecutionFunctionAlreadySet.selector, + TokenReceiverModule.onERC721Received.selector ) ); IModuleManager(account1).installModule({ module: address(tokenReceiverModule), - manifestHash: manifestHash, + manifest: m, moduleInstallData: abi.encode(uint48(1 days)) }); } @@ -320,29 +307,9 @@ contract UpgradeableModularAccountTest is AccountTestBase { vm.startPrank(address(entryPoint)); ComprehensiveModule module = new ComprehensiveModule(); - bytes32 manifestHash = keccak256(abi.encode(module.moduleManifest())); - IModuleManager(account1).installModule({ - module: address(module), - manifestHash: manifestHash, - moduleInstallData: "" - }); - - vm.expectEmit(true, true, true, true); - emit ModuleUninstalled(address(module), true); - IModuleManager(account1).uninstallModule({module: address(module), config: "", moduleUninstallData: ""}); - address[] memory modules = IAccountLoupe(account1).getInstalledModules(); - assertEq(modules.length, 0); - } - - function test_uninstallModule_manifestParameter() public { - vm.startPrank(address(entryPoint)); - - ComprehensiveModule module = new ComprehensiveModule(); - bytes memory serializedManifest = abi.encode(module.moduleManifest()); - bytes32 manifestHash = keccak256(serializedManifest); IModuleManager(account1).installModule({ module: address(module), - manifestHash: manifestHash, + manifest: module.moduleManifest(), moduleInstallData: "" }); @@ -350,48 +317,22 @@ contract UpgradeableModularAccountTest is AccountTestBase { emit ModuleUninstalled(address(module), true); IModuleManager(account1).uninstallModule({ module: address(module), - config: serializedManifest, + manifest: module.moduleManifest(), moduleUninstallData: "" }); - address[] memory modules = IAccountLoupe(account1).getInstalledModules(); - assertEq(modules.length, 0); - } - - function test_uninstallModule_invalidManifestFails() public { - vm.startPrank(address(entryPoint)); - - ComprehensiveModule module = new ComprehensiveModule(); - bytes memory serializedManifest = abi.encode(module.moduleManifest()); - bytes32 manifestHash = keccak256(serializedManifest); - IModuleManager(account1).installModule({ - module: address(module), - manifestHash: manifestHash, - moduleInstallData: "" - }); - // Attempt to uninstall with a blank _manifest - ModuleManifest memory blankManifest; - - vm.expectRevert(abi.encodeWithSelector(ModuleManagerInternals.InvalidModuleManifest.selector)); - IModuleManager(account1).uninstallModule({ - module: address(module), - config: abi.encode(blankManifest), - moduleUninstallData: "" - }); - address[] memory modules = IAccountLoupe(account1).getInstalledModules(); - assertEq(modules.length, 1); - assertEq(modules[0], address(module)); + address handler = IAccountLoupe(account1).getExecutionFunctionHandler(module.foo.selector); + assertEq(handler, address(0)); } function _installModuleWithExecHooks() internal returns (MockModule module) { vm.startPrank(address(entryPoint)); module = new MockModule(_manifest); - bytes32 manifestHash = keccak256(abi.encode(module.moduleManifest())); IModuleManager(account1).installModule({ module: address(module), - manifestHash: manifestHash, + manifest: module.moduleManifest(), moduleInstallData: "" }); diff --git a/test/account/ValidationIntersection.t.sol b/test/account/ValidationIntersection.t.sol index 82a8fa7f..9662702f 100644 --- a/test/account/ValidationIntersection.t.sol +++ b/test/account/ValidationIntersection.t.sol @@ -49,12 +49,12 @@ contract ValidationIntersectionTest is AccountTestBase { vm.startPrank(address(entryPoint)); account1.installModule({ module: address(noHookModule), - manifestHash: keccak256(abi.encode(noHookModule.moduleManifest())), + manifest: noHookModule.moduleManifest(), moduleInstallData: "" }); account1.installModule({ module: address(oneHookModule), - manifestHash: keccak256(abi.encode(oneHookModule.moduleManifest())), + manifest: oneHookModule.moduleManifest(), moduleInstallData: "" }); // TODO: change with new install flow @@ -74,7 +74,7 @@ contract ValidationIntersectionTest is AccountTestBase { ); account1.installModule({ module: address(twoHookModule), - manifestHash: keccak256(abi.encode(twoHookModule.moduleManifest())), + manifest: twoHookModule.moduleManifest(), moduleInstallData: "" }); // temporary fix to add the pre-validation hook diff --git a/test/module/TokenReceiverModule.t.sol b/test/module/TokenReceiverModule.t.sol index ffe85e86..3230d221 100644 --- a/test/module/TokenReceiverModule.t.sol +++ b/test/module/TokenReceiverModule.t.sol @@ -55,10 +55,9 @@ contract TokenReceiverModuleTest is OptimizedTest, IERC1155Receiver { } function _initModule() internal { - bytes32 manifestHash = keccak256(abi.encode(module.moduleManifest())); - - vm.prank(address(entryPoint)); - acct.installModule(address(module), manifestHash, ""); + vm.startPrank(address(entryPoint)); + acct.installModule(address(module), module.moduleManifest(), ""); + vm.stopPrank(); } function test_failERC721Transfer() public { From c41cc7ca74abd34225a901a37eae35dcb3e930f8 Mon Sep 17 00:00:00 2001 From: Adam Egyed <5456061+adamegyed@users.noreply.github.com> Date: Tue, 23 Jul 2024 13:04:52 -0400 Subject: [PATCH 055/111] feat: [v0.8-develop] merge plugin manager contracts 2/N (#102) --- src/account/ModuleManager2.sol | 148 --------------- src/account/ModuleManagerInternals.sol | 181 ++++++++++++++++--- src/account/UpgradeableModularAccount.sol | 2 - src/interfaces/IModule.sol | 2 +- test/mocks/modules/ComprehensiveModule.sol | 2 +- test/mocks/modules/ReturnDataModuleMocks.sol | 2 +- test/mocks/modules/ValidationModuleMocks.sol | 6 +- 7 files changed, 162 insertions(+), 181 deletions(-) delete mode 100644 src/account/ModuleManager2.sol diff --git a/src/account/ModuleManager2.sol b/src/account/ModuleManager2.sol deleted file mode 100644 index e1868403..00000000 --- a/src/account/ModuleManager2.sol +++ /dev/null @@ -1,148 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.25; - -import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; - -import {ModuleEntityLib} from "../helpers/ModuleEntityLib.sol"; -import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; - -import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; -import {IModule} from "../interfaces/IModule.sol"; -import {ModuleEntity, ValidationConfig} from "../interfaces/IModuleManager.sol"; -import {ValidationData, getAccountStorage, toSetValue} from "./AccountStorage.sol"; - -// Temporary additional functions for a user-controlled install flow for validation functions. -abstract contract ModuleManager2 { - using EnumerableSet for EnumerableSet.Bytes32Set; - using ValidationConfigLib for ValidationConfig; - - // Index marking the start of the data for the validation function. - uint8 internal constant _RESERVED_VALIDATION_DATA_INDEX = 255; - uint32 internal constant _SELF_PERMIT_VALIDATION_FUNCTIONID = type(uint32).max; - - error PreValidationAlreadySet(ModuleEntity validationFunction, ModuleEntity preValidationFunction); - error ValidationAlreadySet(bytes4 selector, ModuleEntity validationFunction); - error ValidationNotSet(bytes4 selector, ModuleEntity validationFunction); - error PermissionAlreadySet(ModuleEntity validationFunction, ExecutionHook hook); - error PreValidationHookLimitExceeded(); - - function _installValidation( - ValidationConfig validationConfig, - bytes4[] memory selectors, - bytes calldata installData, - bytes memory preValidationHooks, - bytes memory permissionHooks - ) internal { - ValidationData storage _validationData = - getAccountStorage().validationData[validationConfig.moduleEntity()]; - - if (preValidationHooks.length > 0) { - (ModuleEntity[] memory preValidationFunctions, bytes[] memory initDatas) = - abi.decode(preValidationHooks, (ModuleEntity[], bytes[])); - - for (uint256 i = 0; i < preValidationFunctions.length; ++i) { - ModuleEntity preValidationFunction = preValidationFunctions[i]; - - _validationData.preValidationHooks.push(preValidationFunction); - - if (initDatas[i].length > 0) { - (address preValidationModule,) = ModuleEntityLib.unpack(preValidationFunction); - IModule(preValidationModule).onInstall(initDatas[i]); - } - } - - // Avoid collision between reserved index and actual indices - if (_validationData.preValidationHooks.length > _RESERVED_VALIDATION_DATA_INDEX) { - revert PreValidationHookLimitExceeded(); - } - } - - if (permissionHooks.length > 0) { - (ExecutionHook[] memory permissionFunctions, bytes[] memory initDatas) = - abi.decode(permissionHooks, (ExecutionHook[], bytes[])); - - for (uint256 i = 0; i < permissionFunctions.length; ++i) { - ExecutionHook memory permissionFunction = permissionFunctions[i]; - - if (!_validationData.permissionHooks.add(toSetValue(permissionFunction))) { - revert PermissionAlreadySet(validationConfig.moduleEntity(), permissionFunction); - } - - if (initDatas[i].length > 0) { - (address executionModule,) = ModuleEntityLib.unpack(permissionFunction.hookFunction); - IModule(executionModule).onInstall(initDatas[i]); - } - } - } - - for (uint256 i = 0; i < selectors.length; ++i) { - bytes4 selector = selectors[i]; - if (!_validationData.selectors.add(toSetValue(selector))) { - revert ValidationAlreadySet(selector, validationConfig.moduleEntity()); - } - } - - if (validationConfig.entityId() != _SELF_PERMIT_VALIDATION_FUNCTIONID) { - // Only allow global validations and signature validations if they're not direct-call validations. - - _validationData.isGlobal = validationConfig.isGlobal(); - _validationData.isSignatureValidation = validationConfig.isSignatureValidation(); - if (installData.length > 0) { - IModule(validationConfig.module()).onInstall(installData); - } - } - } - - function _uninstallValidation( - ModuleEntity validationFunction, - bytes calldata uninstallData, - bytes calldata preValidationHookUninstallData, - bytes calldata permissionHookUninstallData - ) internal { - ValidationData storage _validationData = getAccountStorage().validationData[validationFunction]; - - _validationData.isGlobal = false; - _validationData.isSignatureValidation = false; - - { - bytes[] memory preValidationHookUninstallDatas = abi.decode(preValidationHookUninstallData, (bytes[])); - - // Clear pre validation hooks - ModuleEntity[] storage preValidationHooks = _validationData.preValidationHooks; - for (uint256 i = 0; i < preValidationHooks.length; ++i) { - ModuleEntity preValidationFunction = preValidationHooks[i]; - if (preValidationHookUninstallDatas[0].length > 0) { - (address preValidationModule,) = ModuleEntityLib.unpack(preValidationFunction); - IModule(preValidationModule).onUninstall(preValidationHookUninstallDatas[0]); - } - } - delete _validationData.preValidationHooks; - } - - { - bytes[] memory permissionHookUninstallDatas = abi.decode(permissionHookUninstallData, (bytes[])); - - // Clear permission hooks - EnumerableSet.Bytes32Set storage permissionHooks = _validationData.permissionHooks; - uint256 permissionHookLen = permissionHooks.length(); - for (uint256 i = 0; i < permissionHookLen; ++i) { - bytes32 permissionHook = permissionHooks.at(0); - permissionHooks.remove(permissionHook); - address permissionHookModule = address(uint160(bytes20(permissionHook))); - IModule(permissionHookModule).onUninstall(permissionHookUninstallDatas[i]); - } - } - - // Clear selectors - uint256 selectorLen = _validationData.selectors.length(); - for (uint256 i = 0; i < selectorLen; ++i) { - bytes32 selectorSetValue = _validationData.selectors.at(0); - _validationData.selectors.remove(selectorSetValue); - } - - if (uninstallData.length > 0) { - (address module,) = ModuleEntityLib.unpack(validationFunction); - IModule(module).onUninstall(uninstallData); - } - } -} diff --git a/src/account/ModuleManagerInternals.sol b/src/account/ModuleManagerInternals.sol index a6a275b6..9fd60c45 100644 --- a/src/account/ModuleManagerInternals.sol +++ b/src/account/ModuleManagerInternals.sol @@ -7,14 +7,22 @@ import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet import {KnownSelectors} from "../helpers/KnownSelectors.sol"; import {ModuleEntityLib} from "../helpers/ModuleEntityLib.sol"; +import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; import {IModule, ManifestExecutionHook, ManifestValidation, ModuleManifest} from "../interfaces/IModule.sol"; -import {IModuleManager, ModuleEntity} from "../interfaces/IModuleManager.sol"; -import {AccountStorage, SelectorData, getAccountStorage, toSetValue} from "./AccountStorage.sol"; +import {IModuleManager, ModuleEntity, ValidationConfig} from "../interfaces/IModuleManager.sol"; +import {AccountStorage, SelectorData, ValidationData, getAccountStorage, toSetValue} from "./AccountStorage.sol"; abstract contract ModuleManagerInternals is IModuleManager { using EnumerableSet for EnumerableSet.Bytes32Set; using ModuleEntityLib for ModuleEntity; + using ValidationConfigLib for ValidationConfig; + + // Index marking the start of the data for the validation function. + uint8 internal constant _RESERVED_VALIDATION_DATA_INDEX = 255; + + // Magic value for the Entity ID of direct call validation. + uint32 internal constant _SELF_PERMIT_VALIDATION_FUNCTIONID = type(uint32).max; error ArrayLengthMismatch(); error Erc4337FunctionNotAllowed(bytes4 selector); @@ -22,10 +30,12 @@ abstract contract ModuleManagerInternals is IModuleManager { error IModuleFunctionNotAllowed(bytes4 selector); error NativeFunctionNotAllowed(bytes4 selector); error NullModule(); + error PermissionAlreadySet(ModuleEntity validationFunction, ExecutionHook hook); error ModuleInstallCallbackFailed(address module, bytes revertReason); error ModuleInterfaceNotSupported(address module); error ModuleNotInstalled(address module); - error ValidationFunctionAlreadySet(bytes4 selector, ModuleEntity validationFunction); + error PreValidationHookLimitExceeded(); + error ValidationAlreadySet(bytes4 selector, ModuleEntity validationFunction); // Storage update operations @@ -71,39 +81,35 @@ abstract contract ModuleManagerInternals is IModuleManager { _selectorData.allowGlobalValidation = false; } - function _addValidationFunction(address module, ManifestValidation memory mv) internal { - AccountStorage storage _storage = getAccountStorage(); + function _addValidationFunction(ValidationConfig validationConfig, bytes4[] memory selectors) internal { + ValidationData storage _validationData = + getAccountStorage().validationData[validationConfig.moduleEntity()]; - ModuleEntity validationFunction = ModuleEntityLib.pack(module, mv.entityId); - - if (mv.isDefault) { - _storage.validationData[validationFunction].isGlobal = true; + if (validationConfig.isGlobal()) { + _validationData.isGlobal = true; } - if (mv.isSignatureValidation) { - _storage.validationData[validationFunction].isSignatureValidation = true; + if (validationConfig.isSignatureValidation()) { + _validationData.isSignatureValidation = true; } // Add the validation function to the selectors. - uint256 length = mv.selectors.length; + uint256 length = selectors.length; for (uint256 i = 0; i < length; ++i) { - bytes4 selector = mv.selectors[i]; - _storage.validationData[validationFunction].selectors.add(toSetValue(selector)); + _validationData.selectors.add(toSetValue(selectors[i])); } } - function _removeValidationFunction(address module, ManifestValidation memory mv) internal { - AccountStorage storage _storage = getAccountStorage(); - - ModuleEntity validationFunction = ModuleEntityLib.pack(module, mv.entityId); + function _removeValidationFunction(ModuleEntity validationFunction) internal { + ValidationData storage _validationData = getAccountStorage().validationData[validationFunction]; - _storage.validationData[validationFunction].isGlobal = false; - _storage.validationData[validationFunction].isSignatureValidation = false; + _validationData.isGlobal = false; + _validationData.isSignatureValidation = false; // Clear the selectors - while (_storage.validationData[validationFunction].selectors.length() > 0) { - bytes32 selector = _storage.validationData[validationFunction].selectors.at(0); - _storage.validationData[validationFunction].selectors.remove(selector); + uint256 length = _validationData.selectors.length(); + for (uint256 i = 0; i < length; ++i) { + _validationData.selectors.remove(_validationData.selectors.at(0)); } } @@ -161,7 +167,11 @@ abstract contract ModuleManagerInternals is IModuleManager { for (uint256 i = 0; i < length; ++i) { // Todo: limit this to only "direct runtime call" validation path (old EFP), // and add a way for the user to specify permission/pre-val hooks here. - _addValidationFunction(module, manifest.validationFunctions[i]); + ManifestValidation memory mv = manifest.validationFunctions[i]; + + ValidationConfig validationConfig = + ValidationConfigLib.pack(module, mv.entityId, mv.isGlobal, mv.isSignatureValidation); + _addValidationFunction(validationConfig, mv.selectors); } length = manifest.executionHooks.length; @@ -204,7 +214,9 @@ abstract contract ModuleManagerInternals is IModuleManager { length = manifest.validationFunctions.length; for (uint256 i = 0; i < length; ++i) { - _removeValidationFunction(module, manifest.validationFunctions[i]); + ModuleEntity validationFunction = + ModuleEntityLib.pack(module, manifest.validationFunctions[i].entityId); + _removeValidationFunction(validationFunction); } length = manifest.executionFunctions.length; @@ -228,4 +240,123 @@ abstract contract ModuleManagerInternals is IModuleManager { emit ModuleUninstalled(module, onUninstallSuccess); } + + function _installValidation( + ValidationConfig validationConfig, + bytes4[] memory selectors, + bytes calldata installData, + bytes memory preValidationHooks, + bytes memory permissionHooks + ) internal { + ValidationData storage _validationData = + getAccountStorage().validationData[validationConfig.moduleEntity()]; + + if (preValidationHooks.length > 0) { + (ModuleEntity[] memory preValidationFunctions, bytes[] memory initDatas) = + abi.decode(preValidationHooks, (ModuleEntity[], bytes[])); + + for (uint256 i = 0; i < preValidationFunctions.length; ++i) { + ModuleEntity preValidationFunction = preValidationFunctions[i]; + + _validationData.preValidationHooks.push(preValidationFunction); + + if (initDatas[i].length > 0) { + (address preValidationPlugin,) = ModuleEntityLib.unpack(preValidationFunction); + IModule(preValidationPlugin).onInstall(initDatas[i]); + } + } + + // Avoid collision between reserved index and actual indices + if (_validationData.preValidationHooks.length > _RESERVED_VALIDATION_DATA_INDEX) { + revert PreValidationHookLimitExceeded(); + } + } + + if (permissionHooks.length > 0) { + (ExecutionHook[] memory permissionFunctions, bytes[] memory initDatas) = + abi.decode(permissionHooks, (ExecutionHook[], bytes[])); + + for (uint256 i = 0; i < permissionFunctions.length; ++i) { + ExecutionHook memory permissionFunction = permissionFunctions[i]; + + if (!_validationData.permissionHooks.add(toSetValue(permissionFunction))) { + revert PermissionAlreadySet(validationConfig.moduleEntity(), permissionFunction); + } + + if (initDatas[i].length > 0) { + (address executionPlugin,) = ModuleEntityLib.unpack(permissionFunction.hookFunction); + IModule(executionPlugin).onInstall(initDatas[i]); + } + } + } + + for (uint256 i = 0; i < selectors.length; ++i) { + bytes4 selector = selectors[i]; + if (!_validationData.selectors.add(toSetValue(selector))) { + revert ValidationAlreadySet(selector, validationConfig.moduleEntity()); + } + } + + if (validationConfig.entityId() != _SELF_PERMIT_VALIDATION_FUNCTIONID) { + // Only allow global validations and signature validations if they're not direct-call validations. + + _validationData.isGlobal = validationConfig.isGlobal(); + _validationData.isSignatureValidation = validationConfig.isSignatureValidation(); + if (installData.length > 0) { + IModule(validationConfig.module()).onInstall(installData); + } + } + } + + function _uninstallValidation( + ModuleEntity validationFunction, + bytes calldata uninstallData, + bytes calldata preValidationHookUninstallData, + bytes calldata permissionHookUninstallData + ) internal { + ValidationData storage _validationData = getAccountStorage().validationData[validationFunction]; + + _removeValidationFunction(validationFunction); + + { + bytes[] memory preValidationHookUninstallDatas = abi.decode(preValidationHookUninstallData, (bytes[])); + + // Clear pre validation hooks + ModuleEntity[] storage preValidationHooks = _validationData.preValidationHooks; + for (uint256 i = 0; i < preValidationHooks.length; ++i) { + ModuleEntity preValidationFunction = preValidationHooks[i]; + if (preValidationHookUninstallDatas[0].length > 0) { + (address preValidationPlugin,) = ModuleEntityLib.unpack(preValidationFunction); + IModule(preValidationPlugin).onUninstall(preValidationHookUninstallDatas[0]); + } + } + delete _validationData.preValidationHooks; + } + + { + bytes[] memory permissionHookUninstallDatas = abi.decode(permissionHookUninstallData, (bytes[])); + + // Clear permission hooks + EnumerableSet.Bytes32Set storage permissionHooks = _validationData.permissionHooks; + uint256 permissionHookLen = permissionHooks.length(); + for (uint256 i = 0; i < permissionHookLen; ++i) { + bytes32 permissionHook = permissionHooks.at(0); + permissionHooks.remove(permissionHook); + address permissionHookPlugin = address(uint160(bytes20(permissionHook))); + IModule(permissionHookPlugin).onUninstall(permissionHookUninstallDatas[i]); + } + } + + // Clear selectors + uint256 selectorLen = _validationData.selectors.length(); + for (uint256 i = 0; i < selectorLen; ++i) { + bytes32 selectorSetValue = _validationData.selectors.at(0); + _validationData.selectors.remove(selectorSetValue); + } + + if (uninstallData.length > 0) { + (address plugin,) = ModuleEntityLib.unpack(validationFunction); + IModule(plugin).onUninstall(uninstallData); + } + } } diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 2f1da16d..737beebb 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -29,7 +29,6 @@ import {AccountLoupe} from "./AccountLoupe.sol"; import {AccountStorage, getAccountStorage, toExecutionHook, toSetValue} from "./AccountStorage.sol"; import {AccountStorageInitializable} from "./AccountStorageInitializable.sol"; -import {ModuleManager2} from "./ModuleManager2.sol"; import {ModuleManagerInternals} from "./ModuleManagerInternals.sol"; contract UpgradeableModularAccount is @@ -42,7 +41,6 @@ contract UpgradeableModularAccount is IStandardExecutor, IAccountExecute, ModuleManagerInternals, - ModuleManager2, UUPSUpgradeable { using EnumerableSet for EnumerableSet.Bytes32Set; diff --git a/src/interfaces/IModule.sol b/src/interfaces/IModule.sol index 8851acaf..ab78b222 100644 --- a/src/interfaces/IModule.sol +++ b/src/interfaces/IModule.sol @@ -16,7 +16,7 @@ struct ManifestExecutionFunction { // todo: do we need these at all? Or do we fully switch to `installValidation`? struct ManifestValidation { uint32 entityId; - bool isDefault; + bool isGlobal; bool isSignatureValidation; bytes4[] selectors; } diff --git a/test/mocks/modules/ComprehensiveModule.sol b/test/mocks/modules/ComprehensiveModule.sol index bb73b5db..aca09f92 100644 --- a/test/mocks/modules/ComprehensiveModule.sol +++ b/test/mocks/modules/ComprehensiveModule.sol @@ -146,7 +146,7 @@ contract ComprehensiveModule is IValidation, IValidationHook, IExecutionHook, Ba manifest.validationFunctions = new ManifestValidation[](1); manifest.validationFunctions[0] = ManifestValidation({ entityId: uint32(EntityId.VALIDATION), - isDefault: true, + isGlobal: true, isSignatureValidation: false, selectors: validationSelectors }); diff --git a/test/mocks/modules/ReturnDataModuleMocks.sol b/test/mocks/modules/ReturnDataModuleMocks.sol index 8fe3241d..1f219e8b 100644 --- a/test/mocks/modules/ReturnDataModuleMocks.sol +++ b/test/mocks/modules/ReturnDataModuleMocks.sol @@ -126,7 +126,7 @@ contract ResultConsumerModule is BaseModule, IValidation { manifest.validationFunctions = new ManifestValidation[](1); manifest.validationFunctions[0] = ManifestValidation({ entityId: 0, - isDefault: true, + isGlobal: true, isSignatureValidation: false, selectors: validationSelectors }); diff --git a/test/mocks/modules/ValidationModuleMocks.sol b/test/mocks/modules/ValidationModuleMocks.sol index ed152af2..4c37190f 100644 --- a/test/mocks/modules/ValidationModuleMocks.sol +++ b/test/mocks/modules/ValidationModuleMocks.sol @@ -118,7 +118,7 @@ contract MockUserOpValidationModule is MockBaseUserOpValidationModule { manifest.validationFunctions = new ManifestValidation[](1); manifest.validationFunctions[0] = ManifestValidation({ entityId: uint32(EntityId.USER_OP_VALIDATION), - isDefault: false, + isGlobal: false, isSignatureValidation: false, selectors: validationSelectors }); @@ -161,7 +161,7 @@ contract MockUserOpValidation1HookModule is MockBaseUserOpValidationModule { manifest.validationFunctions = new ManifestValidation[](2); manifest.validationFunctions[0] = ManifestValidation({ entityId: uint32(EntityId.USER_OP_VALIDATION), - isDefault: false, + isGlobal: false, isSignatureValidation: false, selectors: validationSelectors }); @@ -207,7 +207,7 @@ contract MockUserOpValidation2HookModule is MockBaseUserOpValidationModule { manifest.validationFunctions = new ManifestValidation[](1); manifest.validationFunctions[0] = ManifestValidation({ entityId: uint32(EntityId.USER_OP_VALIDATION), - isDefault: false, + isGlobal: false, isSignatureValidation: false, selectors: validationSelectors }); From 13a36fa7ff506bc7d5f511bac193cf6c1731b6f4 Mon Sep 17 00:00:00 2001 From: howydev <132113803+howydev@users.noreply.github.com> Date: Thu, 18 Jul 2024 15:53:09 -0700 Subject: [PATCH 056/111] feat: add factory --- src/account/AccountFactory.sol | 76 ++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 src/account/AccountFactory.sol diff --git a/src/account/AccountFactory.sol b/src/account/AccountFactory.sol new file mode 100644 index 00000000..4a7e9686 --- /dev/null +++ b/src/account/AccountFactory.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; + +import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; +import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; +import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; + +contract AccountFactory { + UpgradeableModularAccount public accountImplementation; + bytes32 private immutable _PROXY_BYTECODE_HASH; + uint32 public constant UNSTAKE_DELAY = 1 weeks; + IEntryPoint public entryPoint; + address public self; + + constructor(IEntryPoint _entryPoint, UpgradeableModularAccount _accountImpl) { + entryPoint = _entryPoint; + _PROXY_BYTECODE_HASH = keccak256( + abi.encodePacked(type(ERC1967Proxy).creationCode, abi.encode(address(accountImplementation), "")) + ); + accountImplementation = _accountImpl; + self = address(this); + } + + /** + * create an account, and return its address. + * returns the address even if the account is already deployed. + * Note that during user operation execution, this method is called only if the account is not deployed. + * This method returns an existing account address so that entryPoint.getSenderAddress() would work even after + * account creation + */ + function createAccount( + address owner, + uint256 salt, + uint32 entityId, + SingleSignerValidation singleSignerValidation + ) public returns (UpgradeableModularAccount) { + address addr = Create2.computeAddress(getSalt(owner, salt), _PROXY_BYTECODE_HASH); + + // short circuit if exists + if (addr.code.length == 0) { + bytes memory pluginInstallData = abi.encode(entityId, owner); + // not necessary to check return addr since next call will fail if so + new ERC1967Proxy{salt: getSalt(owner, salt)}(address(accountImplementation), ""); + + // point proxy to actual implementation and init plugins + UpgradeableModularAccount(payable(addr)).initializeWithValidation( + ValidationConfigLib.pack(address(singleSignerValidation), entityId, true, true), + new bytes4[](0), + pluginInstallData, + "", + "" + ); + } + + return UpgradeableModularAccount(payable(addr)); + } + + /** + * alculate the counterfactual address of this account as it would be returned by createAccount() + */ + function getAddress(address owner, uint256 salt) public view returns (address) { + return Create2.computeAddress(getSalt(owner, salt), _PROXY_BYTECODE_HASH); + } + + function addStake() external payable { + entryPoint.addStake{value: msg.value}(UNSTAKE_DELAY); + } + + function getSalt(address owner, uint256 salt) public pure returns (bytes32) { + return keccak256(abi.encodePacked(owner, salt)); + } +} From 3b100c5d5bbe821f97e180953b302805b7dc971e Mon Sep 17 00:00:00 2001 From: howydev <132113803+howydev@users.noreply.github.com> Date: Thu, 18 Jul 2024 15:57:48 -0700 Subject: [PATCH 057/111] update: add ownable, access control --- src/account/AccountFactory.sol | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/account/AccountFactory.sol b/src/account/AccountFactory.sol index 4a7e9686..362c6ad7 100644 --- a/src/account/AccountFactory.sol +++ b/src/account/AccountFactory.sol @@ -4,12 +4,13 @@ pragma solidity ^0.8.19; import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; -contract AccountFactory { +contract AccountFactory is Ownable { UpgradeableModularAccount public accountImplementation; bytes32 private immutable _PROXY_BYTECODE_HASH; uint32 public constant UNSTAKE_DELAY = 1 weeks; @@ -66,10 +67,18 @@ contract AccountFactory { return Create2.computeAddress(getSalt(owner, salt), _PROXY_BYTECODE_HASH); } - function addStake() external payable { + function addStake() external payable onlyOwner { entryPoint.addStake{value: msg.value}(UNSTAKE_DELAY); } + function unlockStake() external onlyOwner { + entryPoint.unlockStake(); + } + + function withdrawStake(address payable withdrawAddress) external onlyOwner { + entryPoint.withdrawStake(withdrawAddress); + } + function getSalt(address owner, uint256 salt) public pure returns (bytes32) { return keccak256(abi.encodePacked(owner, salt)); } From f2ef9d0e6f5e05ba8491be0d1c1267958376f6f4 Mon Sep 17 00:00:00 2001 From: howydev <132113803+howydev@users.noreply.github.com> Date: Thu, 18 Jul 2024 16:01:31 -0700 Subject: [PATCH 058/111] chore: comments --- src/account/AccountFactory.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/account/AccountFactory.sol b/src/account/AccountFactory.sol index 362c6ad7..bd9662ee 100644 --- a/src/account/AccountFactory.sol +++ b/src/account/AccountFactory.sol @@ -27,8 +27,8 @@ contract AccountFactory is Ownable { } /** - * create an account, and return its address. - * returns the address even if the account is already deployed. + * Create an account, and return its address. + * Returns the address even if the account is already deployed. * Note that during user operation execution, this method is called only if the account is not deployed. * This method returns an existing account address so that entryPoint.getSenderAddress() would work even after * account creation @@ -61,7 +61,7 @@ contract AccountFactory is Ownable { } /** - * alculate the counterfactual address of this account as it would be returned by createAccount() + * Calculate the counterfactual address of this account as it would be returned by createAccount() */ function getAddress(address owner, uint256 salt) public view returns (address) { return Create2.computeAddress(getSalt(owner, salt), _PROXY_BYTECODE_HASH); From a99f86d32a39393f94e91f9cccde983a14833ca0 Mon Sep 17 00:00:00 2001 From: howydev <132113803+howydev@users.noreply.github.com> Date: Thu, 18 Jul 2024 18:50:14 -0700 Subject: [PATCH 059/111] fix: lint --- src/account/AccountFactory.sol | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/account/AccountFactory.sol b/src/account/AccountFactory.sol index bd9662ee..36837087 100644 --- a/src/account/AccountFactory.sol +++ b/src/account/AccountFactory.sol @@ -2,9 +2,10 @@ pragma solidity ^0.8.19; import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; + +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; @@ -17,7 +18,7 @@ contract AccountFactory is Ownable { IEntryPoint public entryPoint; address public self; - constructor(IEntryPoint _entryPoint, UpgradeableModularAccount _accountImpl) { + constructor(IEntryPoint _entryPoint, UpgradeableModularAccount _accountImpl) Ownable(msg.sender) { entryPoint = _entryPoint; _PROXY_BYTECODE_HASH = keccak256( abi.encodePacked(type(ERC1967Proxy).creationCode, abi.encode(address(accountImplementation), "")) @@ -38,7 +39,7 @@ contract AccountFactory is Ownable { uint256 salt, uint32 entityId, SingleSignerValidation singleSignerValidation - ) public returns (UpgradeableModularAccount) { + ) external returns (UpgradeableModularAccount) { address addr = Create2.computeAddress(getSalt(owner, salt), _PROXY_BYTECODE_HASH); // short circuit if exists @@ -60,13 +61,6 @@ contract AccountFactory is Ownable { return UpgradeableModularAccount(payable(addr)); } - /** - * Calculate the counterfactual address of this account as it would be returned by createAccount() - */ - function getAddress(address owner, uint256 salt) public view returns (address) { - return Create2.computeAddress(getSalt(owner, salt), _PROXY_BYTECODE_HASH); - } - function addStake() external payable onlyOwner { entryPoint.addStake{value: msg.value}(UNSTAKE_DELAY); } @@ -79,6 +73,13 @@ contract AccountFactory is Ownable { entryPoint.withdrawStake(withdrawAddress); } + /** + * Calculate the counterfactual address of this account as it would be returned by createAccount() + */ + function getAddress(address owner, uint256 salt) external view returns (address) { + return Create2.computeAddress(getSalt(owner, salt), _PROXY_BYTECODE_HASH); + } + function getSalt(address owner, uint256 salt) public pure returns (bytes32) { return keccak256(abi.encodePacked(owner, salt)); } From 01db0f2d2cd5e303931bf2f23a93fe6cd1a1c002 Mon Sep 17 00:00:00 2001 From: howydev <132113803+howydev@users.noreply.github.com> Date: Fri, 19 Jul 2024 11:57:25 -0700 Subject: [PATCH 060/111] test: add tests, cleanup --- src/account/AccountFactory.sol | 45 ++++++++++++++++++--------------- test/account/AccountFactory.sol | 37 +++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 20 deletions(-) create mode 100644 test/account/AccountFactory.sol diff --git a/src/account/AccountFactory.sol b/src/account/AccountFactory.sol index 36837087..f1feea53 100644 --- a/src/account/AccountFactory.sol +++ b/src/account/AccountFactory.sol @@ -7,24 +7,21 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; -import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; -import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; +import {UpgradeableModularAccount} from "../account/UpgradeableModularAccount.sol"; +import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; +import {SingleSignerValidation} from "../plugins/validation/SingleSignerValidation.sol"; contract AccountFactory is Ownable { UpgradeableModularAccount public accountImplementation; bytes32 private immutable _PROXY_BYTECODE_HASH; uint32 public constant UNSTAKE_DELAY = 1 weeks; - IEntryPoint public entryPoint; - address public self; + IEntryPoint public immutable ENTRY_POINT; constructor(IEntryPoint _entryPoint, UpgradeableModularAccount _accountImpl) Ownable(msg.sender) { - entryPoint = _entryPoint; - _PROXY_BYTECODE_HASH = keccak256( - abi.encodePacked(type(ERC1967Proxy).creationCode, abi.encode(address(accountImplementation), "")) - ); + ENTRY_POINT = _entryPoint; + _PROXY_BYTECODE_HASH = + keccak256(abi.encodePacked(type(ERC1967Proxy).creationCode, abi.encode(address(_accountImpl), ""))); accountImplementation = _accountImpl; - self = address(this); } /** @@ -40,14 +37,14 @@ contract AccountFactory is Ownable { uint32 entityId, SingleSignerValidation singleSignerValidation ) external returns (UpgradeableModularAccount) { - address addr = Create2.computeAddress(getSalt(owner, salt), _PROXY_BYTECODE_HASH); + bytes32 combinedSalt = getSalt(owner, salt, entityId, address(singleSignerValidation)); + address addr = Create2.computeAddress(combinedSalt, _PROXY_BYTECODE_HASH); // short circuit if exists if (addr.code.length == 0) { bytes memory pluginInstallData = abi.encode(entityId, owner); // not necessary to check return addr since next call will fail if so - new ERC1967Proxy{salt: getSalt(owner, salt)}(address(accountImplementation), ""); - + new ERC1967Proxy{salt: combinedSalt}(address(accountImplementation), ""); // point proxy to actual implementation and init plugins UpgradeableModularAccount(payable(addr)).initializeWithValidation( ValidationConfigLib.pack(address(singleSignerValidation), entityId, true, true), @@ -62,25 +59,33 @@ contract AccountFactory is Ownable { } function addStake() external payable onlyOwner { - entryPoint.addStake{value: msg.value}(UNSTAKE_DELAY); + ENTRY_POINT.addStake{value: msg.value}(UNSTAKE_DELAY); } function unlockStake() external onlyOwner { - entryPoint.unlockStake(); + ENTRY_POINT.unlockStake(); } function withdrawStake(address payable withdrawAddress) external onlyOwner { - entryPoint.withdrawStake(withdrawAddress); + ENTRY_POINT.withdrawStake(withdrawAddress); } /** * Calculate the counterfactual address of this account as it would be returned by createAccount() */ - function getAddress(address owner, uint256 salt) external view returns (address) { - return Create2.computeAddress(getSalt(owner, salt), _PROXY_BYTECODE_HASH); + function getAddress(address owner, uint256 salt, uint32 entityId, address validation) + external + view + returns (address) + { + return Create2.computeAddress(getSalt(owner, salt, entityId, validation), _PROXY_BYTECODE_HASH); } - function getSalt(address owner, uint256 salt) public pure returns (bytes32) { - return keccak256(abi.encodePacked(owner, salt)); + function getSalt(address owner, uint256 salt, uint32 entityId, address validation) + public + pure + returns (bytes32) + { + return keccak256(abi.encodePacked(owner, salt, entityId, validation)); } } diff --git a/test/account/AccountFactory.sol b/test/account/AccountFactory.sol new file mode 100644 index 00000000..ebd3b1e7 --- /dev/null +++ b/test/account/AccountFactory.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {AccountFactory} from "../../src/account/AccountFactory.sol"; +import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; +import {AccountTestBase} from "../utils/AccountTestBase.sol"; + +contract AccountFactoryTest is AccountTestBase { + AccountFactory internal _factory; + UpgradeableModularAccount internal _account; + + function setUp() public { + _account = new UpgradeableModularAccount(entryPoint); + _factory = new AccountFactory(entryPoint, _account); + } + + function test_createAccount() public { + UpgradeableModularAccount account = _factory.createAccount(address(this), 100, 0, singleSignerValidation); + + assertEq(address(account.entryPoint()), address(entryPoint)); + } + + function test_multipleDeploy() public { + UpgradeableModularAccount account = _factory.createAccount(address(this), 100, 0, singleSignerValidation); + + uint256 startGas = gasleft(); + + UpgradeableModularAccount account2 = _factory.createAccount(address(this), 100, 0, singleSignerValidation); + + // Assert that the 2nd deployment call cost less than 1 sstore + // Implies that no deployment was done on the second calls + assertLe(startGas - 22_000, gasleft()); + + // Assert the return addresses are the same + assertEq(address(account), address(account2)); + } +} From 268b487ddcd136c8c30227185948bcda8a20a3f3 Mon Sep 17 00:00:00 2001 From: howydev <132113803+howydev@users.noreply.github.com> Date: Fri, 19 Jul 2024 13:12:22 -0700 Subject: [PATCH 061/111] chore: use immutable single signer validation --- src/account/AccountFactory.sol | 37 +++++++++++++-------------------- test/account/AccountFactory.sol | 8 +++---- 2 files changed, 19 insertions(+), 26 deletions(-) diff --git a/src/account/AccountFactory.sol b/src/account/AccountFactory.sol index f1feea53..cc0368f3 100644 --- a/src/account/AccountFactory.sol +++ b/src/account/AccountFactory.sol @@ -9,19 +9,22 @@ import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; import {UpgradeableModularAccount} from "../account/UpgradeableModularAccount.sol"; import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; -import {SingleSignerValidation} from "../plugins/validation/SingleSignerValidation.sol"; contract AccountFactory is Ownable { UpgradeableModularAccount public accountImplementation; bytes32 private immutable _PROXY_BYTECODE_HASH; uint32 public constant UNSTAKE_DELAY = 1 weeks; IEntryPoint public immutable ENTRY_POINT; + address public immutable SINGLE_SIGNER_VALIDATION; - constructor(IEntryPoint _entryPoint, UpgradeableModularAccount _accountImpl) Ownable(msg.sender) { + constructor(IEntryPoint _entryPoint, UpgradeableModularAccount _accountImpl, address _singleSignerValidation) + Ownable(msg.sender) + { ENTRY_POINT = _entryPoint; _PROXY_BYTECODE_HASH = keccak256(abi.encodePacked(type(ERC1967Proxy).creationCode, abi.encode(address(_accountImpl), ""))); accountImplementation = _accountImpl; + SINGLE_SIGNER_VALIDATION = _singleSignerValidation; } /** @@ -31,13 +34,11 @@ contract AccountFactory is Ownable { * This method returns an existing account address so that entryPoint.getSenderAddress() would work even after * account creation */ - function createAccount( - address owner, - uint256 salt, - uint32 entityId, - SingleSignerValidation singleSignerValidation - ) external returns (UpgradeableModularAccount) { - bytes32 combinedSalt = getSalt(owner, salt, entityId, address(singleSignerValidation)); + function createAccount(address owner, uint256 salt, uint32 entityId) + external + returns (UpgradeableModularAccount) + { + bytes32 combinedSalt = getSalt(owner, salt, entityId); address addr = Create2.computeAddress(combinedSalt, _PROXY_BYTECODE_HASH); // short circuit if exists @@ -47,7 +48,7 @@ contract AccountFactory is Ownable { new ERC1967Proxy{salt: combinedSalt}(address(accountImplementation), ""); // point proxy to actual implementation and init plugins UpgradeableModularAccount(payable(addr)).initializeWithValidation( - ValidationConfigLib.pack(address(singleSignerValidation), entityId, true, true), + ValidationConfigLib.pack(SINGLE_SIGNER_VALIDATION, entityId, true, true), new bytes4[](0), pluginInstallData, "", @@ -73,19 +74,11 @@ contract AccountFactory is Ownable { /** * Calculate the counterfactual address of this account as it would be returned by createAccount() */ - function getAddress(address owner, uint256 salt, uint32 entityId, address validation) - external - view - returns (address) - { - return Create2.computeAddress(getSalt(owner, salt, entityId, validation), _PROXY_BYTECODE_HASH); + function getAddress(address owner, uint256 salt, uint32 entityId) external view returns (address) { + return Create2.computeAddress(getSalt(owner, salt, entityId), _PROXY_BYTECODE_HASH); } - function getSalt(address owner, uint256 salt, uint32 entityId, address validation) - public - pure - returns (bytes32) - { - return keccak256(abi.encodePacked(owner, salt, entityId, validation)); + function getSalt(address owner, uint256 salt, uint32 entityId) public pure returns (bytes32) { + return keccak256(abi.encodePacked(owner, salt, entityId)); } } diff --git a/test/account/AccountFactory.sol b/test/account/AccountFactory.sol index ebd3b1e7..363725ca 100644 --- a/test/account/AccountFactory.sol +++ b/test/account/AccountFactory.sol @@ -11,21 +11,21 @@ contract AccountFactoryTest is AccountTestBase { function setUp() public { _account = new UpgradeableModularAccount(entryPoint); - _factory = new AccountFactory(entryPoint, _account); + _factory = new AccountFactory(entryPoint, _account, address(singleSignerValidation)); } function test_createAccount() public { - UpgradeableModularAccount account = _factory.createAccount(address(this), 100, 0, singleSignerValidation); + UpgradeableModularAccount account = _factory.createAccount(address(this), 100, 0); assertEq(address(account.entryPoint()), address(entryPoint)); } function test_multipleDeploy() public { - UpgradeableModularAccount account = _factory.createAccount(address(this), 100, 0, singleSignerValidation); + UpgradeableModularAccount account = _factory.createAccount(address(this), 100, 0); uint256 startGas = gasleft(); - UpgradeableModularAccount account2 = _factory.createAccount(address(this), 100, 0, singleSignerValidation); + UpgradeableModularAccount account2 = _factory.createAccount(address(this), 100, 0); // Assert that the 2nd deployment call cost less than 1 sstore // Implies that no deployment was done on the second calls From 4278acf6fe8055a02405cfd55852fb1b32ef6896 Mon Sep 17 00:00:00 2001 From: howydev <132113803+howydev@users.noreply.github.com> Date: Mon, 22 Jul 2024 13:45:23 -0700 Subject: [PATCH 062/111] fix: pr review fixes --- src/account/AccountFactory.sol | 6 +++--- test/account/{AccountFactory.sol => AccountFactory.t.sol} | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) rename test/account/{AccountFactory.sol => AccountFactory.t.sol} (85%) diff --git a/src/account/AccountFactory.sol b/src/account/AccountFactory.sol index cc0368f3..67fcd8c6 100644 --- a/src/account/AccountFactory.sol +++ b/src/account/AccountFactory.sol @@ -11,7 +11,7 @@ import {UpgradeableModularAccount} from "../account/UpgradeableModularAccount.so import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; contract AccountFactory is Ownable { - UpgradeableModularAccount public accountImplementation; + UpgradeableModularAccount public immutable ACCOUNT_IMPL; bytes32 private immutable _PROXY_BYTECODE_HASH; uint32 public constant UNSTAKE_DELAY = 1 weeks; IEntryPoint public immutable ENTRY_POINT; @@ -23,7 +23,7 @@ contract AccountFactory is Ownable { ENTRY_POINT = _entryPoint; _PROXY_BYTECODE_HASH = keccak256(abi.encodePacked(type(ERC1967Proxy).creationCode, abi.encode(address(_accountImpl), ""))); - accountImplementation = _accountImpl; + ACCOUNT_IMPL = _accountImpl; SINGLE_SIGNER_VALIDATION = _singleSignerValidation; } @@ -45,7 +45,7 @@ contract AccountFactory is Ownable { if (addr.code.length == 0) { bytes memory pluginInstallData = abi.encode(entityId, owner); // not necessary to check return addr since next call will fail if so - new ERC1967Proxy{salt: combinedSalt}(address(accountImplementation), ""); + new ERC1967Proxy{salt: combinedSalt}(address(ACCOUNT_IMPL), ""); // point proxy to actual implementation and init plugins UpgradeableModularAccount(payable(addr)).initializeWithValidation( ValidationConfigLib.pack(SINGLE_SIGNER_VALIDATION, entityId, true, true), diff --git a/test/account/AccountFactory.sol b/test/account/AccountFactory.t.sol similarity index 85% rename from test/account/AccountFactory.sol rename to test/account/AccountFactory.t.sol index 363725ca..6e79b1c3 100644 --- a/test/account/AccountFactory.sol +++ b/test/account/AccountFactory.t.sol @@ -20,6 +20,12 @@ contract AccountFactoryTest is AccountTestBase { assertEq(address(account.entryPoint()), address(entryPoint)); } + function test_createAccountAndGetAddress() public { + UpgradeableModularAccount account = _factory.createAccount(address(this), 100, 0); + + assertEq(address(account), address(_factory.createAccount(address(this), 100, 0))); + } + function test_multipleDeploy() public { UpgradeableModularAccount account = _factory.createAccount(address(this), 100, 0); From 4f8947db5e8e784a94ebdf582fb3c5e93f7203f1 Mon Sep 17 00:00:00 2001 From: Adam Egyed <5456061+adamegyed@users.noreply.github.com> Date: Tue, 23 Jul 2024 15:59:02 -0400 Subject: [PATCH 063/111] feat: [v0.8-develop] Remove validation installation from the manifest 3/N (#104) --- src/account/ModuleManagerInternals.sol | 51 ++++++-------------- src/account/UpgradeableModularAccount.sol | 30 ++---------- src/helpers/Constants.sol | 11 +++++ src/interfaces/IModule.sol | 9 ---- test/account/AccountLoupe.t.sol | 16 +++--- test/account/AccountReturnData.t.sol | 13 +++++ test/account/SelfCallAuthorization.t.sol | 12 +++-- test/account/ValidationIntersection.t.sol | 39 ++++++++------- test/mocks/modules/ComprehensiveModule.sol | 12 ----- test/mocks/modules/ReturnDataModuleMocks.sol | 24 ++------- test/mocks/modules/ValidationModuleMocks.sol | 40 +-------------- 11 files changed, 86 insertions(+), 171 deletions(-) create mode 100644 src/helpers/Constants.sol diff --git a/src/account/ModuleManagerInternals.sol b/src/account/ModuleManagerInternals.sol index 9fd60c45..5eeeb77b 100644 --- a/src/account/ModuleManagerInternals.sol +++ b/src/account/ModuleManagerInternals.sol @@ -5,11 +5,12 @@ import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165C import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import {DIRECT_CALL_VALIDATION_ENTITYID, MAX_PRE_VALIDATION_HOOKS} from "../helpers/Constants.sol"; import {KnownSelectors} from "../helpers/KnownSelectors.sol"; import {ModuleEntityLib} from "../helpers/ModuleEntityLib.sol"; import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; -import {IModule, ManifestExecutionHook, ManifestValidation, ModuleManifest} from "../interfaces/IModule.sol"; +import {IModule, ManifestExecutionHook, ModuleManifest} from "../interfaces/IModule.sol"; import {IModuleManager, ModuleEntity, ValidationConfig} from "../interfaces/IModuleManager.sol"; import {AccountStorage, SelectorData, ValidationData, getAccountStorage, toSetValue} from "./AccountStorage.sol"; @@ -18,12 +19,6 @@ abstract contract ModuleManagerInternals is IModuleManager { using ModuleEntityLib for ModuleEntity; using ValidationConfigLib for ValidationConfig; - // Index marking the start of the data for the validation function. - uint8 internal constant _RESERVED_VALIDATION_DATA_INDEX = 255; - - // Magic value for the Entity ID of direct call validation. - uint32 internal constant _SELF_PERMIT_VALIDATION_FUNCTIONID = type(uint32).max; - error ArrayLengthMismatch(); error Erc4337FunctionNotAllowed(bytes4 selector); error ExecutionFunctionAlreadySet(bytes4 selector); @@ -163,17 +158,6 @@ abstract contract ModuleManagerInternals is IModuleManager { _setExecutionFunction(selector, isPublic, allowGlobalValidation, module); } - length = manifest.validationFunctions.length; - for (uint256 i = 0; i < length; ++i) { - // Todo: limit this to only "direct runtime call" validation path (old EFP), - // and add a way for the user to specify permission/pre-val hooks here. - ManifestValidation memory mv = manifest.validationFunctions[i]; - - ValidationConfig validationConfig = - ValidationConfigLib.pack(module, mv.entityId, mv.isGlobal, mv.isSignatureValidation); - _addValidationFunction(validationConfig, mv.selectors); - } - length = manifest.executionHooks.length; for (uint256 i = 0; i < length; ++i) { ManifestExecutionHook memory mh = manifest.executionHooks[i]; @@ -212,13 +196,6 @@ abstract contract ModuleManagerInternals is IModuleManager { _removeExecHooks(execHooks, hookFunction, mh.isPreHook, mh.isPostHook); } - length = manifest.validationFunctions.length; - for (uint256 i = 0; i < length; ++i) { - ModuleEntity validationFunction = - ModuleEntityLib.pack(module, manifest.validationFunctions[i].entityId); - _removeValidationFunction(validationFunction); - } - length = manifest.executionFunctions.length; for (uint256 i = 0; i < length; ++i) { bytes4 selector = manifest.executionFunctions[i].executionSelector; @@ -261,13 +238,13 @@ abstract contract ModuleManagerInternals is IModuleManager { _validationData.preValidationHooks.push(preValidationFunction); if (initDatas[i].length > 0) { - (address preValidationPlugin,) = ModuleEntityLib.unpack(preValidationFunction); - IModule(preValidationPlugin).onInstall(initDatas[i]); + (address preValidationModule,) = ModuleEntityLib.unpack(preValidationFunction); + IModule(preValidationModule).onInstall(initDatas[i]); } } // Avoid collision between reserved index and actual indices - if (_validationData.preValidationHooks.length > _RESERVED_VALIDATION_DATA_INDEX) { + if (_validationData.preValidationHooks.length > MAX_PRE_VALIDATION_HOOKS) { revert PreValidationHookLimitExceeded(); } } @@ -284,8 +261,8 @@ abstract contract ModuleManagerInternals is IModuleManager { } if (initDatas[i].length > 0) { - (address executionPlugin,) = ModuleEntityLib.unpack(permissionFunction.hookFunction); - IModule(executionPlugin).onInstall(initDatas[i]); + (address executionModule,) = ModuleEntityLib.unpack(permissionFunction.hookFunction); + IModule(executionModule).onInstall(initDatas[i]); } } } @@ -297,7 +274,7 @@ abstract contract ModuleManagerInternals is IModuleManager { } } - if (validationConfig.entityId() != _SELF_PERMIT_VALIDATION_FUNCTIONID) { + if (validationConfig.entityId() != DIRECT_CALL_VALIDATION_ENTITYID) { // Only allow global validations and signature validations if they're not direct-call validations. _validationData.isGlobal = validationConfig.isGlobal(); @@ -326,8 +303,8 @@ abstract contract ModuleManagerInternals is IModuleManager { for (uint256 i = 0; i < preValidationHooks.length; ++i) { ModuleEntity preValidationFunction = preValidationHooks[i]; if (preValidationHookUninstallDatas[0].length > 0) { - (address preValidationPlugin,) = ModuleEntityLib.unpack(preValidationFunction); - IModule(preValidationPlugin).onUninstall(preValidationHookUninstallDatas[0]); + (address preValidationModule,) = ModuleEntityLib.unpack(preValidationFunction); + IModule(preValidationModule).onUninstall(preValidationHookUninstallDatas[0]); } } delete _validationData.preValidationHooks; @@ -342,8 +319,8 @@ abstract contract ModuleManagerInternals is IModuleManager { for (uint256 i = 0; i < permissionHookLen; ++i) { bytes32 permissionHook = permissionHooks.at(0); permissionHooks.remove(permissionHook); - address permissionHookPlugin = address(uint160(bytes20(permissionHook))); - IModule(permissionHookPlugin).onUninstall(permissionHookUninstallDatas[i]); + address permissionHookModule = address(uint160(bytes20(permissionHook))); + IModule(permissionHookModule).onUninstall(permissionHookUninstallDatas[i]); } } @@ -355,8 +332,8 @@ abstract contract ModuleManagerInternals is IModuleManager { } if (uninstallData.length > 0) { - (address plugin,) = ModuleEntityLib.unpack(validationFunction); - IModule(plugin).onUninstall(uninstallData); + (address module,) = ModuleEntityLib.unpack(validationFunction); + IModule(module).onUninstall(uninstallData); } } } diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 737beebb..87379241 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -18,6 +18,7 @@ import {SparseCalldataSegmentLib} from "../helpers/SparseCalldataSegmentLib.sol" import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; import {_coalescePreValidation, _coalesceValidation} from "../helpers/ValidationResHelpers.sol"; +import {DIRECT_CALL_VALIDATION_ENTITYID, RESERVED_VALIDATION_DATA_INDEX} from "../helpers/Constants.sol"; import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; import {ModuleManifest} from "../interfaces/IModule.sol"; import {IModuleManager, ModuleEntity, ValidationConfig} from "../interfaces/IModuleManager.sol"; @@ -28,7 +29,6 @@ import {AccountExecutor} from "./AccountExecutor.sol"; import {AccountLoupe} from "./AccountLoupe.sol"; import {AccountStorage, getAccountStorage, toExecutionHook, toSetValue} from "./AccountStorage.sol"; import {AccountStorageInitializable} from "./AccountStorageInitializable.sol"; - import {ModuleManagerInternals} from "./ModuleManagerInternals.sol"; contract UpgradeableModularAccount is @@ -105,28 +105,6 @@ contract UpgradeableModularAccount is // EXTERNAL FUNCTIONS - /// @notice Initializes the account with a set of modules - /// @param modules The modules to install - /// @param manifests The manifests of the modules to install - /// @param moduleInstallDatas The module install datas of the modules to install - function initialize( - address[] memory modules, - ModuleManifest[] calldata manifests, - bytes[] memory moduleInstallDatas - ) external initializer { - uint256 length = modules.length; - - if (length != manifests.length || length != moduleInstallDatas.length) { - revert ArrayLengthMismatch(); - } - - for (uint256 i = 0; i < length; ++i) { - _installModule(modules[i], manifests[i], moduleInstallDatas[i]); - } - - emit ModularAccountInitialized(_ENTRY_POINT); - } - receive() external payable {} /// @notice Fallback function @@ -441,7 +419,7 @@ contract UpgradeableModularAccount is // Run the user op validationFunction { - if (signatureSegment.getIndex() != _RESERVED_VALIDATION_DATA_INDEX) { + if (signatureSegment.getIndex() != RESERVED_VALIDATION_DATA_INDEX) { revert ValidationSignatureSegmentMissing(); } @@ -497,7 +475,7 @@ contract UpgradeableModularAccount is _doPreRuntimeValidationHook(preRuntimeValidationHooks[i], callData, currentAuthData); } - if (authSegment.getIndex() != _RESERVED_VALIDATION_DATA_INDEX) { + if (authSegment.getIndex() != RESERVED_VALIDATION_DATA_INDEX) { revert ValidationSignatureSegmentMissing(); } @@ -635,7 +613,7 @@ contract UpgradeableModularAccount is return (new PostExecToRun[](0), new PostExecToRun[](0)); } - ModuleEntity directCallValidationKey = ModuleEntityLib.pack(msg.sender, _SELF_PERMIT_VALIDATION_FUNCTIONID); + ModuleEntity directCallValidationKey = ModuleEntityLib.pack(msg.sender, DIRECT_CALL_VALIDATION_ENTITYID); _checkIfValidationAppliesCallData(msg.data, directCallValidationKey, false); diff --git a/src/helpers/Constants.sol b/src/helpers/Constants.sol new file mode 100644 index 00000000..4ad649c1 --- /dev/null +++ b/src/helpers/Constants.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.25; + +// Index marking the start of the data for the validation function. +uint8 constant RESERVED_VALIDATION_DATA_INDEX = type(uint8).max; + +// Maximum number of pre-validation hooks that can be registered. +uint8 constant MAX_PRE_VALIDATION_HOOKS = type(uint8).max; + +// Magic value for the Entity ID of direct call validation. +uint32 constant DIRECT_CALL_VALIDATION_ENTITYID = type(uint32).max; diff --git a/src/interfaces/IModule.sol b/src/interfaces/IModule.sol index ab78b222..87272404 100644 --- a/src/interfaces/IModule.sol +++ b/src/interfaces/IModule.sol @@ -13,14 +13,6 @@ struct ManifestExecutionFunction { bool allowGlobalValidation; } -// todo: do we need these at all? Or do we fully switch to `installValidation`? -struct ManifestValidation { - uint32 entityId; - bool isGlobal; - bool isSignatureValidation; - bytes4[] selectors; -} - struct ManifestExecutionHook { // TODO(erc6900 spec): These fields can be packed into a single word bytes4 executionSelector; @@ -54,7 +46,6 @@ struct ModuleMetadata { struct ModuleManifest { // Execution functions defined in this module to be installed on the MSCA. ManifestExecutionFunction[] executionFunctions; - ManifestValidation[] validationFunctions; ManifestExecutionHook[] executionHooks; // List of ERC-165 interface IDs to add to account to support introspection checks. This MUST NOT include // IModule's interface ID. diff --git a/test/account/AccountLoupe.t.sol b/test/account/AccountLoupe.t.sol index c10540fb..8b03d86f 100644 --- a/test/account/AccountLoupe.t.sol +++ b/test/account/AccountLoupe.t.sol @@ -16,8 +16,12 @@ contract AccountLoupeTest is CustomValidationTestBase { event ReceivedCall(bytes msgData, uint256 msgValue); + ModuleEntity public comprehensiveModuleValidation; + function setUp() public { comprehensiveModule = new ComprehensiveModule(); + comprehensiveModuleValidation = + ModuleEntityLib.pack(address(comprehensiveModule), uint32(ComprehensiveModule.EntityId.VALIDATION)); _customValidationSetup(); @@ -61,9 +65,6 @@ contract AccountLoupeTest is CustomValidationTestBase { } function test_moduleLoupe_getSelectors() public { - ModuleEntity comprehensiveModuleValidation = - ModuleEntityLib.pack(address(comprehensiveModule), uint32(ComprehensiveModule.EntityId.VALIDATION)); - bytes4[] memory selectors = account1.getSelectors(comprehensiveModuleValidation); assertEq(selectors.length, 1); @@ -107,7 +108,7 @@ contract AccountLoupeTest is CustomValidationTestBase { } function test_moduleLoupe_getValidationHooks() public { - ModuleEntity[] memory hooks = account1.getPreValidationHooks(_signerValidation); + ModuleEntity[] memory hooks = account1.getPreValidationHooks(comprehensiveModuleValidation); assertEq(hooks.length, 2); assertEq( @@ -144,12 +145,15 @@ contract AccountLoupeTest is CustomValidationTestBase { address(comprehensiveModule), uint32(ComprehensiveModule.EntityId.PRE_VALIDATION_HOOK_2) ); + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = comprehensiveModule.foo.selector; + bytes[] memory installDatas = new bytes[](2); return ( - _signerValidation, + comprehensiveModuleValidation, true, true, - new bytes4[](0), + selectors, bytes(""), abi.encode(preValidationHooks, installDatas), "" diff --git a/test/account/AccountReturnData.t.sol b/test/account/AccountReturnData.t.sol index 7d51e9d1..d6834af6 100644 --- a/test/account/AccountReturnData.t.sol +++ b/test/account/AccountReturnData.t.sol @@ -1,8 +1,11 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; +import {DIRECT_CALL_VALIDATION_ENTITYID} from "../../src/helpers/Constants.sol"; import {ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; +import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import {Call} from "../../src/interfaces/IStandardExecutor.sol"; +import {IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; import { RegularResultContract, @@ -38,6 +41,16 @@ contract AccountReturnDataTest is AccountTestBase { manifest: resultConsumerModule.moduleManifest(), moduleInstallData: "" }); + // Allow the result consumer module to perform direct calls to the account + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = IStandardExecutor.execute.selector; + account1.installValidation( + ValidationConfigLib.pack(address(resultConsumerModule), DIRECT_CALL_VALIDATION_ENTITYID, false, false), + selectors, + "", + "", + "" + ); vm.stopPrank(); } diff --git a/test/account/SelfCallAuthorization.t.sol b/test/account/SelfCallAuthorization.t.sol index a9f61905..4dd8a595 100644 --- a/test/account/SelfCallAuthorization.t.sol +++ b/test/account/SelfCallAuthorization.t.sol @@ -24,12 +24,18 @@ contract SelfCallAuthorizationTest is AccountTestBase { comprehensiveModule = new ComprehensiveModule(); + comprehensiveModuleValidation = + ModuleEntityLib.pack(address(comprehensiveModule), uint32(ComprehensiveModule.EntityId.VALIDATION)); + + bytes4[] memory validationSelectors = new bytes4[](1); + validationSelectors[0] = ComprehensiveModule.foo.selector; + vm.startPrank(address(entryPoint)); account1.installModule(address(comprehensiveModule), comprehensiveModule.moduleManifest(), ""); + account1.installValidation( + ValidationConfigLib.pack(comprehensiveModuleValidation, false, false), validationSelectors, "", "", "" + ); vm.stopPrank(); - - comprehensiveModuleValidation = - ModuleEntityLib.pack(address(comprehensiveModule), uint32(ComprehensiveModule.EntityId.VALIDATION)); } function test_selfCallFails_userOp() public { diff --git a/test/account/ValidationIntersection.t.sol b/test/account/ValidationIntersection.t.sol index 9662702f..c9c1a882 100644 --- a/test/account/ValidationIntersection.t.sol +++ b/test/account/ValidationIntersection.t.sol @@ -46,19 +46,21 @@ contract ValidationIntersectionTest is AccountTestBase { entityId: uint32(MockBaseUserOpValidationModule.EntityId.USER_OP_VALIDATION) }); + bytes4[] memory validationSelectors = new bytes4[](1); + validationSelectors[0] = MockUserOpValidationModule.foo.selector; + vm.startPrank(address(entryPoint)); - account1.installModule({ - module: address(noHookModule), - manifest: noHookModule.moduleManifest(), - moduleInstallData: "" - }); - account1.installModule({ - module: address(oneHookModule), - manifest: oneHookModule.moduleManifest(), - moduleInstallData: "" - }); - // TODO: change with new install flow - // temporary fix to add the pre-validation hook + // Install noHookValidation + account1.installValidation( + ValidationConfigLib.pack(noHookValidation, true, true), + validationSelectors, + bytes(""), + bytes(""), + bytes("") + ); + + // Install oneHookValidation + validationSelectors[0] = MockUserOpValidation1HookModule.bar.selector; ModuleEntity[] memory preValidationHooks = new ModuleEntity[](1); preValidationHooks[0] = ModuleEntityLib.pack({ addr: address(oneHookModule), @@ -67,17 +69,14 @@ contract ValidationIntersectionTest is AccountTestBase { bytes[] memory installDatas = new bytes[](1); account1.installValidation( ValidationConfigLib.pack(oneHookValidation, true, true), - new bytes4[](0), + validationSelectors, bytes(""), abi.encode(preValidationHooks, installDatas), bytes("") ); - account1.installModule({ - module: address(twoHookModule), - manifest: twoHookModule.moduleManifest(), - moduleInstallData: "" - }); - // temporary fix to add the pre-validation hook + + // Install twoHookValidation + validationSelectors[0] = MockUserOpValidation2HookModule.baz.selector; preValidationHooks = new ModuleEntity[](2); preValidationHooks[0] = ModuleEntityLib.pack({ addr: address(twoHookModule), @@ -90,7 +89,7 @@ contract ValidationIntersectionTest is AccountTestBase { installDatas = new bytes[](2); account1.installValidation( ValidationConfigLib.pack(twoHookValidation, true, true), - new bytes4[](0), + validationSelectors, bytes(""), abi.encode(preValidationHooks, installDatas), bytes("") diff --git a/test/mocks/modules/ComprehensiveModule.sol b/test/mocks/modules/ComprehensiveModule.sol index aca09f92..fe0f6cfe 100644 --- a/test/mocks/modules/ComprehensiveModule.sol +++ b/test/mocks/modules/ComprehensiveModule.sol @@ -7,7 +7,6 @@ import {IExecutionHook} from "../../../src/interfaces/IExecutionHook.sol"; import { ManifestExecutionFunction, ManifestExecutionHook, - ManifestValidation, ModuleManifest, ModuleMetadata } from "../../../src/interfaces/IModule.sol"; @@ -140,17 +139,6 @@ contract ComprehensiveModule is IValidation, IValidationHook, IExecutionHook, Ba allowGlobalValidation: false }); - bytes4[] memory validationSelectors = new bytes4[](1); - validationSelectors[0] = this.foo.selector; - - manifest.validationFunctions = new ManifestValidation[](1); - manifest.validationFunctions[0] = ManifestValidation({ - entityId: uint32(EntityId.VALIDATION), - isGlobal: true, - isSignatureValidation: false, - selectors: validationSelectors - }); - manifest.executionHooks = new ManifestExecutionHook[](3); manifest.executionHooks[0] = ManifestExecutionHook({ executionSelector: this.foo.selector, diff --git a/test/mocks/modules/ReturnDataModuleMocks.sol b/test/mocks/modules/ReturnDataModuleMocks.sol index 1f219e8b..b7baf03e 100644 --- a/test/mocks/modules/ReturnDataModuleMocks.sol +++ b/test/mocks/modules/ReturnDataModuleMocks.sol @@ -3,12 +3,9 @@ pragma solidity ^0.8.19; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; -import { - ManifestExecutionFunction, - ManifestValidation, - ModuleManifest, - ModuleMetadata -} from "../../../src/interfaces/IModule.sol"; +import {ManifestExecutionFunction, ModuleManifest, ModuleMetadata} from "../../../src/interfaces/IModule.sol"; + +import {DIRECT_CALL_VALIDATION_ENTITYID} from "../../../src/helpers/Constants.sol"; import {IStandardExecutor} from "../../../src/interfaces/IStandardExecutor.sol"; import {IValidation} from "../../../src/interfaces/IValidation.sol"; @@ -103,7 +100,8 @@ contract ResultConsumerModule is BaseModule, IValidation { // This result should be allowed based on the manifest permission request bytes memory returnData = IStandardExecutor(msg.sender).executeWithAuthorization( abi.encodeCall(IStandardExecutor.execute, (target, 0, abi.encodeCall(RegularResultContract.foo, ()))), - abi.encodePacked(this, uint32(0), uint8(0), uint32(1), uint8(255)) // Validation function of self, + abi.encodePacked(this, DIRECT_CALL_VALIDATION_ENTITYID, uint8(0), uint32(1), uint8(255)) // Validation + // function of self, // selector-associated, with no auth data ); @@ -119,18 +117,6 @@ contract ResultConsumerModule is BaseModule, IValidation { function moduleManifest() external pure override returns (ModuleManifest memory) { ModuleManifest memory manifest; - // todo: this is the exact workflow that would benefit from a "permiteed call" setup in the manifest. - bytes4[] memory validationSelectors = new bytes4[](1); - validationSelectors[0] = IStandardExecutor.execute.selector; - - manifest.validationFunctions = new ManifestValidation[](1); - manifest.validationFunctions[0] = ManifestValidation({ - entityId: 0, - isGlobal: true, - isSignatureValidation: false, - selectors: validationSelectors - }); - manifest.executionFunctions = new ManifestExecutionFunction[](2); manifest.executionFunctions[0] = ManifestExecutionFunction({ executionSelector: this.checkResultFallback.selector, diff --git a/test/mocks/modules/ValidationModuleMocks.sol b/test/mocks/modules/ValidationModuleMocks.sol index 4c37190f..3186051f 100644 --- a/test/mocks/modules/ValidationModuleMocks.sol +++ b/test/mocks/modules/ValidationModuleMocks.sol @@ -3,12 +3,7 @@ pragma solidity ^0.8.19; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; -import { - ManifestExecutionFunction, - ManifestValidation, - ModuleManifest, - ModuleMetadata -} from "../../../src/interfaces/IModule.sol"; +import {ManifestExecutionFunction, ModuleManifest, ModuleMetadata} from "../../../src/interfaces/IModule.sol"; import {IValidation} from "../../../src/interfaces/IValidation.sol"; import {IValidationHook} from "../../../src/interfaces/IValidationHook.sol"; import {BaseModule} from "../../../src/modules/BaseModule.sol"; @@ -112,17 +107,6 @@ contract MockUserOpValidationModule is MockBaseUserOpValidationModule { allowGlobalValidation: false }); - bytes4[] memory validationSelectors = new bytes4[](1); - validationSelectors[0] = this.foo.selector; - - manifest.validationFunctions = new ManifestValidation[](1); - manifest.validationFunctions[0] = ManifestValidation({ - entityId: uint32(EntityId.USER_OP_VALIDATION), - isGlobal: false, - isSignatureValidation: false, - selectors: validationSelectors - }); - return manifest; } } @@ -155,17 +139,6 @@ contract MockUserOpValidation1HookModule is MockBaseUserOpValidationModule { allowGlobalValidation: false }); - bytes4[] memory validationSelectors = new bytes4[](1); - validationSelectors[0] = this.bar.selector; - - manifest.validationFunctions = new ManifestValidation[](2); - manifest.validationFunctions[0] = ManifestValidation({ - entityId: uint32(EntityId.USER_OP_VALIDATION), - isGlobal: false, - isSignatureValidation: false, - selectors: validationSelectors - }); - return manifest; } } @@ -201,17 +174,6 @@ contract MockUserOpValidation2HookModule is MockBaseUserOpValidationModule { allowGlobalValidation: false }); - bytes4[] memory validationSelectors = new bytes4[](1); - validationSelectors[0] = this.baz.selector; - - manifest.validationFunctions = new ManifestValidation[](1); - manifest.validationFunctions[0] = ManifestValidation({ - entityId: uint32(EntityId.USER_OP_VALIDATION), - isGlobal: false, - isSignatureValidation: false, - selectors: validationSelectors - }); - return manifest; } } From e36feac47d8755248cd0758d83b7cf502dbb1f2e Mon Sep 17 00:00:00 2001 From: Adam Egyed <5456061+adamegyed@users.noreply.github.com> Date: Wed, 24 Jul 2024 18:59:33 -0400 Subject: [PATCH 064/111] feat: [v0.8-develop] HookConfig install parameter & internal structure 4/N (#109) --- foundry.toml | 2 +- src/account/AccountFactory.sol | 3 +- src/account/AccountLoupe.sol | 22 ++- src/account/AccountStorage.sol | 18 +- src/account/ModuleManagerInternals.sol | 198 +++++++++++----------- src/account/UpgradeableModularAccount.sol | 41 ++--- src/helpers/HookConfigLib.sol | 140 +++++++++++++++ src/interfaces/IModuleManager.sol | 21 +-- test/account/AccountLoupe.t.sol | 28 ++- test/account/AccountReturnData.t.sol | 3 +- test/account/DirectCallsFromModule.t.sol | 18 +- test/account/MultiValidation.t.sol | 3 +- test/account/PerHookData.t.sol | 26 ++- test/account/SelfCallAuthorization.t.sol | 12 +- test/account/ValidationIntersection.t.sol | 53 +++--- test/libraries/HookConfigLib.t.sol | 67 ++++++++ test/mocks/SingleSignerFactoryFixture.sol | 3 +- test/module/AllowlistModule.t.sol | 27 ++- test/module/ERC20TokenLimitModule.t.sol | 14 +- test/module/NativeTokenLimitModule.t.sol | 25 ++- test/module/SingleSignerValidation.t.sol | 3 +- test/utils/CustomValidationTestBase.sol | 9 +- 22 files changed, 454 insertions(+), 282 deletions(-) create mode 100644 src/helpers/HookConfigLib.sol create mode 100644 test/libraries/HookConfigLib.t.sol diff --git a/foundry.toml b/foundry.toml index 2aa76ba2..0252d4e5 100644 --- a/foundry.toml +++ b/foundry.toml @@ -38,7 +38,7 @@ runs = 5000 depth = 32 [profile.deep.fuzz] -runs = 10000 +runs = 100000 [profile.deep.invariant] runs = 5000 diff --git a/src/account/AccountFactory.sol b/src/account/AccountFactory.sol index 67fcd8c6..ca15e815 100644 --- a/src/account/AccountFactory.sol +++ b/src/account/AccountFactory.sol @@ -51,8 +51,7 @@ contract AccountFactory is Ownable { ValidationConfigLib.pack(SINGLE_SIGNER_VALIDATION, entityId, true, true), new bytes4[](0), pluginInstallData, - "", - "" + new bytes[](0) ); } diff --git a/src/account/AccountLoupe.sol b/src/account/AccountLoupe.sol index f0afc1b2..bcd6dbcd 100644 --- a/src/account/AccountLoupe.sol +++ b/src/account/AccountLoupe.sol @@ -6,14 +6,16 @@ import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeab import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import {HookConfigLib} from "../helpers/HookConfigLib.sol"; import {ExecutionHook, IAccountLoupe} from "../interfaces/IAccountLoupe.sol"; -import {IModuleManager, ModuleEntity} from "../interfaces/IModuleManager.sol"; +import {HookConfig, IModuleManager, ModuleEntity} from "../interfaces/IModuleManager.sol"; import {IStandardExecutor} from "../interfaces/IStandardExecutor.sol"; -import {getAccountStorage, toExecutionHook, toSelector} from "./AccountStorage.sol"; +import {getAccountStorage, toHookConfig, toSelector} from "./AccountStorage.sol"; abstract contract AccountLoupe is IAccountLoupe { using EnumerableSet for EnumerableSet.Bytes32Set; using EnumerableMap for EnumerableMap.AddressToUintMap; + using HookConfigLib for HookConfig; /// @inheritdoc IAccountLoupe function getExecutionFunctionHandler(bytes4 selector) external view override returns (address module) { @@ -56,8 +58,12 @@ abstract contract AccountLoupe is IAccountLoupe { for (uint256 i = 0; i < executionHooksLength; ++i) { bytes32 key = hooks.at(i); - ExecutionHook memory execHook = execHooks[i]; - (execHook.hookFunction, execHook.isPreHook, execHook.isPostHook) = toExecutionHook(key); + HookConfig hookConfig = toHookConfig(key); + execHooks[i] = ExecutionHook({ + hookFunction: hookConfig.moduleEntity(), + isPreHook: hookConfig.hasPreHook(), + isPostHook: hookConfig.hasPostHook() + }); } } @@ -74,8 +80,12 @@ abstract contract AccountLoupe is IAccountLoupe { permissionHooks = new ExecutionHook[](executionHooksLength); for (uint256 i = 0; i < executionHooksLength; ++i) { bytes32 key = hooks.at(i); - ExecutionHook memory execHook = permissionHooks[i]; - (execHook.hookFunction, execHook.isPreHook, execHook.isPostHook) = toExecutionHook(key); + HookConfig hookConfig = toHookConfig(key); + permissionHooks[i] = ExecutionHook({ + hookFunction: hookConfig.moduleEntity(), + isPreHook: hookConfig.hasPreHook(), + isPostHook: hookConfig.hasPostHook() + }); } } diff --git a/src/account/AccountStorage.sol b/src/account/AccountStorage.sol index c9845a8e..f7d279e4 100644 --- a/src/account/AccountStorage.sol +++ b/src/account/AccountStorage.sol @@ -3,8 +3,7 @@ pragma solidity ^0.8.25; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; -import {ModuleEntity} from "../interfaces/IModuleManager.sol"; +import {HookConfig, ModuleEntity} from "../interfaces/IModuleManager.sol"; // bytes = keccak256("ERC6900.UpgradeableModularAccount.Storage") bytes32 constant _ACCOUNT_STORAGE_SLOT = 0x9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e40; @@ -70,19 +69,12 @@ function toModuleEntity(bytes32 setValue) pure returns (ModuleEntity) { // 0x________________________________________________AA____________________ is pre hook // 0x__________________________________________________BB__________________ is post hook -function toSetValue(ExecutionHook memory executionHook) pure returns (bytes32) { - return bytes32(ModuleEntity.unwrap(executionHook.hookFunction)) - | bytes32(executionHook.isPreHook ? uint256(1) << 56 : 0) - | bytes32(executionHook.isPostHook ? uint256(1) << 48 : 0); +function toSetValue(HookConfig hookConfig) pure returns (bytes32) { + return bytes32(HookConfig.unwrap(hookConfig)); } -function toExecutionHook(bytes32 setValue) - pure - returns (ModuleEntity hookFunction, bool isPreHook, bool isPostHook) -{ - hookFunction = ModuleEntity.wrap(bytes24(setValue)); - isPreHook = (uint256(setValue) >> 56) & 0xFF == 1; - isPostHook = (uint256(setValue) >> 48) & 0xFF == 1; +function toHookConfig(bytes32 setValue) pure returns (HookConfig) { + return HookConfig.wrap(bytes26(setValue)); } function toSetValue(bytes4 selector) pure returns (bytes32) { diff --git a/src/account/ModuleManagerInternals.sol b/src/account/ModuleManagerInternals.sol index 5eeeb77b..1c5dc6db 100644 --- a/src/account/ModuleManagerInternals.sol +++ b/src/account/ModuleManagerInternals.sol @@ -5,19 +5,27 @@ import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165C import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import {DIRECT_CALL_VALIDATION_ENTITYID, MAX_PRE_VALIDATION_HOOKS} from "../helpers/Constants.sol"; +import {MAX_PRE_VALIDATION_HOOKS} from "../helpers/Constants.sol"; +import {HookConfigLib} from "../helpers/HookConfigLib.sol"; import {KnownSelectors} from "../helpers/KnownSelectors.sol"; import {ModuleEntityLib} from "../helpers/ModuleEntityLib.sol"; import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; -import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; import {IModule, ManifestExecutionHook, ModuleManifest} from "../interfaces/IModule.sol"; -import {IModuleManager, ModuleEntity, ValidationConfig} from "../interfaces/IModuleManager.sol"; -import {AccountStorage, SelectorData, ValidationData, getAccountStorage, toSetValue} from "./AccountStorage.sol"; +import {HookConfig, IModuleManager, ModuleEntity, ValidationConfig} from "../interfaces/IModuleManager.sol"; +import { + AccountStorage, + SelectorData, + ValidationData, + getAccountStorage, + toModuleEntity, + toSetValue +} from "./AccountStorage.sol"; abstract contract ModuleManagerInternals is IModuleManager { using EnumerableSet for EnumerableSet.Bytes32Set; using ModuleEntityLib for ModuleEntity; using ValidationConfigLib for ValidationConfig; + using HookConfigLib for HookConfig; error ArrayLengthMismatch(); error Erc4337FunctionNotAllowed(bytes4 selector); @@ -25,7 +33,7 @@ abstract contract ModuleManagerInternals is IModuleManager { error IModuleFunctionNotAllowed(bytes4 selector); error NativeFunctionNotAllowed(bytes4 selector); error NullModule(); - error PermissionAlreadySet(ModuleEntity validationFunction, ExecutionHook hook); + error PermissionAlreadySet(ModuleEntity validationFunction, HookConfig hookConfig); error ModuleInstallCallbackFailed(address module, bytes revertReason); error ModuleInterfaceNotSupported(address module); error ModuleNotInstalled(address module); @@ -108,30 +116,12 @@ abstract contract ModuleManagerInternals is IModuleManager { } } - function _addExecHooks( - EnumerableSet.Bytes32Set storage hooks, - ModuleEntity hookFunction, - bool isPreExecHook, - bool isPostExecHook - ) internal { - hooks.add( - toSetValue( - ExecutionHook({hookFunction: hookFunction, isPreHook: isPreExecHook, isPostHook: isPostExecHook}) - ) - ); + function _addExecHooks(EnumerableSet.Bytes32Set storage hooks, HookConfig hookConfig) internal { + hooks.add(toSetValue(hookConfig)); } - function _removeExecHooks( - EnumerableSet.Bytes32Set storage hooks, - ModuleEntity hookFunction, - bool isPreExecHook, - bool isPostExecHook - ) internal { - hooks.remove( - toSetValue( - ExecutionHook({hookFunction: hookFunction, isPreHook: isPreExecHook, isPostHook: isPostExecHook}) - ) - ); + function _removeExecHooks(EnumerableSet.Bytes32Set storage hooks, HookConfig hookConfig) internal { + hooks.remove(toSetValue(hookConfig)); } function _installModule(address module, ModuleManifest calldata manifest, bytes memory moduleInstallData) @@ -162,8 +152,13 @@ abstract contract ModuleManagerInternals is IModuleManager { for (uint256 i = 0; i < length; ++i) { ManifestExecutionHook memory mh = manifest.executionHooks[i]; EnumerableSet.Bytes32Set storage execHooks = _storage.selectorData[mh.executionSelector].executionHooks; - ModuleEntity hookFunction = ModuleEntityLib.pack(module, mh.entityId); - _addExecHooks(execHooks, hookFunction, mh.isPreHook, mh.isPostHook); + HookConfig hookConfig = HookConfigLib.packExecHook({ + _module: module, + _entityId: mh.entityId, + _hasPre: mh.isPreHook, + _hasPost: mh.isPostHook + }); + _addExecHooks(execHooks, hookConfig); } length = manifest.interfaceIds.length; @@ -191,9 +186,14 @@ abstract contract ModuleManagerInternals is IModuleManager { uint256 length = manifest.executionHooks.length; for (uint256 i = 0; i < length; ++i) { ManifestExecutionHook memory mh = manifest.executionHooks[i]; - ModuleEntity hookFunction = ModuleEntityLib.pack(module, mh.entityId); EnumerableSet.Bytes32Set storage execHooks = _storage.selectorData[mh.executionSelector].executionHooks; - _removeExecHooks(execHooks, hookFunction, mh.isPreHook, mh.isPostHook); + HookConfig hookConfig = HookConfigLib.packExecHook({ + _module: module, + _entityId: mh.entityId, + _hasPre: mh.isPreHook, + _hasPost: mh.isPostHook + }); + _removeExecHooks(execHooks, hookConfig); } length = manifest.executionFunctions.length; @@ -218,53 +218,44 @@ abstract contract ModuleManagerInternals is IModuleManager { emit ModuleUninstalled(module, onUninstallSuccess); } + function _onInstall(address module, bytes calldata data) internal { + if (data.length > 0) { + IModule(module).onInstall(data); + } + } + + function _onUninstall(address module, bytes calldata data) internal { + if (data.length > 0) { + IModule(module).onUninstall(data); + } + } + function _installValidation( ValidationConfig validationConfig, - bytes4[] memory selectors, + bytes4[] calldata selectors, bytes calldata installData, - bytes memory preValidationHooks, - bytes memory permissionHooks + bytes[] calldata hooks ) internal { ValidationData storage _validationData = getAccountStorage().validationData[validationConfig.moduleEntity()]; - if (preValidationHooks.length > 0) { - (ModuleEntity[] memory preValidationFunctions, bytes[] memory initDatas) = - abi.decode(preValidationHooks, (ModuleEntity[], bytes[])); - - for (uint256 i = 0; i < preValidationFunctions.length; ++i) { - ModuleEntity preValidationFunction = preValidationFunctions[i]; + for (uint256 i = 0; i < hooks.length; ++i) { + HookConfig hookConfig = HookConfig.wrap(bytes26(hooks[i][:26])); + bytes calldata hookData = hooks[i][26:]; - _validationData.preValidationHooks.push(preValidationFunction); + if (hookConfig.isValidationHook()) { + _validationData.preValidationHooks.push(hookConfig.moduleEntity()); - if (initDatas[i].length > 0) { - (address preValidationModule,) = ModuleEntityLib.unpack(preValidationFunction); - IModule(preValidationModule).onInstall(initDatas[i]); + // Avoid collision between reserved index and actual indices + if (_validationData.preValidationHooks.length > MAX_PRE_VALIDATION_HOOKS) { + revert PreValidationHookLimitExceeded(); } + } // Hook is an execution hook + else if (!_validationData.permissionHooks.add(toSetValue(hookConfig))) { + revert PermissionAlreadySet(validationConfig.moduleEntity(), hookConfig); } - // Avoid collision between reserved index and actual indices - if (_validationData.preValidationHooks.length > MAX_PRE_VALIDATION_HOOKS) { - revert PreValidationHookLimitExceeded(); - } - } - - if (permissionHooks.length > 0) { - (ExecutionHook[] memory permissionFunctions, bytes[] memory initDatas) = - abi.decode(permissionHooks, (ExecutionHook[], bytes[])); - - for (uint256 i = 0; i < permissionFunctions.length; ++i) { - ExecutionHook memory permissionFunction = permissionFunctions[i]; - - if (!_validationData.permissionHooks.add(toSetValue(permissionFunction))) { - revert PermissionAlreadySet(validationConfig.moduleEntity(), permissionFunction); - } - - if (initDatas[i].length > 0) { - (address executionModule,) = ModuleEntityLib.unpack(permissionFunction.hookFunction); - IModule(executionModule).onInstall(initDatas[i]); - } - } + _onInstall(hookConfig.module(), hookData); } for (uint256 i = 0; i < selectors.length; ++i) { @@ -274,56 +265,59 @@ abstract contract ModuleManagerInternals is IModuleManager { } } - if (validationConfig.entityId() != DIRECT_CALL_VALIDATION_ENTITYID) { - // Only allow global validations and signature validations if they're not direct-call validations. + _validationData.isGlobal = validationConfig.isGlobal(); + _validationData.isSignatureValidation = validationConfig.isSignatureValidation(); - _validationData.isGlobal = validationConfig.isGlobal(); - _validationData.isSignatureValidation = validationConfig.isSignatureValidation(); - if (installData.length > 0) { - IModule(validationConfig.module()).onInstall(installData); - } - } + _onInstall(validationConfig.module(), installData); } function _uninstallValidation( ModuleEntity validationFunction, bytes calldata uninstallData, - bytes calldata preValidationHookUninstallData, - bytes calldata permissionHookUninstallData + bytes[] calldata hookUninstallDatas ) internal { ValidationData storage _validationData = getAccountStorage().validationData[validationFunction]; _removeValidationFunction(validationFunction); - { - bytes[] memory preValidationHookUninstallDatas = abi.decode(preValidationHookUninstallData, (bytes[])); + // Send `onUninstall` to hooks + if (hookUninstallDatas.length > 0) { + // If any uninstall data is provided, assert it is of the correct length. + if ( + hookUninstallDatas.length + != _validationData.preValidationHooks.length + _validationData.permissionHooks.length() + ) { + revert ArrayLengthMismatch(); + } - // Clear pre validation hooks - ModuleEntity[] storage preValidationHooks = _validationData.preValidationHooks; - for (uint256 i = 0; i < preValidationHooks.length; ++i) { - ModuleEntity preValidationFunction = preValidationHooks[i]; - if (preValidationHookUninstallDatas[0].length > 0) { - (address preValidationModule,) = ModuleEntityLib.unpack(preValidationFunction); - IModule(preValidationModule).onUninstall(preValidationHookUninstallDatas[0]); - } + // Hook uninstall data is provided in the order of pre-validation hooks, then permission hooks. + uint256 hookIndex = 0; + for (uint256 i = 0; i < _validationData.preValidationHooks.length; ++i) { + bytes calldata hookData = hookUninstallDatas[hookIndex]; + (address hookModule,) = ModuleEntityLib.unpack(_validationData.preValidationHooks[i]); + _onUninstall(hookModule, hookData); + hookIndex++; } - delete _validationData.preValidationHooks; - } - { - bytes[] memory permissionHookUninstallDatas = abi.decode(permissionHookUninstallData, (bytes[])); - - // Clear permission hooks - EnumerableSet.Bytes32Set storage permissionHooks = _validationData.permissionHooks; - uint256 permissionHookLen = permissionHooks.length(); - for (uint256 i = 0; i < permissionHookLen; ++i) { - bytes32 permissionHook = permissionHooks.at(0); - permissionHooks.remove(permissionHook); - address permissionHookModule = address(uint160(bytes20(permissionHook))); - IModule(permissionHookModule).onUninstall(permissionHookUninstallDatas[i]); + for (uint256 i = 0; i < _validationData.permissionHooks.length(); ++i) { + bytes calldata hookData = hookUninstallDatas[hookIndex]; + (address hookModule,) = + ModuleEntityLib.unpack(toModuleEntity(_validationData.permissionHooks.at(i))); + _onUninstall(hookModule, hookData); + hookIndex++; } } + // Clear all stored hooks + delete _validationData.preValidationHooks; + + EnumerableSet.Bytes32Set storage permissionHooks = _validationData.permissionHooks; + uint256 permissionHookLen = permissionHooks.length(); + for (uint256 i = 0; i < permissionHookLen; ++i) { + bytes32 permissionHook = permissionHooks.at(0); + permissionHooks.remove(permissionHook); + } + // Clear selectors uint256 selectorLen = _validationData.selectors.length(); for (uint256 i = 0; i < selectorLen; ++i) { @@ -331,9 +325,7 @@ abstract contract ModuleManagerInternals is IModuleManager { _validationData.selectors.remove(selectorSetValue); } - if (uninstallData.length > 0) { - (address module,) = ModuleEntityLib.unpack(validationFunction); - IModule(module).onUninstall(uninstallData); - } + (address module,) = ModuleEntityLib.unpack(validationFunction); + _onUninstall(module, uninstallData); } } diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 87379241..f8545051 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -12,6 +12,7 @@ import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeab import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import {HookConfig, HookConfigLib} from "../helpers/HookConfigLib.sol"; import {ModuleEntityLib} from "../helpers/ModuleEntityLib.sol"; import {SparseCalldataSegmentLib} from "../helpers/SparseCalldataSegmentLib.sol"; @@ -27,7 +28,7 @@ import {IValidation} from "../interfaces/IValidation.sol"; import {IValidationHook} from "../interfaces/IValidationHook.sol"; import {AccountExecutor} from "./AccountExecutor.sol"; import {AccountLoupe} from "./AccountLoupe.sol"; -import {AccountStorage, getAccountStorage, toExecutionHook, toSetValue} from "./AccountStorage.sol"; +import {AccountStorage, getAccountStorage, toHookConfig, toSetValue} from "./AccountStorage.sol"; import {AccountStorageInitializable} from "./AccountStorageInitializable.sol"; import {ModuleManagerInternals} from "./ModuleManagerInternals.sol"; @@ -46,6 +47,7 @@ contract UpgradeableModularAccount is using EnumerableSet for EnumerableSet.Bytes32Set; using ModuleEntityLib for ModuleEntity; using ValidationConfigLib for ValidationConfig; + using HookConfigLib for HookConfig; using SparseCalldataSegmentLib for bytes; struct PostExecToRun { @@ -249,12 +251,11 @@ contract UpgradeableModularAccount is /// @dev This function is only callable once, and only by the EntryPoint. function initializeWithValidation( ValidationConfig validationConfig, - bytes4[] memory selectors, + bytes4[] calldata selectors, bytes calldata installData, - bytes calldata preValidationHooks, - bytes calldata permissionHooks + bytes[] calldata hooks ) external initializer { - _installValidation(validationConfig, selectors, installData, preValidationHooks, permissionHooks); + _installValidation(validationConfig, selectors, installData, hooks); emit ModularAccountInitialized(_ENTRY_POINT); } @@ -262,12 +263,11 @@ contract UpgradeableModularAccount is /// @notice May be validated by a global validation. function installValidation( ValidationConfig validationConfig, - bytes4[] memory selectors, + bytes4[] calldata selectors, bytes calldata installData, - bytes calldata preValidationHooks, - bytes calldata permissionHooks + bytes[] calldata hooks ) external wrapNativeFunction { - _installValidation(validationConfig, selectors, installData, preValidationHooks, permissionHooks); + _installValidation(validationConfig, selectors, installData, hooks); } /// @inheritdoc IModuleManager @@ -275,12 +275,9 @@ contract UpgradeableModularAccount is function uninstallValidation( ModuleEntity validationFunction, bytes calldata uninstallData, - bytes calldata preValidationHookUninstallData, - bytes calldata permissionHookUninstallData + bytes[] calldata hookUninstallData ) external wrapNativeFunction { - _uninstallValidation( - validationFunction, uninstallData, preValidationHookUninstallData, permissionHookUninstallData - ); + _uninstallValidation(validationFunction, uninstallData, hookUninstallData); } /// @notice ERC165 introspection @@ -503,26 +500,24 @@ contract UpgradeableModularAccount is // Copy all post hooks to the array. This happens before any pre hooks are run, so we can // be sure that the set of hooks to run will not be affected by state changes mid-execution. for (uint256 i = 0; i < hooksLength; ++i) { - bytes32 key = executionHooks.at(i); - (ModuleEntity hookFunction,, bool isPostHook) = toExecutionHook(key); - if (isPostHook) { - postHooksToRun[i].postExecHook = hookFunction; + HookConfig hookConfig = toHookConfig(executionHooks.at(i)); + if (hookConfig.hasPostHook()) { + postHooksToRun[i].postExecHook = hookConfig.moduleEntity(); } } // Run the pre hooks and copy their return data to the post hooks array, if an associated post-exec hook // exists. for (uint256 i = 0; i < hooksLength; ++i) { - bytes32 key = executionHooks.at(i); - (ModuleEntity hookFunction, bool isPreHook, bool isPostHook) = toExecutionHook(key); + HookConfig hookConfig = toHookConfig(executionHooks.at(i)); - if (isPreHook) { + if (hookConfig.hasPreHook()) { bytes memory preExecHookReturnData; - preExecHookReturnData = _runPreExecHook(hookFunction, data); + preExecHookReturnData = _runPreExecHook(hookConfig.moduleEntity(), data); // If there is an associated post-exec hook, save the return data. - if (isPostHook) { + if (hookConfig.hasPostHook()) { postHooksToRun[i].preExecHookReturnData = preExecHookReturnData; } } diff --git a/src/helpers/HookConfigLib.sol b/src/helpers/HookConfigLib.sol new file mode 100644 index 00000000..9c94e4bd --- /dev/null +++ b/src/helpers/HookConfigLib.sol @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.25; + +import {HookConfig, ModuleEntity} from "../interfaces/IModuleManager.sol"; + +// Hook types: +// Exec hook: bools for hasPre, hasPost +// Validation hook: no bools + +// Hook fields: +// module address +// entity ID +// hook type +// if exec hook: hasPre, hasPost + +// Hook config is a packed representation of a hook function and flags for its configuration. +// Layout: +// 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA________________________ // Address +// 0x________________________________________BBBBBBBB________________ // Entity ID +// 0x________________________________________________CC______________ // Type +// 0x__________________________________________________DD____________ // exec hook flags +// + +// Hook types: +// 0x00 // Exec (selector and validation associated) +// 0x01 // Validation + +// Exec hook flags layout: +// 0b000000__ // unused +// 0b______A_ // hasPre +// 0b_______B // hasPost + +library HookConfigLib { + // Hook type constants + // Exec has no bits set + bytes32 internal constant _HOOK_TYPE_EXEC = bytes32(uint256(0)); + // Validation has 1 in the 25th byte + bytes32 internal constant _HOOK_TYPE_VALIDATION = bytes32(uint256(1) << 56); + + // Exec hook flags constants + // Pre hook has 1 in 2's bit in the 26th byte + bytes32 internal constant _EXEC_HOOK_HAS_PRE = bytes32(uint256(1) << 49); + // Post hook has 1 in 1's bit in the 26th byte + bytes32 internal constant _EXEC_HOOK_HAS_POST = bytes32(uint256(1) << 48); + + function packValidationHook(ModuleEntity _hookFunction) internal pure returns (HookConfig) { + return + HookConfig.wrap(bytes26(bytes26(ModuleEntity.unwrap(_hookFunction)) | bytes26(_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( + bytes26( + bytes26(ModuleEntity.unwrap(_hookFunction)) + // | bytes26(_HOOK_TYPE_EXEC) // Can omit because exec type is 0 + | bytes26(_hasPre ? _EXEC_HOOK_HAS_PRE : bytes32(0)) + | bytes26(_hasPost ? _EXEC_HOOK_HAS_POST : bytes32(0)) + ) + ); + } + + function packExecHook(address _module, uint32 _entityId, bool _hasPre, bool _hasPost) + internal + pure + returns (HookConfig) + { + return HookConfig.wrap( + bytes26( + // module address stored in the first 20 bytes + bytes26(bytes20(_module)) + // entityId stored in the 21st - 24th byte + | bytes26(bytes24(uint192(_entityId))) + // | bytes26(_HOOK_TYPE_EXEC) // Can omit because exec type is 0 + | bytes26(_hasPre ? _EXEC_HOOK_HAS_PRE : bytes32(0)) + | bytes26(_hasPost ? _EXEC_HOOK_HAS_POST : bytes32(0)) + ) + ); + } + + function unpackValidationHook(HookConfig _config) internal pure returns (ModuleEntity _hookFunction) { + bytes26 configBytes = HookConfig.unwrap(_config); + _hookFunction = ModuleEntity.wrap(bytes24(configBytes)); + } + + function unpackExecHook(HookConfig _config) + internal + pure + returns (ModuleEntity _hookFunction, bool _hasPre, bool _hasPost) + { + bytes26 configBytes = HookConfig.unwrap(_config); + _hookFunction = ModuleEntity.wrap(bytes24(configBytes)); + _hasPre = configBytes & _EXEC_HOOK_HAS_PRE != 0; + _hasPost = configBytes & _EXEC_HOOK_HAS_POST != 0; + } + + function module(HookConfig _config) internal pure returns (address) { + return address(bytes20(HookConfig.unwrap(_config))); + } + + function entityId(HookConfig _config) internal pure returns (uint32) { + return uint32(bytes4(HookConfig.unwrap(_config) << 160)); + } + + function moduleEntity(HookConfig _config) internal pure returns (ModuleEntity) { + return ModuleEntity.wrap(bytes24(HookConfig.unwrap(_config))); + } + + // Check if the hook is a validation hook + // If false, it is an exec hook + function isValidationHook(HookConfig _config) internal pure returns (bool) { + return HookConfig.unwrap(_config) & _HOOK_TYPE_VALIDATION != 0; + } + + // 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; + } + + // 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; + } +} diff --git a/src/interfaces/IModuleManager.sol b/src/interfaces/IModuleManager.sol index 9923bd34..d2e63f74 100644 --- a/src/interfaces/IModuleManager.sol +++ b/src/interfaces/IModuleManager.sol @@ -7,6 +7,8 @@ type ModuleEntity is bytes24; type ValidationConfig is bytes26; +type HookConfig is bytes26; + interface IModuleManager { event ModuleInstalled(address indexed module); @@ -29,14 +31,14 @@ interface IModuleManager { /// @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 decoded and used by the module to setup initial module state. - /// @param preValidationHooks Optional pre-validation hooks to install for the validation function. - /// @param permissionHooks Optional permission hooks to install for the validation function. + /// @param hooks Optional hooks to install, associated with the validation function. These may be + /// pre-validation hooks or execution hooks. The expected format is a bytes26 HookConfig, followed by the + /// install data, if any. function installValidation( ValidationConfig validationConfig, - bytes4[] memory selectors, + bytes4[] calldata selectors, bytes calldata installData, - bytes calldata preValidationHooks, - bytes calldata permissionHooks + bytes[] calldata hooks ) external; /// @notice Uninstall a validation function from a set of execution selectors. @@ -44,14 +46,13 @@ interface IModuleManager { /// @param validationFunction The validation function to uninstall. /// @param uninstallData Optional data to be decoded and used by the module to clear module data for the /// account. - /// @param preValidationHookUninstallData Optional data to be decoded and used by the module to clear account - /// data - /// @param permissionHookUninstallData Optional data to be decoded and used by the module to clear account data + /// @param hookUninstallData Optional data to be used by hooks for cleanup. If any are provided, the array must + /// be of a length equal to existing pre-validation hooks plus permission hooks. Hooks are indexed by + /// pre-validation hook order first, then permission hooks. function uninstallValidation( ModuleEntity validationFunction, bytes calldata uninstallData, - bytes calldata preValidationHookUninstallData, - bytes calldata permissionHookUninstallData + bytes[] calldata hookUninstallData ) external; /// @notice Uninstall a module from the modular account. diff --git a/test/account/AccountLoupe.t.sol b/test/account/AccountLoupe.t.sol index 8b03d86f..988e3dd5 100644 --- a/test/account/AccountLoupe.t.sol +++ b/test/account/AccountLoupe.t.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.19; import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; +import {HookConfigLib} from "../../src/helpers/HookConfigLib.sol"; import {ModuleEntity, ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; import {IModuleManager} from "../../src/interfaces/IModuleManager.sol"; @@ -135,28 +136,23 @@ contract AccountLoupeTest is CustomValidationTestBase { internal virtual override - returns (ModuleEntity, bool, bool, bytes4[] memory, bytes memory, bytes memory, bytes memory) + returns (ModuleEntity, bool, bool, bytes4[] memory, bytes memory, bytes[] memory) { - ModuleEntity[] memory preValidationHooks = new ModuleEntity[](2); - preValidationHooks[0] = ModuleEntityLib.pack( - address(comprehensiveModule), uint32(ComprehensiveModule.EntityId.PRE_VALIDATION_HOOK_1) + bytes[] memory hooks = new bytes[](2); + hooks[0] = abi.encodePacked( + HookConfigLib.packValidationHook( + address(comprehensiveModule), uint32(ComprehensiveModule.EntityId.PRE_VALIDATION_HOOK_1) + ) ); - preValidationHooks[1] = ModuleEntityLib.pack( - address(comprehensiveModule), uint32(ComprehensiveModule.EntityId.PRE_VALIDATION_HOOK_2) + hooks[1] = abi.encodePacked( + HookConfigLib.packValidationHook( + address(comprehensiveModule), uint32(ComprehensiveModule.EntityId.PRE_VALIDATION_HOOK_2) + ) ); bytes4[] memory selectors = new bytes4[](1); selectors[0] = comprehensiveModule.foo.selector; - bytes[] memory installDatas = new bytes[](2); - return ( - comprehensiveModuleValidation, - true, - true, - selectors, - bytes(""), - abi.encode(preValidationHooks, installDatas), - "" - ); + return (comprehensiveModuleValidation, true, true, selectors, bytes(""), hooks); } } diff --git a/test/account/AccountReturnData.t.sol b/test/account/AccountReturnData.t.sol index d6834af6..4f6fef9d 100644 --- a/test/account/AccountReturnData.t.sol +++ b/test/account/AccountReturnData.t.sol @@ -48,8 +48,7 @@ contract AccountReturnDataTest is AccountTestBase { ValidationConfigLib.pack(address(resultConsumerModule), DIRECT_CALL_VALIDATION_ENTITYID, false, false), selectors, "", - "", - "" + new bytes[](0) ); vm.stopPrank(); } diff --git a/test/account/DirectCallsFromModule.t.sol b/test/account/DirectCallsFromModule.t.sol index b8d39728..6d108540 100644 --- a/test/account/DirectCallsFromModule.t.sol +++ b/test/account/DirectCallsFromModule.t.sol @@ -1,9 +1,10 @@ pragma solidity ^0.8.19; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; + +import {HookConfigLib} from "../../src/helpers/HookConfigLib.sol"; import {ModuleEntity, ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {ValidationConfig, ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; -import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; import {Call, IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; import {DirectCallModule} from "../mocks/modules/DirectCallModule.sol"; @@ -108,23 +109,22 @@ contract DirectCallsFromModuleTest is AccountTestBase { bytes4[] memory selectors = new bytes4[](1); selectors[0] = IStandardExecutor.execute.selector; - ExecutionHook[] memory permissionHooks = new ExecutionHook[](1); - bytes[] memory permissionHookInitDatas = new bytes[](1); - - permissionHooks[0] = ExecutionHook({hookFunction: _moduleEntity, isPreHook: true, isPostHook: true}); - - bytes memory encodedPermissionHooks = abi.encode(permissionHooks, permissionHookInitDatas); + bytes[] memory hooks = new bytes[](1); + hooks[0] = abi.encodePacked( + HookConfigLib.packExecHook({_hookFunction: _moduleEntity, _hasPre: true, _hasPost: true}), + hex"00" // onInstall data + ); vm.prank(address(entryPoint)); ValidationConfig validationConfig = ValidationConfigLib.pack(_moduleEntity, false, false); - account1.installValidation(validationConfig, selectors, "", "", encodedPermissionHooks); + account1.installValidation(validationConfig, selectors, "", hooks); } function _uninstallModule() internal { vm.prank(address(entryPoint)); - account1.uninstallValidation(_moduleEntity, "", abi.encode(new bytes[](0)), abi.encode(new bytes[](1))); + account1.uninstallValidation(_moduleEntity, "", new bytes[](1)); } function _buildDirectCallDisallowedError(bytes4 selector) internal pure returns (bytes memory) { diff --git a/test/account/MultiValidation.t.sol b/test/account/MultiValidation.t.sol index 772011bf..3e927ee6 100644 --- a/test/account/MultiValidation.t.sol +++ b/test/account/MultiValidation.t.sol @@ -39,8 +39,7 @@ contract MultiValidationTest is AccountTestBase { ValidationConfigLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID, true, true), new bytes4[](0), abi.encode(TEST_DEFAULT_VALIDATION_ENTITY_ID, owner2), - "", - "" + new bytes[](0) ); ModuleEntity[] memory validations = new ModuleEntity[](2); diff --git a/test/account/PerHookData.t.sol b/test/account/PerHookData.t.sol index 15cefbd9..cc3f3415 100644 --- a/test/account/PerHookData.t.sol +++ b/test/account/PerHookData.t.sol @@ -6,7 +6,9 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {ModuleEntity, ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; + +import {HookConfigLib} from "../../src/helpers/HookConfigLib.sol"; +import {ModuleEntity} from "../../src/helpers/ModuleEntityLib.sol"; import {Counter} from "../mocks/Counter.sol"; import {MockAccessControlHookModule} from "../mocks/modules/MockAccessControlHookModule.sol"; @@ -330,29 +332,23 @@ contract PerHookDataTest is CustomValidationTestBase { internal virtual override - returns (ModuleEntity, bool, bool, bytes4[] memory, bytes memory, bytes memory, bytes memory) + returns (ModuleEntity, bool, bool, bytes4[] memory, bytes memory, bytes[] memory) { - ModuleEntity accessControlHook = ModuleEntityLib.pack( - address(_accessControlHookModule), uint32(MockAccessControlHookModule.EntityId.PRE_VALIDATION_HOOK) + bytes[] memory hooks = new bytes[](1); + hooks[0] = abi.encodePacked( + HookConfigLib.packValidationHook( + address(_accessControlHookModule), uint32(MockAccessControlHookModule.EntityId.PRE_VALIDATION_HOOK) + ), + abi.encode(_counter) ); - ModuleEntity[] memory preValidationHooks = new ModuleEntity[](1); - preValidationHooks[0] = accessControlHook; - - bytes[] memory preValidationHookData = new bytes[](1); - // Access control is restricted to only the counter - preValidationHookData[0] = abi.encode(_counter); - - bytes memory packedPreValidationHooks = abi.encode(preValidationHooks, preValidationHookData); - return ( _signerValidation, true, true, new bytes4[](0), abi.encode(TEST_DEFAULT_VALIDATION_ENTITY_ID, owner1), - packedPreValidationHooks, - "" + hooks ); } } diff --git a/test/account/SelfCallAuthorization.t.sol b/test/account/SelfCallAuthorization.t.sol index 4dd8a595..de2839c7 100644 --- a/test/account/SelfCallAuthorization.t.sol +++ b/test/account/SelfCallAuthorization.t.sol @@ -33,7 +33,10 @@ contract SelfCallAuthorizationTest is AccountTestBase { vm.startPrank(address(entryPoint)); account1.installModule(address(comprehensiveModule), comprehensiveModule.moduleManifest(), ""); account1.installValidation( - ValidationConfigLib.pack(comprehensiveModuleValidation, false, false), validationSelectors, "", "", "" + ValidationConfigLib.pack(comprehensiveModuleValidation, false, false), + validationSelectors, + "", + new bytes[](0) ); vm.stopPrank(); } @@ -302,7 +305,12 @@ contract SelfCallAuthorizationTest is AccountTestBase { account1.executeWithAuthorization( abi.encodeCall( UpgradeableModularAccount.installValidation, - (ValidationConfigLib.pack(comprehensiveModuleValidation, false, false), selectors, "", "", "") + ( + ValidationConfigLib.pack(comprehensiveModuleValidation, false, false), + selectors, + "", + new bytes[](0) + ) ), _encodeSignature(_signerValidation, GLOBAL_VALIDATION, "") ); diff --git a/test/account/ValidationIntersection.t.sol b/test/account/ValidationIntersection.t.sol index c9c1a882..73deb89f 100644 --- a/test/account/ValidationIntersection.t.sol +++ b/test/account/ValidationIntersection.t.sol @@ -4,6 +4,8 @@ pragma solidity ^0.8.19; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; + +import {HookConfigLib} from "../../src/helpers/HookConfigLib.sol"; import {ModuleEntity, ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; @@ -52,47 +54,36 @@ contract ValidationIntersectionTest is AccountTestBase { vm.startPrank(address(entryPoint)); // Install noHookValidation account1.installValidation( - ValidationConfigLib.pack(noHookValidation, true, true), - validationSelectors, - bytes(""), - bytes(""), - bytes("") + ValidationConfigLib.pack(noHookValidation, true, true), validationSelectors, bytes(""), new bytes[](0) ); // Install oneHookValidation validationSelectors[0] = MockUserOpValidation1HookModule.bar.selector; - ModuleEntity[] memory preValidationHooks = new ModuleEntity[](1); - preValidationHooks[0] = ModuleEntityLib.pack({ - addr: address(oneHookModule), - entityId: uint32(MockBaseUserOpValidationModule.EntityId.PRE_VALIDATION_HOOK_1) - }); - bytes[] memory installDatas = new bytes[](1); + bytes[] memory hooks = new bytes[](1); + hooks[0] = abi.encodePacked( + HookConfigLib.packValidationHook( + address(oneHookModule), uint32(MockBaseUserOpValidationModule.EntityId.PRE_VALIDATION_HOOK_1) + ) + ); account1.installValidation( - ValidationConfigLib.pack(oneHookValidation, true, true), - validationSelectors, - bytes(""), - abi.encode(preValidationHooks, installDatas), - bytes("") + ValidationConfigLib.pack(oneHookValidation, true, true), validationSelectors, bytes(""), hooks ); // Install twoHookValidation validationSelectors[0] = MockUserOpValidation2HookModule.baz.selector; - preValidationHooks = new ModuleEntity[](2); - preValidationHooks[0] = ModuleEntityLib.pack({ - addr: address(twoHookModule), - entityId: uint32(MockBaseUserOpValidationModule.EntityId.PRE_VALIDATION_HOOK_1) - }); - preValidationHooks[1] = ModuleEntityLib.pack({ - addr: address(twoHookModule), - entityId: uint32(MockBaseUserOpValidationModule.EntityId.PRE_VALIDATION_HOOK_2) - }); - installDatas = new bytes[](2); + hooks = new bytes[](2); + hooks[0] = abi.encodePacked( + HookConfigLib.packValidationHook( + address(twoHookModule), uint32(MockBaseUserOpValidationModule.EntityId.PRE_VALIDATION_HOOK_1) + ) + ); + hooks[1] = abi.encodePacked( + HookConfigLib.packValidationHook( + address(twoHookModule), uint32(MockBaseUserOpValidationModule.EntityId.PRE_VALIDATION_HOOK_2) + ) + ); account1.installValidation( - ValidationConfigLib.pack(twoHookValidation, true, true), - validationSelectors, - bytes(""), - abi.encode(preValidationHooks, installDatas), - bytes("") + ValidationConfigLib.pack(twoHookValidation, true, true), validationSelectors, bytes(""), hooks ); vm.stopPrank(); } diff --git a/test/libraries/HookConfigLib.t.sol b/test/libraries/HookConfigLib.t.sol new file mode 100644 index 00000000..7a4671b8 --- /dev/null +++ b/test/libraries/HookConfigLib.t.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.25; + +import {Test} from "forge-std/Test.sol"; + +import {HookConfigLib} from "../../src/helpers/HookConfigLib.sol"; +import {ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; +import {HookConfig, ModuleEntity} from "../../src/interfaces/IModuleManager.sol"; + +contract HookConfigLibTest is Test { + 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.module(), addr, "module mismatch"); + assertEq(hookConfig.entityId(), entityId, "entityId mismatch"); + assertEq(hookConfig.isValidationHook(), isValidation, "isValidation mismatch"); + + if (!isValidation) { + assertEq(hookConfig.hasPreHook(), hasPre, "hasPre mismatch"); + assertEq(hookConfig.hasPostHook(), 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.moduleEntity()), + ModuleEntity.unwrap(hookFunction), + "moduleEntity mismatch" + ); + assertEq(hookConfig.isValidationHook(), isValidation, "isValidation mismatch"); + + if (!isValidation) { + assertEq(hookConfig.hasPreHook(), hasPre, "hasPre mismatch"); + assertEq(hookConfig.hasPostHook(), hasPost, "hasPost mismatch"); + } + } +} diff --git a/test/mocks/SingleSignerFactoryFixture.sol b/test/mocks/SingleSignerFactoryFixture.sol index 98dfc0b8..b4a3a4ff 100644 --- a/test/mocks/SingleSignerFactoryFixture.sol +++ b/test/mocks/SingleSignerFactoryFixture.sol @@ -56,8 +56,7 @@ contract SingleSignerFactoryFixture is OptimizedTest { ), new bytes4[](0), moduleInstallData, - "", - "" + new bytes[](0) ); } diff --git a/test/module/AllowlistModule.t.sol b/test/module/AllowlistModule.t.sol index 136ae211..b0e09c90 100644 --- a/test/module/AllowlistModule.t.sol +++ b/test/module/AllowlistModule.t.sol @@ -4,7 +4,9 @@ pragma solidity ^0.8.25; import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {ModuleEntity, ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; + +import {HookConfigLib} from "../../src/helpers/HookConfigLib.sol"; +import {ModuleEntity} from "../../src/helpers/ModuleEntityLib.sol"; import {Call} from "../../src/interfaces/IStandardExecutor.sol"; import {AllowlistModule} from "../../src/modules/permissionhooks/AllowlistModule.sol"; @@ -291,19 +293,15 @@ contract AllowlistModuleTest is CustomValidationTestBase { internal virtual override - returns (ModuleEntity, bool, bool, bytes4[] memory, bytes memory, bytes memory, bytes memory) + returns (ModuleEntity, bool, bool, bytes4[] memory, bytes memory, bytes[] memory) { - ModuleEntity accessControlHook = - ModuleEntityLib.pack(address(allowlistModule), uint32(AllowlistModule.EntityId.PRE_VALIDATION_HOOK)); - - ModuleEntity[] memory preValidationHooks = new ModuleEntity[](1); - preValidationHooks[0] = accessControlHook; - - bytes[] memory preValidationHookData = new bytes[](1); - // Access control is restricted to only the counter - preValidationHookData[0] = abi.encode(allowlistInit); - - bytes memory packedPreValidationHooks = abi.encode(preValidationHooks, preValidationHookData); + bytes[] memory hooks = new bytes[](1); + hooks[0] = abi.encodePacked( + HookConfigLib.packValidationHook( + address(allowlistModule), uint32(AllowlistModule.EntityId.PRE_VALIDATION_HOOK) + ), + abi.encode(allowlistInit) + ); return ( _signerValidation, @@ -311,8 +309,7 @@ contract AllowlistModuleTest is CustomValidationTestBase { true, new bytes4[](0), abi.encode(TEST_DEFAULT_VALIDATION_ENTITY_ID, owner1), - packedPreValidationHooks, - "" + hooks ); } diff --git a/test/module/ERC20TokenLimitModule.t.sol b/test/module/ERC20TokenLimitModule.t.sol index e88a4527..c0aa8719 100644 --- a/test/module/ERC20TokenLimitModule.t.sol +++ b/test/module/ERC20TokenLimitModule.t.sol @@ -8,6 +8,7 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {ModuleEntity} from "../../src/helpers/ModuleEntityLib.sol"; +import {HookConfigLib} from "../../src/helpers/HookConfigLib.sol"; import {ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; @@ -52,16 +53,15 @@ contract ERC20TokenLimitModuleTest is AccountTestBase { ERC20TokenLimitModule.ERC20SpendLimit[] memory limit = new ERC20TokenLimitModule.ERC20SpendLimit[](1); limit[0] = ERC20TokenLimitModule.ERC20SpendLimit({token: address(erc20), limits: limits}); - bytes[] memory permissionInitDatas = new bytes[](1); - permissionInitDatas[0] = abi.encode(uint8(0), limit); + bytes[] memory hooks = new bytes[](1); + hooks[0] = abi.encodePacked( + HookConfigLib.packExecHook({_module: address(module), _entityId: 0, _hasPre: true, _hasPost: false}), + abi.encode(uint32(0), limit) + ); vm.prank(address(acct)); acct.installValidation( - ValidationConfigLib.pack(address(validationModule), 0, true, true), - new bytes4[](0), - new bytes(0), - new bytes(0), - abi.encode(permissionHooks, permissionInitDatas) + ValidationConfigLib.pack(address(validationModule), 0, true, true), new bytes4[](0), "", hooks ); validationFunction = ModuleEntityLib.pack(address(validationModule), 0); diff --git a/test/module/NativeTokenLimitModule.t.sol b/test/module/NativeTokenLimitModule.t.sol index a5e20389..540dc902 100644 --- a/test/module/NativeTokenLimitModule.t.sol +++ b/test/module/NativeTokenLimitModule.t.sol @@ -8,8 +8,8 @@ import {ModuleEntity} from "../../src/helpers/ModuleEntityLib.sol"; import {ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; +import {HookConfigLib} from "../../src/helpers/HookConfigLib.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; -import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; import {ModuleManifest} from "../../src/interfaces/IModule.sol"; import {Call, IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; import {NativeTokenLimitModule} from "../../src/modules/NativeTokenLimitModule.sol"; @@ -38,29 +38,24 @@ contract NativeTokenLimitModuleTest is AccountTestBase { ModuleEntity[] memory preValidationHooks = new ModuleEntity[](1); preValidationHooks[0] = ModuleEntityLib.pack(address(module), 0); - ExecutionHook[] memory permissionHooks = new ExecutionHook[](1); - permissionHooks[0] = ExecutionHook({ - hookFunction: ModuleEntityLib.pack(address(module), 0), - isPreHook: true, - isPostHook: false - }); - uint256[] memory spendLimits = new uint256[](1); spendLimits[0] = spendLimit; - bytes[] memory preValHooksInitDatas = new bytes[](1); - preValHooksInitDatas[0] = ""; + bytes[] memory hooks = new bytes[](2); + hooks[0] = abi.encodePacked(HookConfigLib.packValidationHook({_module: address(module), _entityId: 0})); + // No init data for pre validation - bytes[] memory permissionInitDatas = new bytes[](1); - permissionInitDatas[0] = abi.encode(0, spendLimits); + hooks[1] = abi.encodePacked( + HookConfigLib.packExecHook({_module: address(module), _entityId: 0, _hasPre: true, _hasPost: false}), + abi.encode(0, spendLimits) + ); vm.prank(address(acct)); acct.installValidation( ValidationConfigLib.pack(address(validationModule), 0, true, true), new bytes4[](0), new bytes(0), - abi.encode(preValidationHooks, preValHooksInitDatas), - abi.encode(permissionHooks, permissionInitDatas) + hooks ); validationFunction = ModuleEntityLib.pack(address(validationModule), 0); @@ -120,7 +115,7 @@ contract NativeTokenLimitModuleTest is AccountTestBase { UpgradeableModularAccount.PreExecHookReverted.selector, abi.encode( address(module), - uint8(0), + uint32(0), abi.encodePacked(NativeTokenLimitModule.ExceededNativeTokenLimit.selector) ) ) diff --git a/test/module/SingleSignerValidation.t.sol b/test/module/SingleSignerValidation.t.sol index ae3be4e5..9358b59f 100644 --- a/test/module/SingleSignerValidation.t.sol +++ b/test/module/SingleSignerValidation.t.sol @@ -83,8 +83,7 @@ contract SingleSignerValidationTest is AccountTestBase { ValidationConfigLib.pack(address(singleSignerValidation), newEntityId, true, false), new bytes4[](0), abi.encode(newEntityId, owner2), - "", - "" + new bytes[](0) ); vm.prank(owner2); diff --git a/test/utils/CustomValidationTestBase.sol b/test/utils/CustomValidationTestBase.sol index 18fd6ae4..3b313039 100644 --- a/test/utils/CustomValidationTestBase.sol +++ b/test/utils/CustomValidationTestBase.sol @@ -21,8 +21,7 @@ abstract contract CustomValidationTestBase is AccountTestBase { bool isSignatureValidation, bytes4[] memory selectors, bytes memory installData, - bytes memory preValidationHooks, - bytes memory permissionHooks + bytes[] memory hooks ) = _initialValidationConfig(); address accountImplementation = address(factory.accountImplementation()); @@ -33,8 +32,7 @@ abstract contract CustomValidationTestBase is AccountTestBase { ValidationConfigLib.pack(validationFunction, isGlobal, isSignatureValidation), selectors, installData, - preValidationHooks, - permissionHooks + hooks ); vm.deal(address(account1), 100 ether); @@ -49,7 +47,6 @@ abstract contract CustomValidationTestBase is AccountTestBase { bool isSignatureValidation, bytes4[] memory selectors, bytes memory installData, - bytes memory preValidationHooks, - bytes memory permissionHooks + bytes[] memory hooks ); } From ebabd8c1bfa17122d35b682de5be274437fcc887 Mon Sep 17 00:00:00 2001 From: Adam Egyed <5456061+adamegyed@users.noreply.github.com> Date: Wed, 24 Jul 2024 19:06:31 -0400 Subject: [PATCH 065/111] test: [v0.8-develop] Add test for ValidationConfigLib 5/N (#110) --- test/libraries/ValidationConfigLib.t.sol | 105 +++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 test/libraries/ValidationConfigLib.t.sol diff --git a/test/libraries/ValidationConfigLib.t.sol b/test/libraries/ValidationConfigLib.t.sol new file mode 100644 index 00000000..4d49c383 --- /dev/null +++ b/test/libraries/ValidationConfigLib.t.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.25; + +import {Test} from "forge-std/Test.sol"; + +import {ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; +import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; +import {ModuleEntity, ValidationConfig} from "../../src/interfaces/IModuleManager.sol"; + +contract ValidationConfigLibTest is Test { + using ModuleEntityLib for ModuleEntity; + using ValidationConfigLib for ValidationConfig; + + // Tests the packing and unpacking of a validation config with a randomized state + + function testFuzz_validationConfig_packingUnderlying( + address module, + uint32 entityId, + bool isGlobal, + bool isSignatureValidation + ) public { + ValidationConfig validationConfig = + ValidationConfigLib.pack(module, entityId, isGlobal, isSignatureValidation); + + // Test unpacking underlying + (address module2, uint32 entityId2, bool isGlobal2, bool isSignatureValidation2) = + validationConfig.unpackUnderlying(); + + assertEq(module, module2, "module mismatch"); + assertEq(entityId, entityId2, "entityId mismatch"); + assertEq(isGlobal, isGlobal2, "isGlobal mismatch"); + assertEq(isSignatureValidation, isSignatureValidation2, "isSignatureValidation mismatch"); + + // Test unpacking to ModuleEntity + + ModuleEntity expectedModuleEntity = ModuleEntityLib.pack(module, entityId); + + (ModuleEntity validationFunction, bool isGlobal3, bool isSignatureValidation3) = validationConfig.unpack(); + + assertEq( + ModuleEntity.unwrap(validationFunction), + ModuleEntity.unwrap(expectedModuleEntity), + "validationFunction mismatch" + ); + assertEq(isGlobal, isGlobal3, "isGlobal mismatch"); + assertEq(isSignatureValidation, isSignatureValidation3, "isSignatureValidation 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"); + } + + function testFuzz_validationConfig_packingModuleEntity( + ModuleEntity validationFunction, + bool isGlobal, + bool isSignatureValidation + ) public { + ValidationConfig validationConfig = + ValidationConfigLib.pack(validationFunction, isGlobal, isSignatureValidation); + + // Test unpacking underlying + + (address expectedModule, uint32 expectedEntityId) = validationFunction.unpack(); + + (address module, uint32 entityId, bool isGlobal2, bool isSignatureValidation2) = + validationConfig.unpackUnderlying(); + + assertEq(expectedModule, module, "module mismatch"); + assertEq(expectedEntityId, entityId, "entityId mismatch"); + assertEq(isGlobal, isGlobal2, "isGlobal mismatch"); + assertEq(isSignatureValidation, isSignatureValidation2, "isSignatureValidation mismatch"); + + // Test unpacking to ModuleEntity + + (ModuleEntity validationFunction2, bool isGlobal3, bool isSignatureValidation3) = validationConfig.unpack(); + + assertEq( + ModuleEntity.unwrap(validationFunction), + ModuleEntity.unwrap(validationFunction2), + "validationFunction mismatch" + ); + assertEq(isGlobal, isGlobal3, "isGlobal mismatch"); + assertEq(isSignatureValidation, isSignatureValidation3, "isSignatureValidation mismatch"); + + // Test individual view functions + + assertEq(validationConfig.module(), expectedModule, "module mismatch"); + assertEq(validationConfig.entityId(), expectedEntityId, "entityId mismatch"); + assertEq( + ModuleEntity.unwrap(validationConfig.moduleEntity()), + ModuleEntity.unwrap(validationFunction), + "validationFunction mismatch" + ); + assertEq(validationConfig.isGlobal(), isGlobal, "isGlobal mismatch"); + assertEq(validationConfig.isSignatureValidation(), isSignatureValidation, "isSignatureValidation mismatch"); + } +} From b71d6c9ae94cdb342474c8ffa2ae6ce8b45c5736 Mon Sep 17 00:00:00 2001 From: Zer0dot Date: Fri, 26 Jul 2024 06:19:20 +0800 Subject: [PATCH 066/111] fix: Handle fallback hooks & entrypoint/self-call/public selector runtime execution hooks (#112) --- src/account/UpgradeableModularAccount.sol | 45 +++++++++++------------ 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index f8545051..8ef40fab 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -117,12 +117,8 @@ contract UpgradeableModularAccount is if (execModule == address(0)) { revert UnrecognizedFunction(msg.sig); } - - _checkPermittedCallerAndAssociatedHooks(); - - PostExecToRun[] memory postExecHooks; - // Cache post-exec hooks in memory - postExecHooks = _doPreHooks(getAccountStorage().selectorData[msg.sig].executionHooks, msg.data); + (PostExecToRun[] memory postPermissionHooks, PostExecToRun[] memory postExecHooks) = + _checkPermittedCallerAndAssociatedHooks(); // execute the function, bubbling up any reverts (bool execSuccess, bytes memory execReturnData) = execModule.call(msg.data); @@ -135,6 +131,7 @@ contract UpgradeableModularAccount is } _doCachedPostExecHooks(postExecHooks); + _doCachedPostExecHooks(postPermissionHooks); return execReturnData; } @@ -600,33 +597,35 @@ contract UpgradeableModularAccount is returns (PostExecToRun[] memory, PostExecToRun[] memory) { AccountStorage storage _storage = getAccountStorage(); + PostExecToRun[] memory postPermissionHooks; + // We only need to handle permission hooks when the sender is not the entry point or the account itself, + // and the selector isn't public. if ( - msg.sender == address(_ENTRY_POINT) || msg.sender == address(this) - || _storage.selectorData[msg.sig].isPublic + msg.sender != address(_ENTRY_POINT) && msg.sender != address(this) + && !_storage.selectorData[msg.sig].isPublic ) { - return (new PostExecToRun[](0), new PostExecToRun[](0)); - } + ModuleEntity directCallValidationKey = + ModuleEntityLib.pack(msg.sender, DIRECT_CALL_VALIDATION_ENTITYID); - ModuleEntity directCallValidationKey = ModuleEntityLib.pack(msg.sender, DIRECT_CALL_VALIDATION_ENTITYID); + _checkIfValidationAppliesCallData(msg.data, directCallValidationKey, false); - _checkIfValidationAppliesCallData(msg.data, directCallValidationKey, false); + // Direct call is allowed, run associated permission & validation hooks - // Direct call is allowed, run associated permission & validation hooks + // Validation hooks + ModuleEntity[] memory preRuntimeValidationHooks = + _storage.validationData[directCallValidationKey].preValidationHooks; - // Validation hooks - ModuleEntity[] memory preRuntimeValidationHooks = - _storage.validationData[directCallValidationKey].preValidationHooks; + uint256 hookLen = preRuntimeValidationHooks.length; + for (uint256 i = 0; i < hookLen; ++i) { + _doPreRuntimeValidationHook(preRuntimeValidationHooks[i], msg.data, ""); + } - uint256 hookLen = preRuntimeValidationHooks.length; - for (uint256 i = 0; i < hookLen; ++i) { - _doPreRuntimeValidationHook(preRuntimeValidationHooks[i], msg.data, ""); + // Permission hooks + postPermissionHooks = + _doPreHooks(_storage.validationData[directCallValidationKey].permissionHooks, msg.data); } - // Permission hooks - PostExecToRun[] memory postPermissionHooks = - _doPreHooks(_storage.validationData[directCallValidationKey].permissionHooks, msg.data); - // Exec hooks PostExecToRun[] memory postExecutionHooks = _doPreHooks(_storage.selectorData[msg.sig].executionHooks, msg.data); From 7efb881f318801a5a205c35a8da2d2a1b0f6ad73 Mon Sep 17 00:00:00 2001 From: zer0dot Date: Thu, 25 Jul 2024 18:39:15 -0400 Subject: [PATCH 067/111] fix: address renaming leftover issues --- src/account/UpgradeableModularAccount.sol | 10 +++++----- src/modules/validation/SingleSignerValidation.sol | 10 +++++----- test/account/ImmutableAppend.t.sol | 6 +++--- test/mocks/SingleSignerFactoryFixture.sol | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 74a5bb63..3db9bbda 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -226,7 +226,7 @@ contract UpgradeableModularAccount is // We pass a bool for "enabled" for ease of use, rather than the more efficient "disabled" // We just negate it later. function setBytecodeAppendedValidationEnabled(bool enabled) external wrapNativeFunction { - PluginEntity appendedValidation = _getAppendedValidation(); + ModuleEntity appendedValidation = _getAppendedValidation(); getAccountStorage().validationData[appendedValidation].isAppendedBytecodeValidationDisabled = !enabled; // TODO: event @@ -737,7 +737,7 @@ contract UpgradeableModularAccount is if ( _getAppendedValidation().eq(validationFunction) - && !validationFunction.eq(PluginEntity.wrap(bytes24(0))) + && !validationFunction.eq(ModuleEntity.wrap(bytes24(0))) && !_storage.validationData[validationFunction].isAppendedBytecodeValidationDisabled ) { return; @@ -752,7 +752,7 @@ contract UpgradeableModularAccount is } } - function _getAppendedValidation() internal view returns (PluginEntity) { + function _getAppendedValidation() internal view returns (ModuleEntity) { // Get only the 24 first bytes of appended data bytes memory appendedData = LibClone.argsOnERC1967(address(this), 0, 24); // Appended bytecode is under the format abi.encodePacked(pluginEntity, any...) @@ -761,9 +761,9 @@ contract UpgradeableModularAccount is if (appendedData.length > 0) { // TODO: Evaluate if it's better to somehow pass the data back from here and have it passed to the // validation instead of having it be read from bytecode by the validation - PluginEntity appendedValidationFunction = PluginEntity.wrap(bytes24(appendedData)); + ModuleEntity appendedValidationFunction = ModuleEntity.wrap(bytes24(appendedData)); return appendedValidationFunction; } - return PluginEntity.wrap(bytes24(0)); + return ModuleEntity.wrap(bytes24(0)); } } diff --git a/src/modules/validation/SingleSignerValidation.sol b/src/modules/validation/SingleSignerValidation.sol index 7d0f0c27..2f2205ee 100644 --- a/src/modules/validation/SingleSignerValidation.sol +++ b/src/modules/validation/SingleSignerValidation.sol @@ -13,8 +13,8 @@ import {IValidation} from "../../interfaces/IValidation.sol"; import {BaseModule} from "../BaseModule.sol"; import {ISingleSignerValidation} from "./ISingleSignerValidation.sol"; -import {PluginEntityLib} from "../../helpers/PluginEntityLib.sol"; -import {PluginEntity} from "../../interfaces/IPluginManager.sol"; +import {ModuleEntityLib} from "../../helpers/ModuleEntityLib.sol"; +import {ModuleEntity} from "../../interfaces/IModuleManager.sol"; /// @title ECSDA Validation /// @author ERC-6900 Authors @@ -31,7 +31,7 @@ import {PluginEntity} from "../../interfaces/IPluginManager.sol"; /// to validate partially or fully. contract SingleSignerValidation is ISingleSignerValidation, BaseModule { using ECDSA for bytes32; - using PluginEntityLib for PluginEntity; + using ModuleEntityLib for ModuleEntity; using MessageHashUtils for bytes32; string internal constant _NAME = "SingleSigner Validation"; @@ -156,9 +156,9 @@ contract SingleSignerValidation is ISingleSignerValidation, BaseModule { bytes memory immutables = LibClone.argsOnERC1967(account); // TODO: Add length check to prevent casting empty bytes, maybe? - PluginEntity validation = PluginEntity.wrap(bytes24(immutables)); + ModuleEntity validation = ModuleEntity.wrap(bytes24(immutables)); - if (!validation.eq(PluginEntityLib.pack(address(this), entityId))) { + if (!validation.eq(ModuleEntityLib.pack(address(this), entityId))) { revert("Validation incorrect"); } diff --git a/test/account/ImmutableAppend.t.sol b/test/account/ImmutableAppend.t.sol index f9b6c86d..a2d4f4e7 100644 --- a/test/account/ImmutableAppend.t.sol +++ b/test/account/ImmutableAppend.t.sol @@ -2,11 +2,11 @@ pragma solidity ^0.8.19; import {IEntryPoint, UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; +import {ModuleEntity, ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {ValidationConfig, ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; import {Call, IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; -import {DirectCallPlugin} from "../mocks/plugins/DirectCallPlugin.sol"; +import {DirectCallModule} from "../mocks/modules/DirectCallModule.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; @@ -26,7 +26,7 @@ contract ImmutableAppendTest is AccountTestBase { function test_success_getData() public { bytes memory expectedArgs = abi.encodePacked( - PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), + ModuleEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), singleSignerValidation.signerOf(TEST_DEFAULT_VALIDATION_ENTITY_ID, address(account1)) ); diff --git a/test/mocks/SingleSignerFactoryFixture.sol b/test/mocks/SingleSignerFactoryFixture.sol index 27a6f2f0..eec2ab7d 100644 --- a/test/mocks/SingleSignerFactoryFixture.sol +++ b/test/mocks/SingleSignerFactoryFixture.sol @@ -78,7 +78,7 @@ contract SingleSignerFactoryFixture is OptimizedTest { function _getImmutableArgs(address owner) private view returns (bytes memory) { return abi.encodePacked( - PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), + ModuleEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), owner ); } From cc6620129813ab96b09a0f8cbc7b2b5c7a521344 Mon Sep 17 00:00:00 2001 From: zer0dot Date: Thu, 25 Jul 2024 18:43:10 -0400 Subject: [PATCH 068/111] chore: fix comment naming --- src/account/UpgradeableModularAccount.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 3db9bbda..9cd693cb 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -755,7 +755,7 @@ contract UpgradeableModularAccount is function _getAppendedValidation() internal view returns (ModuleEntity) { // Get only the 24 first bytes of appended data bytes memory appendedData = LibClone.argsOnERC1967(address(this), 0, 24); - // Appended bytecode is under the format abi.encodePacked(pluginEntity, any...) + // Appended bytecode is under the format abi.encodePacked(moduleEntity, any...) // Validations will then decode this arbitrary data for whatever information they need // bytecode-appended validation. if (appendedData.length > 0) { From f743d2a35a18b84908490c533b80498fc2ed6809 Mon Sep 17 00:00:00 2001 From: howydev <132113803+howydev@users.noreply.github.com> Date: Wed, 12 Jun 2024 16:04:58 -0400 Subject: [PATCH 069/111] feat: add native token spend limit plugin --- src/plugins/NativeTokenLimitPlugin.sol | 152 +++++++++++++++++ test/plugin/NativeTokenLimitPlugin.t.sol | 206 +++++++++++++++++++++++ 2 files changed, 358 insertions(+) create mode 100644 src/plugins/NativeTokenLimitPlugin.sol create mode 100644 test/plugin/NativeTokenLimitPlugin.t.sol diff --git a/src/plugins/NativeTokenLimitPlugin.sol b/src/plugins/NativeTokenLimitPlugin.sol new file mode 100644 index 00000000..5127f18a --- /dev/null +++ b/src/plugins/NativeTokenLimitPlugin.sol @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.25; + +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; +import {UserOperationLib} from "@eth-infinitism/account-abstraction/core/UserOperationLib.sol"; +import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import {IAccountExecute} from "@eth-infinitism/account-abstraction/interfaces/IAccountExecute.sol"; + +import {PluginManifest, PluginMetadata} from "../interfaces/IPlugin.sol"; +import {IStandardExecutor, Call} from "../interfaces/IStandardExecutor.sol"; +import {IPlugin} from "../interfaces/IPlugin.sol"; +import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; +import {IValidationHook} from "../interfaces/IValidationHook.sol"; +import {BasePlugin, IERC165} from "./BasePlugin.sol"; + +/// @title Native Token Limit Plugin +/// @author ERC-6900 Authors +/// @notice This plugin supports a single total native token spend limit. +/// It tracks a total spend limit across UserOperation gas limits and native token transfers. +/// If a paymaster is used, UO gas would not cause the limit to decrease. +contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { + using UserOperationLib for PackedUserOperation; + using EnumerableSet for EnumerableSet.Bytes32Set; + + string public constant NAME = "Native Token Limit"; + string public constant VERSION = "1.0.0"; + string public constant AUTHOR = "ERC-6900 Authors"; + + mapping(address account => uint256[] limits) public limits; + + error ExceededNativeTokenLimit(); + error ExceededNumberOfEntities(); + + function updateLimits(uint8 functionId, uint256 newLimit) external { + limits[msg.sender][functionId] = newLimit; + } + + /// @inheritdoc IExecutionHook + function preExecutionHook(uint8 functionId, bytes calldata data) external override returns (bytes memory) { + bytes calldata callData; + bytes4 execSelector; + + // TODO: plugins should never have to do these gymnastics + execSelector = bytes4(data[52:56]); + if (execSelector == IAccountExecute.executeUserOp.selector) { + callData = data[56:]; + execSelector = bytes4(callData); + } else { + callData = data[52:]; + } + + uint256 value; + // Get value being sent + if (execSelector == IStandardExecutor.execute.selector) { + value = uint256(bytes32(callData[36:68])); + } else if (execSelector == IStandardExecutor.executeBatch.selector) { + Call[] memory calls = abi.decode(callData[4:], (Call[])); + for (uint256 i = 0; i < calls.length; i++) { + value += calls[i].value; + } + } + + uint256 limit = limits[msg.sender][functionId]; + if (value > limit) { + revert ExceededNativeTokenLimit(); + } + limits[msg.sender][functionId] = limit - value; + + return ""; + } + + /// @inheritdoc IExecutionHook + function postExecutionHook(uint8, bytes calldata) external pure override { + revert NotImplemented(); + } + + // No implementation, no revert + // Runtime spends no account gas, and we check native token spend limits in exec hooks + function preRuntimeValidationHook(uint8 functionId, address, uint256, bytes calldata) external pure override { + // silence warnings + (functionId); + } + + /// @inheritdoc IValidationHook + function preUserOpValidationHook(uint8 functionId, PackedUserOperation calldata userOp, bytes32) + external + returns (uint256) + { + // Decrease limit only if no paymaster is used + if (userOp.paymasterAndData.length == 0) { + uint256 vgl = UserOperationLib.unpackVerificationGasLimit(userOp); + uint256 cgl = UserOperationLib.unpackCallGasLimit(userOp); + uint256 totalGas = userOp.preVerificationGas + vgl + cgl; + uint256 usage = totalGas * UserOperationLib.unpackMaxFeePerGas(userOp); + + uint256 limit = limits[msg.sender][functionId]; + if (usage > limit) { + revert ExceededNativeTokenLimit(); + } + limits[msg.sender][functionId] = limit - usage; + } + return 0; + } + + /// @inheritdoc IPlugin + function onInstall(bytes calldata data) external override { + uint256[] memory spendLimits = abi.decode(data, (uint256[])); + + for (uint256 i = 0; i < spendLimits.length; i++) { + limits[msg.sender].push(spendLimits[i]); + } + + if (limits[msg.sender].length > type(uint8).max) { + revert ExceededNumberOfEntities(); + } + } + + /// @inheritdoc IPlugin + function onUninstall(bytes calldata data) external override { + uint8 functionId = abi.decode(data, (uint8)); + delete limits[msg.sender][functionId]; + } + + /// @inheritdoc IPlugin + function pluginManifest() external pure override returns (PluginManifest memory) { + // silence warnings + PluginManifest memory manifest; + return manifest; + } + + /// @inheritdoc IPlugin + function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { + PluginMetadata memory metadata; + metadata.name = NAME; + metadata.version = VERSION; + metadata.author = AUTHOR; + + metadata.permissionRequest = new string[](2); + metadata.permissionRequest[0] = "native-token-limit"; + metadata.permissionRequest[1] = "gas-limit"; + return metadata; + } + + // ┏━━━━━━━━━━━━━━━┓ + // ┃ EIP-165 ┃ + // ┗━━━━━━━━━━━━━━━┛ + + /// @inheritdoc BasePlugin + function supportsInterface(bytes4 interfaceId) public view override(BasePlugin, IERC165) returns (bool) { + return super.supportsInterface(interfaceId); + } +} diff --git a/test/plugin/NativeTokenLimitPlugin.t.sol b/test/plugin/NativeTokenLimitPlugin.t.sol new file mode 100644 index 00000000..4284d36e --- /dev/null +++ b/test/plugin/NativeTokenLimitPlugin.t.sol @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.sol"; +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; + +import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; +import {FunctionReference} from "../../src/helpers/FunctionReferenceLib.sol"; +import {NativeTokenLimitPlugin} from "../../src/plugins/NativeTokenLimitPlugin.sol"; +import {MockPlugin} from "../mocks/MockPlugin.sol"; +import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; +import {FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; +import {IStandardExecutor, Call} from "../../src/interfaces/IStandardExecutor.sol"; +import {PluginManifest} from "../../src/interfaces/IPlugin.sol"; + +import {MSCAFactoryFixture} from "../mocks/MSCAFactoryFixture.sol"; +import {OptimizedTest} from "../utils/OptimizedTest.sol"; + +contract NativeTokenLimitPluginTest is OptimizedTest { + EntryPoint public entryPoint = new EntryPoint(); + address public recipient = address(1); + address payable public bundler = payable(address(2)); + PluginManifest internal _m; + MockPlugin public validationPlugin = new MockPlugin(_m); + FunctionReference public validationFunction; + + UpgradeableModularAccount public acct; + NativeTokenLimitPlugin public plugin = new NativeTokenLimitPlugin(); + uint256 public spendLimit = 10 ether; + + function setUp() public { + // Set up a validator with hooks from the gas spend limit plugin attached + + MSCAFactoryFixture factory = new MSCAFactoryFixture(entryPoint, _deploySingleOwnerPlugin()); + + acct = factory.createAccount(address(this), 0); + + vm.deal(address(acct), 10 ether); + + FunctionReference[] memory preValidationHooks = new FunctionReference[](1); + preValidationHooks[0] = FunctionReferenceLib.pack(address(plugin), 0); + + ExecutionHook[] memory permissionHooks = new ExecutionHook[](1); + permissionHooks[0] = ExecutionHook({ + hookFunction: FunctionReferenceLib.pack(address(plugin), 0), + isPreHook: true, + isPostHook: false, + requireUOContext: false + }); + + uint256[] memory spendLimits = new uint256[](1); + spendLimits[0] = spendLimit; + + bytes[] memory preValHooksInitDatas = new bytes[](1); + preValHooksInitDatas[0] = ""; + + bytes[] memory permissionInitDatas = new bytes[](1); + permissionInitDatas[0] = abi.encode(spendLimits); + + vm.prank(address(acct)); + acct.installValidation( + FunctionReferenceLib.pack(address(validationPlugin), 0), + true, + new bytes4[](0), + new bytes(0), + abi.encode(preValidationHooks, preValHooksInitDatas), + abi.encode(permissionHooks, permissionInitDatas) + ); + + validationFunction = FunctionReferenceLib.pack(address(validationPlugin), 0); + } + + function _getExecuteWithValue(uint256 value) internal view returns (bytes memory) { + return abi.encodeCall(UpgradeableModularAccount.execute, (recipient, value, "")); + } + + function _getPackedUO(uint256 gas1, uint256 gas2, uint256 gas3, uint256 gasPrice, bytes memory callData) + internal + view + returns (PackedUserOperation memory uo) + { + uo = PackedUserOperation({ + sender: address(acct), + nonce: 0, + initCode: "", + callData: abi.encodePacked(UpgradeableModularAccount.executeUserOp.selector, callData), + accountGasLimits: bytes32(bytes16(uint128(gas1))) | bytes32(uint256(gas2)), + preVerificationGas: gas3, + gasFees: bytes32(uint256(uint128(gasPrice))), + paymasterAndData: "", + signature: abi.encodePacked(FunctionReferenceLib.pack(address(validationPlugin), 0), uint8(1)) + }); + } + + function test_userOp_gasLimit() public { + vm.startPrank(address(entryPoint)); + + // uses 10e - 200000 of gas + assertEq(plugin.limits(address(acct), 0), 10 ether); + uint256 result = acct.validateUserOp( + _getPackedUO(100000, 100000, 10 ether - 400000, 1, _getExecuteWithValue(0)), bytes32(0), 0 + ); + assertEq(plugin.limits(address(acct), 0), 200000); + + uint256 expected = uint256(type(uint48).max) << 160; + assertEq(result, expected); + + // uses 200k + 1 wei of gas + vm.expectRevert(NativeTokenLimitPlugin.ExceededNativeTokenLimit.selector); + result = acct.validateUserOp(_getPackedUO(100000, 100000, 1, 1, _getExecuteWithValue(0)), bytes32(0), 0); + } + + function test_userOp_executeLimit() public { + vm.startPrank(address(entryPoint)); + + // uses 5e of native tokens + assertEq(plugin.limits(address(acct), 0), 10 ether); + acct.executeUserOp(_getPackedUO(0, 0, 0, 0, _getExecuteWithValue(5 ether)), bytes32(0)); + assertEq(plugin.limits(address(acct), 0), 5 ether); + + // uses 5e + 1wei of native tokens + vm.expectRevert( + abi.encodePacked( + UpgradeableModularAccount.PreExecHookReverted.selector, + abi.encode( + address(plugin), + uint8(0), + abi.encodePacked(NativeTokenLimitPlugin.ExceededNativeTokenLimit.selector) + ) + ) + ); + acct.executeUserOp(_getPackedUO(0, 0, 0, 0, _getExecuteWithValue(5 ether + 1)), bytes32(0)); + } + + function test_userOp_executeBatchLimit() public { + Call[] memory calls = new Call[](3); + calls[0] = Call({target: recipient, value: 1, data: ""}); + calls[1] = Call({target: recipient, value: 1 ether, data: ""}); + calls[2] = Call({target: recipient, value: 5 ether + 100000, data: ""}); + + vm.startPrank(address(entryPoint)); + assertEq(plugin.limits(address(acct), 0), 10 ether); + acct.executeUserOp( + _getPackedUO(0, 0, 0, 0, abi.encodeCall(IStandardExecutor.executeBatch, (calls))), bytes32(0) + ); + assertEq(plugin.limits(address(acct), 0), 10 ether - 6 ether - 100001); + assertEq(recipient.balance, 6 ether + 100001); + } + + function test_userOp_combinedExecLimit_success() public { + assertEq(plugin.limits(address(acct), 0), 10 ether); + PackedUserOperation[] memory uos = new PackedUserOperation[](1); + uos[0] = _getPackedUO(100000, 100000, 100000, 1, _getExecuteWithValue(5 ether)); + entryPoint.handleOps(uos, bundler); + + assertEq(plugin.limits(address(acct), 0), 5 ether - 300000); + assertEq(recipient.balance, 5 ether); + } + + function test_userOp_combinedExecBatchLimit_success() public { + Call[] memory calls = new Call[](3); + calls[0] = Call({target: recipient, value: 1, data: ""}); + calls[1] = Call({target: recipient, value: 1 ether, data: ""}); + calls[2] = Call({target: recipient, value: 5 ether + 100000, data: ""}); + + vm.startPrank(address(entryPoint)); + assertEq(plugin.limits(address(acct), 0), 10 ether); + PackedUserOperation[] memory uos = new PackedUserOperation[](1); + uos[0] = _getPackedUO(200000, 200000, 200000, 1, abi.encodeCall(IStandardExecutor.executeBatch, (calls))); + entryPoint.handleOps(uos, bundler); + + assertEq(plugin.limits(address(acct), 0), 10 ether - 6 ether - 700001); + assertEq(recipient.balance, 6 ether + 100001); + } + + function test_userOp_combinedExecLimit_failExec() public { + assertEq(plugin.limits(address(acct), 0), 10 ether); + PackedUserOperation[] memory uos = new PackedUserOperation[](1); + uos[0] = _getPackedUO(100000, 100000, 100000, 1, _getExecuteWithValue(10 ether)); + entryPoint.handleOps(uos, bundler); + + assertEq(plugin.limits(address(acct), 0), 10 ether - 300000); + assertEq(recipient.balance, 0); + } + + function test_runtime_executeLimit() public { + assertEq(plugin.limits(address(acct), 0), 10 ether); + acct.executeWithAuthorization( + _getExecuteWithValue(5 ether), abi.encodePacked(validationFunction, uint8(1)) + ); + assertEq(plugin.limits(address(acct), 0), 5 ether); + } + + function test_runtime_executeBatchLimit() public { + Call[] memory calls = new Call[](3); + calls[0] = Call({target: recipient, value: 1, data: ""}); + calls[1] = Call({target: recipient, value: 1 ether, data: ""}); + calls[2] = Call({target: recipient, value: 5 ether + 100000, data: ""}); + + assertEq(plugin.limits(address(acct), 0), 10 ether); + acct.executeWithAuthorization( + abi.encodeCall(IStandardExecutor.executeBatch, (calls)), abi.encodePacked(validationFunction, uint8(1)) + ); + assertEq(plugin.limits(address(acct), 0), 4 ether - 100001); + } +} From 3476930fa205e0c51e2998294cc78bb0762ea589 Mon Sep 17 00:00:00 2001 From: howydev <132113803+howydev@users.noreply.github.com> Date: Thu, 27 Jun 2024 15:23:25 -0400 Subject: [PATCH 070/111] chore: update --- src/plugins/NativeTokenLimitPlugin.sol | 37 ++++++++++++++---------- test/plugin/NativeTokenLimitPlugin.t.sol | 3 +- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/plugins/NativeTokenLimitPlugin.sol b/src/plugins/NativeTokenLimitPlugin.sol index 5127f18a..19a192d3 100644 --- a/src/plugins/NativeTokenLimitPlugin.sol +++ b/src/plugins/NativeTokenLimitPlugin.sol @@ -4,12 +4,12 @@ pragma solidity ^0.8.25; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {UserOperationLib} from "@eth-infinitism/account-abstraction/core/UserOperationLib.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import {IAccountExecute} from "@eth-infinitism/account-abstraction/interfaces/IAccountExecute.sol"; import {PluginManifest, PluginMetadata} from "../interfaces/IPlugin.sol"; import {IStandardExecutor, Call} from "../interfaces/IStandardExecutor.sol"; import {IPlugin} from "../interfaces/IPlugin.sol"; import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; +import {IPermissionHook} from "../interfaces/IPermissionHook.sol"; import {IValidationHook} from "../interfaces/IValidationHook.sol"; import {BasePlugin, IERC165} from "./BasePlugin.sol"; @@ -18,7 +18,7 @@ import {BasePlugin, IERC165} from "./BasePlugin.sol"; /// @notice This plugin supports a single total native token spend limit. /// It tracks a total spend limit across UserOperation gas limits and native token transfers. /// If a paymaster is used, UO gas would not cause the limit to decrease. -contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { +contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IPermissionHook, IValidationHook { using UserOperationLib for PackedUserOperation; using EnumerableSet for EnumerableSet.Bytes32Set; @@ -36,25 +36,30 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { } /// @inheritdoc IExecutionHook - function preExecutionHook(uint8 functionId, bytes calldata data) external override returns (bytes memory) { - bytes calldata callData; - bytes4 execSelector; - - // TODO: plugins should never have to do these gymnastics - execSelector = bytes4(data[52:56]); - if (execSelector == IAccountExecute.executeUserOp.selector) { - callData = data[56:]; - execSelector = bytes4(callData); - } else { - callData = data[52:]; - } + function preExecutionHook(uint8 functionId, address, uint256, bytes calldata data) + external + override + returns (bytes memory) + { + return _checkAndDecrementLimit(functionId, data); + } + + function preUserOpExecutionHook(uint8 functionId, PackedUserOperation calldata uo) + external + override + returns (bytes memory) + { + return _checkAndDecrementLimit(functionId, uo.callData); + } + function _checkAndDecrementLimit(uint8 functionId, bytes calldata data) internal returns (bytes memory) { + bytes4 execSelector = bytes4(data); uint256 value; // Get value being sent if (execSelector == IStandardExecutor.execute.selector) { - value = uint256(bytes32(callData[36:68])); + value = uint256(bytes32(data[36:68])); } else if (execSelector == IStandardExecutor.executeBatch.selector) { - Call[] memory calls = abi.decode(callData[4:], (Call[])); + Call[] memory calls = abi.decode(data[4:], (Call[])); for (uint256 i = 0; i < calls.length; i++) { value += calls[i].value; } diff --git a/test/plugin/NativeTokenLimitPlugin.t.sol b/test/plugin/NativeTokenLimitPlugin.t.sol index 4284d36e..81b8f7ea 100644 --- a/test/plugin/NativeTokenLimitPlugin.t.sol +++ b/test/plugin/NativeTokenLimitPlugin.t.sol @@ -44,8 +44,7 @@ contract NativeTokenLimitPluginTest is OptimizedTest { permissionHooks[0] = ExecutionHook({ hookFunction: FunctionReferenceLib.pack(address(plugin), 0), isPreHook: true, - isPostHook: false, - requireUOContext: false + isPostHook: false }); uint256[] memory spendLimits = new uint256[](1); From 4a1302d3a1e1c7d4e27bff48d170b593d079b7ea Mon Sep 17 00:00:00 2001 From: howydev <132113803+howydev@users.noreply.github.com> Date: Thu, 27 Jun 2024 15:42:51 -0400 Subject: [PATCH 071/111] fix: tests --- test/plugin/NativeTokenLimitPlugin.t.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/plugin/NativeTokenLimitPlugin.t.sol b/test/plugin/NativeTokenLimitPlugin.t.sol index 81b8f7ea..07a554d2 100644 --- a/test/plugin/NativeTokenLimitPlugin.t.sol +++ b/test/plugin/NativeTokenLimitPlugin.t.sol @@ -149,10 +149,10 @@ contract NativeTokenLimitPluginTest is OptimizedTest { function test_userOp_combinedExecLimit_success() public { assertEq(plugin.limits(address(acct), 0), 10 ether); PackedUserOperation[] memory uos = new PackedUserOperation[](1); - uos[0] = _getPackedUO(100000, 100000, 100000, 1, _getExecuteWithValue(5 ether)); + uos[0] = _getPackedUO(200000, 200000, 200000, 1, _getExecuteWithValue(5 ether)); entryPoint.handleOps(uos, bundler); - assertEq(plugin.limits(address(acct), 0), 5 ether - 300000); + assertEq(plugin.limits(address(acct), 0), 5 ether - 600000); assertEq(recipient.balance, 5 ether); } @@ -175,10 +175,10 @@ contract NativeTokenLimitPluginTest is OptimizedTest { function test_userOp_combinedExecLimit_failExec() public { assertEq(plugin.limits(address(acct), 0), 10 ether); PackedUserOperation[] memory uos = new PackedUserOperation[](1); - uos[0] = _getPackedUO(100000, 100000, 100000, 1, _getExecuteWithValue(10 ether)); + uos[0] = _getPackedUO(200000, 200000, 200000, 1, _getExecuteWithValue(10 ether)); entryPoint.handleOps(uos, bundler); - assertEq(plugin.limits(address(acct), 0), 10 ether - 300000); + assertEq(plugin.limits(address(acct), 0), 10 ether - 600000); assertEq(recipient.balance, 0); } From 797c4ef8cf14e3ef857678336563f5ad9ba1d84f Mon Sep 17 00:00:00 2001 From: howydev <132113803+howydev@users.noreply.github.com> Date: Fri, 28 Jun 2024 16:50:04 -0400 Subject: [PATCH 072/111] chore: remove permission hooks, add new internal helper function for devX, fix lint --- src/plugins/BasePlugin.sol | 24 +++++++ src/plugins/NativeTokenLimitPlugin.sol | 99 ++++++++++++-------------- 2 files changed, 70 insertions(+), 53 deletions(-) diff --git a/src/plugins/BasePlugin.sol b/src/plugins/BasePlugin.sol index 3b0fd521..18345b14 100644 --- a/src/plugins/BasePlugin.sol +++ b/src/plugins/BasePlugin.sol @@ -2,6 +2,8 @@ pragma solidity ^0.8.25; import {ERC165, IERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +import {IAccountExecute} from "@eth-infinitism/account-abstraction/interfaces/IAccountExecute.sol"; +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {IPlugin} from "../interfaces/IPlugin.sol"; @@ -27,4 +29,26 @@ abstract contract BasePlugin is ERC165, IPlugin { function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { return interfaceId == type(IPlugin).interfaceId || super.supportsInterface(interfaceId); } + + function _getSelectorAndCalldata(bytes calldata data) internal view returns (bytes4, bytes memory) { + if (bytes4(data[:4]) == IAccountExecute.executeUserOp.selector) { + (PackedUserOperation memory uo,) = abi.decode(data[4:], (PackedUserOperation, bytes32)); + bytes4 selector; + bytes memory callData = uo.callData; + // Bytes arr representation: [bytes32(len), bytes4(executeUserOp.selector), bytes4(actualSelector), + // bytes(actualCallData)] + // 1. Copy actualSelector into a new var + // 2. Shorten bytes arry by 8 by: store length - 8 into the new pointer location + // 3. Move the callData pointer by 8 + assembly { + selector := mload(add(callData, 36)) + + let len := mload(callData) + mstore(add(callData, 8), sub(len, 8)) + callData := add(callData, 8) + } + return (selector, callData); + } + return (bytes4(data[:4]), data[4:]); + } } diff --git a/src/plugins/NativeTokenLimitPlugin.sol b/src/plugins/NativeTokenLimitPlugin.sol index 19a192d3..5fd806d6 100644 --- a/src/plugins/NativeTokenLimitPlugin.sol +++ b/src/plugins/NativeTokenLimitPlugin.sol @@ -9,7 +9,6 @@ import {PluginManifest, PluginMetadata} from "../interfaces/IPlugin.sol"; import {IStandardExecutor, Call} from "../interfaces/IStandardExecutor.sol"; import {IPlugin} from "../interfaces/IPlugin.sol"; import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; -import {IPermissionHook} from "../interfaces/IPermissionHook.sol"; import {IValidationHook} from "../interfaces/IValidationHook.sol"; import {BasePlugin, IERC165} from "./BasePlugin.sol"; @@ -18,7 +17,8 @@ import {BasePlugin, IERC165} from "./BasePlugin.sol"; /// @notice This plugin supports a single total native token spend limit. /// It tracks a total spend limit across UserOperation gas limits and native token transfers. /// If a paymaster is used, UO gas would not cause the limit to decrease. -contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IPermissionHook, IValidationHook { + +contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { using UserOperationLib for PackedUserOperation; using EnumerableSet for EnumerableSet.Bytes32Set; @@ -35,57 +35,6 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IPermissionHook, limits[msg.sender][functionId] = newLimit; } - /// @inheritdoc IExecutionHook - function preExecutionHook(uint8 functionId, address, uint256, bytes calldata data) - external - override - returns (bytes memory) - { - return _checkAndDecrementLimit(functionId, data); - } - - function preUserOpExecutionHook(uint8 functionId, PackedUserOperation calldata uo) - external - override - returns (bytes memory) - { - return _checkAndDecrementLimit(functionId, uo.callData); - } - - function _checkAndDecrementLimit(uint8 functionId, bytes calldata data) internal returns (bytes memory) { - bytes4 execSelector = bytes4(data); - uint256 value; - // Get value being sent - if (execSelector == IStandardExecutor.execute.selector) { - value = uint256(bytes32(data[36:68])); - } else if (execSelector == IStandardExecutor.executeBatch.selector) { - Call[] memory calls = abi.decode(data[4:], (Call[])); - for (uint256 i = 0; i < calls.length; i++) { - value += calls[i].value; - } - } - - uint256 limit = limits[msg.sender][functionId]; - if (value > limit) { - revert ExceededNativeTokenLimit(); - } - limits[msg.sender][functionId] = limit - value; - - return ""; - } - - /// @inheritdoc IExecutionHook - function postExecutionHook(uint8, bytes calldata) external pure override { - revert NotImplemented(); - } - - // No implementation, no revert - // Runtime spends no account gas, and we check native token spend limits in exec hooks - function preRuntimeValidationHook(uint8 functionId, address, uint256, bytes calldata) external pure override { - // silence warnings - (functionId); - } - /// @inheritdoc IValidationHook function preUserOpValidationHook(uint8 functionId, PackedUserOperation calldata userOp, bytes32) external @@ -107,6 +56,15 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IPermissionHook, return 0; } + /// @inheritdoc IExecutionHook + function preExecutionHook(uint8 functionId, address, uint256, bytes calldata data) + external + override + returns (bytes memory) + { + return _checkAndDecrementLimit(functionId, data); + } + /// @inheritdoc IPlugin function onInstall(bytes calldata data) external override { uint256[] memory spendLimits = abi.decode(data, (uint256[])); @@ -126,6 +84,18 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IPermissionHook, delete limits[msg.sender][functionId]; } + /// @inheritdoc IExecutionHook + function postExecutionHook(uint8, bytes calldata) external pure override { + revert NotImplemented(); + } + + // No implementation, no revert + // Runtime spends no account gas, and we check native token spend limits in exec hooks + function preRuntimeValidationHook(uint8 functionId, address, uint256, bytes calldata) external pure override { + // silence warnings + (functionId); + } + /// @inheritdoc IPlugin function pluginManifest() external pure override returns (PluginManifest memory) { // silence warnings @@ -154,4 +124,27 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IPermissionHook, function supportsInterface(bytes4 interfaceId) public view override(BasePlugin, IERC165) returns (bool) { return super.supportsInterface(interfaceId); } + + function _checkAndDecrementLimit(uint8 functionId, bytes calldata data) internal returns (bytes memory) { + (bytes4 selector, bytes memory callData) = _getSelectorAndCalldata(data); + + uint256 value; + // Get value being sent + if (selector == IStandardExecutor.execute.selector) { + (, value) = abi.decode(callData, (address, uint256)); + } else if (selector == IStandardExecutor.executeBatch.selector) { + Call[] memory calls = abi.decode(callData, (Call[])); + for (uint256 i = 0; i < calls.length; i++) { + value += calls[i].value; + } + } + + uint256 limit = limits[msg.sender][functionId]; + if (value > limit) { + revert ExceededNativeTokenLimit(); + } + limits[msg.sender][functionId] = limit - value; + + return ""; + } } From 29853e21a5eb59d30e90c7fb93f236d203e1a107 Mon Sep 17 00:00:00 2001 From: howydev <132113803+howydev@users.noreply.github.com> Date: Mon, 8 Jul 2024 13:34:38 -0400 Subject: [PATCH 073/111] fix: move to associated storage --- src/plugins/NativeTokenLimitPlugin.sol | 27 ++++++++++--------- test/plugin/NativeTokenLimitPlugin.t.sol | 34 ++++++++++++------------ 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/src/plugins/NativeTokenLimitPlugin.sol b/src/plugins/NativeTokenLimitPlugin.sol index 5fd806d6..984b373b 100644 --- a/src/plugins/NativeTokenLimitPlugin.sol +++ b/src/plugins/NativeTokenLimitPlugin.sol @@ -26,13 +26,13 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { string public constant VERSION = "1.0.0"; string public constant AUTHOR = "ERC-6900 Authors"; - mapping(address account => uint256[] limits) public limits; + mapping(uint256 funcIds => mapping(address account => uint256 limit)) public limits; error ExceededNativeTokenLimit(); error ExceededNumberOfEntities(); function updateLimits(uint8 functionId, uint256 newLimit) external { - limits[msg.sender][functionId] = newLimit; + limits[functionId][msg.sender] = newLimit; } /// @inheritdoc IValidationHook @@ -47,11 +47,11 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { uint256 totalGas = userOp.preVerificationGas + vgl + cgl; uint256 usage = totalGas * UserOperationLib.unpackMaxFeePerGas(userOp); - uint256 limit = limits[msg.sender][functionId]; + uint256 limit = limits[functionId][msg.sender]; if (usage > limit) { revert ExceededNativeTokenLimit(); } - limits[msg.sender][functionId] = limit - usage; + limits[functionId][msg.sender] = limit - usage; } return 0; } @@ -67,21 +67,24 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { /// @inheritdoc IPlugin function onInstall(bytes calldata data) external override { - uint256[] memory spendLimits = abi.decode(data, (uint256[])); + (uint8 startFunctionId, uint256[] memory spendLimits) = abi.decode(data, (uint8, uint256[])); - for (uint256 i = 0; i < spendLimits.length; i++) { - limits[msg.sender].push(spendLimits[i]); + if (startFunctionId + spendLimits.length > type(uint8).max) { + revert ExceededNumberOfEntities(); } - if (limits[msg.sender].length > type(uint8).max) { - revert ExceededNumberOfEntities(); + for (uint256 i = 0; i < spendLimits.length; i++) { + limits[i + startFunctionId][msg.sender] = spendLimits[i]; } } /// @inheritdoc IPlugin function onUninstall(bytes calldata data) external override { + // This is the highest functionId that's being used by the account uint8 functionId = abi.decode(data, (uint8)); - delete limits[msg.sender][functionId]; + for (uint256 i = 0; i < functionId; i++) { + delete limits[i][msg.sender]; + } } /// @inheritdoc IExecutionHook @@ -139,11 +142,11 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { } } - uint256 limit = limits[msg.sender][functionId]; + uint256 limit = limits[functionId][msg.sender]; if (value > limit) { revert ExceededNativeTokenLimit(); } - limits[msg.sender][functionId] = limit - value; + limits[functionId][msg.sender] = limit - value; return ""; } diff --git a/test/plugin/NativeTokenLimitPlugin.t.sol b/test/plugin/NativeTokenLimitPlugin.t.sol index 07a554d2..b8072238 100644 --- a/test/plugin/NativeTokenLimitPlugin.t.sol +++ b/test/plugin/NativeTokenLimitPlugin.t.sol @@ -54,7 +54,7 @@ contract NativeTokenLimitPluginTest is OptimizedTest { preValHooksInitDatas[0] = ""; bytes[] memory permissionInitDatas = new bytes[](1); - permissionInitDatas[0] = abi.encode(spendLimits); + permissionInitDatas[0] = abi.encode(0, spendLimits); vm.prank(address(acct)); acct.installValidation( @@ -95,11 +95,11 @@ contract NativeTokenLimitPluginTest is OptimizedTest { vm.startPrank(address(entryPoint)); // uses 10e - 200000 of gas - assertEq(plugin.limits(address(acct), 0), 10 ether); + assertEq(plugin.limits(0, address(acct)), 10 ether); uint256 result = acct.validateUserOp( _getPackedUO(100000, 100000, 10 ether - 400000, 1, _getExecuteWithValue(0)), bytes32(0), 0 ); - assertEq(plugin.limits(address(acct), 0), 200000); + assertEq(plugin.limits(0, address(acct)), 200000); uint256 expected = uint256(type(uint48).max) << 160; assertEq(result, expected); @@ -113,9 +113,9 @@ contract NativeTokenLimitPluginTest is OptimizedTest { vm.startPrank(address(entryPoint)); // uses 5e of native tokens - assertEq(plugin.limits(address(acct), 0), 10 ether); + assertEq(plugin.limits(0, address(acct)), 10 ether); acct.executeUserOp(_getPackedUO(0, 0, 0, 0, _getExecuteWithValue(5 ether)), bytes32(0)); - assertEq(plugin.limits(address(acct), 0), 5 ether); + assertEq(plugin.limits(0, address(acct)), 5 ether); // uses 5e + 1wei of native tokens vm.expectRevert( @@ -138,21 +138,21 @@ contract NativeTokenLimitPluginTest is OptimizedTest { calls[2] = Call({target: recipient, value: 5 ether + 100000, data: ""}); vm.startPrank(address(entryPoint)); - assertEq(plugin.limits(address(acct), 0), 10 ether); + assertEq(plugin.limits(0, address(acct)), 10 ether); acct.executeUserOp( _getPackedUO(0, 0, 0, 0, abi.encodeCall(IStandardExecutor.executeBatch, (calls))), bytes32(0) ); - assertEq(plugin.limits(address(acct), 0), 10 ether - 6 ether - 100001); + assertEq(plugin.limits(0, address(acct)), 10 ether - 6 ether - 100001); assertEq(recipient.balance, 6 ether + 100001); } function test_userOp_combinedExecLimit_success() public { - assertEq(plugin.limits(address(acct), 0), 10 ether); + assertEq(plugin.limits(0, address(acct)), 10 ether); PackedUserOperation[] memory uos = new PackedUserOperation[](1); uos[0] = _getPackedUO(200000, 200000, 200000, 1, _getExecuteWithValue(5 ether)); entryPoint.handleOps(uos, bundler); - assertEq(plugin.limits(address(acct), 0), 5 ether - 600000); + assertEq(plugin.limits(0, address(acct)), 5 ether - 600000); assertEq(recipient.balance, 5 ether); } @@ -163,31 +163,31 @@ contract NativeTokenLimitPluginTest is OptimizedTest { calls[2] = Call({target: recipient, value: 5 ether + 100000, data: ""}); vm.startPrank(address(entryPoint)); - assertEq(plugin.limits(address(acct), 0), 10 ether); + assertEq(plugin.limits(0, address(acct)), 10 ether); PackedUserOperation[] memory uos = new PackedUserOperation[](1); uos[0] = _getPackedUO(200000, 200000, 200000, 1, abi.encodeCall(IStandardExecutor.executeBatch, (calls))); entryPoint.handleOps(uos, bundler); - assertEq(plugin.limits(address(acct), 0), 10 ether - 6 ether - 700001); + assertEq(plugin.limits(0, address(acct)), 10 ether - 6 ether - 700001); assertEq(recipient.balance, 6 ether + 100001); } function test_userOp_combinedExecLimit_failExec() public { - assertEq(plugin.limits(address(acct), 0), 10 ether); + assertEq(plugin.limits(0, address(acct)), 10 ether); PackedUserOperation[] memory uos = new PackedUserOperation[](1); uos[0] = _getPackedUO(200000, 200000, 200000, 1, _getExecuteWithValue(10 ether)); entryPoint.handleOps(uos, bundler); - assertEq(plugin.limits(address(acct), 0), 10 ether - 600000); + assertEq(plugin.limits(0, address(acct)), 10 ether - 600000); assertEq(recipient.balance, 0); } function test_runtime_executeLimit() public { - assertEq(plugin.limits(address(acct), 0), 10 ether); + assertEq(plugin.limits(0, address(acct)), 10 ether); acct.executeWithAuthorization( _getExecuteWithValue(5 ether), abi.encodePacked(validationFunction, uint8(1)) ); - assertEq(plugin.limits(address(acct), 0), 5 ether); + assertEq(plugin.limits(0, address(acct)), 5 ether); } function test_runtime_executeBatchLimit() public { @@ -196,10 +196,10 @@ contract NativeTokenLimitPluginTest is OptimizedTest { calls[1] = Call({target: recipient, value: 1 ether, data: ""}); calls[2] = Call({target: recipient, value: 5 ether + 100000, data: ""}); - assertEq(plugin.limits(address(acct), 0), 10 ether); + assertEq(plugin.limits(0, address(acct)), 10 ether); acct.executeWithAuthorization( abi.encodeCall(IStandardExecutor.executeBatch, (calls)), abi.encodePacked(validationFunction, uint8(1)) ); - assertEq(plugin.limits(address(acct), 0), 4 ether - 100001); + assertEq(plugin.limits(0, address(acct)), 4 ether - 100001); } } From 834e2a490eb8fff869a6ca0bcd4616838afa05e1 Mon Sep 17 00:00:00 2001 From: howydev <132113803+howydev@users.noreply.github.com> Date: Mon, 8 Jul 2024 13:38:44 -0400 Subject: [PATCH 074/111] feat: add special paymaster list to still count towards limits --- src/plugins/BasePlugin.sol | 2 +- src/plugins/NativeTokenLimitPlugin.sol | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/plugins/BasePlugin.sol b/src/plugins/BasePlugin.sol index 18345b14..f5c8523b 100644 --- a/src/plugins/BasePlugin.sol +++ b/src/plugins/BasePlugin.sol @@ -30,7 +30,7 @@ abstract contract BasePlugin is ERC165, IPlugin { return interfaceId == type(IPlugin).interfaceId || super.supportsInterface(interfaceId); } - function _getSelectorAndCalldata(bytes calldata data) internal view returns (bytes4, bytes memory) { + function _getSelectorAndCalldata(bytes calldata data) internal pure returns (bytes4, bytes memory) { if (bytes4(data[:4]) == IAccountExecute.executeUserOp.selector) { (PackedUserOperation memory uo,) = abi.decode(data[4:], (PackedUserOperation, bytes32)); bytes4 selector; diff --git a/src/plugins/NativeTokenLimitPlugin.sol b/src/plugins/NativeTokenLimitPlugin.sol index 984b373b..777484ce 100644 --- a/src/plugins/NativeTokenLimitPlugin.sol +++ b/src/plugins/NativeTokenLimitPlugin.sol @@ -16,8 +16,8 @@ import {BasePlugin, IERC165} from "./BasePlugin.sol"; /// @author ERC-6900 Authors /// @notice This plugin supports a single total native token spend limit. /// It tracks a total spend limit across UserOperation gas limits and native token transfers. -/// If a paymaster is used, UO gas would not cause the limit to decrease. - +/// If a non whitelisted paymaster is used, UO gas would not cause the limit to decrease. +/// If a whitelisted paymaster is used, gas is still counted towards the limit contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { using UserOperationLib for PackedUserOperation; using EnumerableSet for EnumerableSet.Bytes32Set; @@ -27,6 +27,9 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { string public constant AUTHOR = "ERC-6900 Authors"; mapping(uint256 funcIds => mapping(address account => uint256 limit)) public limits; + // Accounts should add paymasters that still use the accounts tokens here + // E.g. ERC20 paymasters that pull funds from the account + mapping(address paymaster => mapping(address account => bool allowed)) public specialPaymasters; error ExceededNativeTokenLimit(); error ExceededNumberOfEntities(); @@ -35,13 +38,20 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { limits[functionId][msg.sender] = newLimit; } + function updateSpecialPaymaster(address paymaster, bool allowed) external { + specialPaymasters[paymaster][msg.sender] = allowed; + } + /// @inheritdoc IValidationHook function preUserOpValidationHook(uint8 functionId, PackedUserOperation calldata userOp, bytes32) external returns (uint256) { - // Decrease limit only if no paymaster is used - if (userOp.paymasterAndData.length == 0) { + // Decrease limit only if no paymaster is used, or if its a special paymaster + if ( + userOp.paymasterAndData.length == 0 + || specialPaymasters[address(bytes20(userOp.paymasterAndData[:20]))][msg.sender] + ) { uint256 vgl = UserOperationLib.unpackVerificationGasLimit(userOp); uint256 cgl = UserOperationLib.unpackCallGasLimit(userOp); uint256 totalGas = userOp.preVerificationGas + vgl + cgl; From b8595193c2b30987a613faaf38e7ab3bb10174db Mon Sep 17 00:00:00 2001 From: howydev <132113803+howydev@users.noreply.github.com> Date: Wed, 10 Jul 2024 13:57:13 -0400 Subject: [PATCH 075/111] c --- src/plugins/NativeTokenLimitPlugin.sol | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/plugins/NativeTokenLimitPlugin.sol b/src/plugins/NativeTokenLimitPlugin.sol index 777484ce..cb84a4ee 100644 --- a/src/plugins/NativeTokenLimitPlugin.sol +++ b/src/plugins/NativeTokenLimitPlugin.sol @@ -22,9 +22,9 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { using UserOperationLib for PackedUserOperation; using EnumerableSet for EnumerableSet.Bytes32Set; - string public constant NAME = "Native Token Limit"; - string public constant VERSION = "1.0.0"; - string public constant AUTHOR = "ERC-6900 Authors"; + string internal constant NAME = "Native Token Limit"; + string internal constant VERSION = "1.0.0"; + string internal constant AUTHOR = "ERC-6900 Authors"; mapping(uint256 funcIds => mapping(address account => uint256 limit)) public limits; // Accounts should add paymasters that still use the accounts tokens here @@ -104,10 +104,7 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { // No implementation, no revert // Runtime spends no account gas, and we check native token spend limits in exec hooks - function preRuntimeValidationHook(uint8 functionId, address, uint256, bytes calldata) external pure override { - // silence warnings - (functionId); - } + function preRuntimeValidationHook(uint8, address, uint256, bytes calldata) external pure override {} /// @inheritdoc IPlugin function pluginManifest() external pure override returns (PluginManifest memory) { @@ -135,7 +132,7 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { /// @inheritdoc BasePlugin function supportsInterface(bytes4 interfaceId) public view override(BasePlugin, IERC165) returns (bool) { - return super.supportsInterface(interfaceId); + return interfaceId == type(IExecutionHook).interfaceId || super.supportsInterface(interfaceId); } function _checkAndDecrementLimit(uint8 functionId, bytes calldata data) internal returns (bytes memory) { From 8862e91c8de63e2b7003feea81abef23228bbe7f Mon Sep 17 00:00:00 2001 From: howydev <132113803+howydev@users.noreply.github.com> Date: Wed, 10 Jul 2024 14:00:33 -0400 Subject: [PATCH 076/111] fix: lint --- src/plugins/NativeTokenLimitPlugin.sol | 28 +++++++++++++++----------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/plugins/NativeTokenLimitPlugin.sol b/src/plugins/NativeTokenLimitPlugin.sol index cb84a4ee..58d10a9b 100644 --- a/src/plugins/NativeTokenLimitPlugin.sol +++ b/src/plugins/NativeTokenLimitPlugin.sol @@ -22,9 +22,9 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { using UserOperationLib for PackedUserOperation; using EnumerableSet for EnumerableSet.Bytes32Set; - string internal constant NAME = "Native Token Limit"; - string internal constant VERSION = "1.0.0"; - string internal constant AUTHOR = "ERC-6900 Authors"; + string internal constant _NAME = "Native Token Limit"; + string internal constant _VERSION = "1.0.0"; + string internal constant _AUTHOR = "ERC-6900 Authors"; mapping(uint256 funcIds => mapping(address account => uint256 limit)) public limits; // Accounts should add paymasters that still use the accounts tokens here @@ -54,7 +54,13 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { ) { uint256 vgl = UserOperationLib.unpackVerificationGasLimit(userOp); uint256 cgl = UserOperationLib.unpackCallGasLimit(userOp); - uint256 totalGas = userOp.preVerificationGas + vgl + cgl; + uint256 pvgl; + uint256 ppogl; + if (userOp.paymasterAndData.length > 0) { + // Can skip the EP length check here since it would have reverted there if it was invalid + (, pvgl, ppogl) = UserOperationLib.unpackPaymasterStaticFields(userOp.paymasterAndData); + } + uint256 totalGas = userOp.preVerificationGas + vgl + cgl + pvgl + ppogl; uint256 usage = totalGas * UserOperationLib.unpackMaxFeePerGas(userOp); uint256 limit = limits[functionId][msg.sender]; @@ -104,21 +110,19 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { // No implementation, no revert // Runtime spends no account gas, and we check native token spend limits in exec hooks + // solhint-disable-next-line no-empty-blocks function preRuntimeValidationHook(uint8, address, uint256, bytes calldata) external pure override {} /// @inheritdoc IPlugin - function pluginManifest() external pure override returns (PluginManifest memory) { - // silence warnings - PluginManifest memory manifest; - return manifest; - } + // solhint-disable-next-line no-empty-blocks + function pluginManifest() external pure override returns (PluginManifest memory) {} /// @inheritdoc IPlugin function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { PluginMetadata memory metadata; - metadata.name = NAME; - metadata.version = VERSION; - metadata.author = AUTHOR; + metadata.name = _NAME; + metadata.version = _VERSION; + metadata.author = _AUTHOR; metadata.permissionRequest = new string[](2); metadata.permissionRequest[0] = "native-token-limit"; From 28ed73b6c0a7472694d883c74b6183848f6581ab Mon Sep 17 00:00:00 2001 From: howydev <132113803+howydev@users.noreply.github.com> Date: Wed, 10 Jul 2024 19:55:09 -0400 Subject: [PATCH 077/111] fix: lint, test --- src/plugins/NativeTokenLimitPlugin.sol | 7 +++++-- test/plugin/NativeTokenLimitPlugin.t.sol | 14 +++++--------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/plugins/NativeTokenLimitPlugin.sol b/src/plugins/NativeTokenLimitPlugin.sol index 58d10a9b..2b512c22 100644 --- a/src/plugins/NativeTokenLimitPlugin.sol +++ b/src/plugins/NativeTokenLimitPlugin.sol @@ -110,8 +110,11 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { // No implementation, no revert // Runtime spends no account gas, and we check native token spend limits in exec hooks - // solhint-disable-next-line no-empty-blocks - function preRuntimeValidationHook(uint8, address, uint256, bytes calldata) external pure override {} + function preRuntimeValidationHook(uint8, address, uint256, bytes calldata, bytes calldata) + external + pure + override + {} // solhint-disable-line no-empty-blocks /// @inheritdoc IPlugin // solhint-disable-next-line no-empty-blocks diff --git a/test/plugin/NativeTokenLimitPlugin.t.sol b/test/plugin/NativeTokenLimitPlugin.t.sol index b8072238..bfc16b33 100644 --- a/test/plugin/NativeTokenLimitPlugin.t.sol +++ b/test/plugin/NativeTokenLimitPlugin.t.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.sol"; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; @@ -14,10 +13,9 @@ import {IStandardExecutor, Call} from "../../src/interfaces/IStandardExecutor.so import {PluginManifest} from "../../src/interfaces/IPlugin.sol"; import {MSCAFactoryFixture} from "../mocks/MSCAFactoryFixture.sol"; -import {OptimizedTest} from "../utils/OptimizedTest.sol"; +import {AccountTestBase} from "../utils/AccountTestBase.sol"; -contract NativeTokenLimitPluginTest is OptimizedTest { - EntryPoint public entryPoint = new EntryPoint(); +contract NativeTokenLimitPluginTest is AccountTestBase { address public recipient = address(1); address payable public bundler = payable(address(2)); PluginManifest internal _m; @@ -87,7 +85,7 @@ contract NativeTokenLimitPluginTest is OptimizedTest { preVerificationGas: gas3, gasFees: bytes32(uint256(uint128(gasPrice))), paymasterAndData: "", - signature: abi.encodePacked(FunctionReferenceLib.pack(address(validationPlugin), 0), uint8(1)) + signature: _encodeSignature(FunctionReferenceLib.pack(address(validationPlugin), 0), 1, "") }); } @@ -184,9 +182,7 @@ contract NativeTokenLimitPluginTest is OptimizedTest { function test_runtime_executeLimit() public { assertEq(plugin.limits(0, address(acct)), 10 ether); - acct.executeWithAuthorization( - _getExecuteWithValue(5 ether), abi.encodePacked(validationFunction, uint8(1)) - ); + acct.executeWithAuthorization(_getExecuteWithValue(5 ether), _encodeSignature(validationFunction, 1, "")); assertEq(plugin.limits(0, address(acct)), 5 ether); } @@ -198,7 +194,7 @@ contract NativeTokenLimitPluginTest is OptimizedTest { assertEq(plugin.limits(0, address(acct)), 10 ether); acct.executeWithAuthorization( - abi.encodeCall(IStandardExecutor.executeBatch, (calls)), abi.encodePacked(validationFunction, uint8(1)) + abi.encodeCall(IStandardExecutor.executeBatch, (calls)), _encodeSignature(validationFunction, 1, "") ); assertEq(plugin.limits(0, address(acct)), 4 ether - 100001); } From 6b2edbbf68920a4dfa3f32592ceb317cdbaab609 Mon Sep 17 00:00:00 2001 From: howydev <132113803+howydev@users.noreply.github.com> Date: Mon, 15 Jul 2024 22:52:26 -0400 Subject: [PATCH 078/111] fix: test --- test/plugin/NativeTokenLimitPlugin.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/plugin/NativeTokenLimitPlugin.t.sol b/test/plugin/NativeTokenLimitPlugin.t.sol index bfc16b33..5fbf2a3d 100644 --- a/test/plugin/NativeTokenLimitPlugin.t.sol +++ b/test/plugin/NativeTokenLimitPlugin.t.sol @@ -11,6 +11,7 @@ import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; import {FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; import {IStandardExecutor, Call} from "../../src/interfaces/IStandardExecutor.sol"; import {PluginManifest} from "../../src/interfaces/IPlugin.sol"; +import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import {MSCAFactoryFixture} from "../mocks/MSCAFactoryFixture.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; @@ -56,8 +57,7 @@ contract NativeTokenLimitPluginTest is AccountTestBase { vm.prank(address(acct)); acct.installValidation( - FunctionReferenceLib.pack(address(validationPlugin), 0), - true, + ValidationConfigLib.pack(address(validationPlugin), 0, true, true), new bytes4[](0), new bytes(0), abi.encode(preValidationHooks, preValHooksInitDatas), From edeb0fa84062472105950a33e4ac55b58dc831f4 Mon Sep 17 00:00:00 2001 From: howy <132113803+howydev@users.noreply.github.com> Date: Tue, 16 Jul 2024 16:59:00 -0700 Subject: [PATCH 079/111] [5/n permissions] feat: add erc20 token spend limit plugin (#80) Co-authored-by: fangting-alchemy <119372438+fangting-alchemy@users.noreply.github.com> --- .gitmodules | 3 + lib/modular-account-libs | 1 + remappings.txt | 1 + src/plugins/ERC20TokenLimitPlugin.sol | 154 ++++++++++++++++++++ test/mocks/MockERC20.sol | 12 ++ test/plugin/ERC20TokenLimitPlugin.t.sol | 184 ++++++++++++++++++++++++ 6 files changed, 355 insertions(+) create mode 160000 lib/modular-account-libs create mode 100644 src/plugins/ERC20TokenLimitPlugin.sol create mode 100644 test/mocks/MockERC20.sol create mode 100644 test/plugin/ERC20TokenLimitPlugin.t.sol diff --git a/.gitmodules b/.gitmodules index 813d955e..05bd137f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "lib/modular-account-libs"] + path = lib/modular-account-libs + url = https://github.com/erc6900/modular-account-libs diff --git a/lib/modular-account-libs b/lib/modular-account-libs new file mode 160000 index 00000000..5d9d0e40 --- /dev/null +++ b/lib/modular-account-libs @@ -0,0 +1 @@ +Subproject commit 5d9d0e403332251045eee2954c2a8b7ea0bae953 diff --git a/remappings.txt b/remappings.txt index 3d0ee0df..bc2ce0be 100644 --- a/remappings.txt +++ b/remappings.txt @@ -2,3 +2,4 @@ ds-test/=lib/forge-std/lib/ds-test/src/ forge-std/=lib/forge-std/src/ @eth-infinitism/account-abstraction/=lib/account-abstraction/contracts/ @openzeppelin/=lib/openzeppelin-contracts/ +@modular-account-libs/=lib/modular-account-libs/src/ \ No newline at end of file diff --git a/src/plugins/ERC20TokenLimitPlugin.sol b/src/plugins/ERC20TokenLimitPlugin.sol new file mode 100644 index 00000000..1df5bcfd --- /dev/null +++ b/src/plugins/ERC20TokenLimitPlugin.sol @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.25; + +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; +import {UserOperationLib} from "@eth-infinitism/account-abstraction/core/UserOperationLib.sol"; +import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { + SetValue, + AssociatedLinkedListSet, + AssociatedLinkedListSetLib +} from "@modular-account-libs/libraries/AssociatedLinkedListSetLib.sol"; + +import {PluginManifest, PluginMetadata} from "../interfaces/IPlugin.sol"; +import {IStandardExecutor, Call} from "../interfaces/IStandardExecutor.sol"; +import {IPlugin} from "../interfaces/IPlugin.sol"; +import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; +import {BasePlugin, IERC165} from "./BasePlugin.sol"; + +/// @title ERC20 Token Limit Plugin +/// @author ERC-6900 Authors +/// @notice This plugin supports an ERC20 token spend limit. This should be combined with a contract whitelist +/// plugin to make sure that token transfers not tracked by the plugin don't happen. +/// Note: this plugin is opinionated on what selectors can be called for token contracts to guard against weird +/// edge cases like DAI. You wouldn't be able to use uni v2 pairs directly as the pair contract is also the LP +/// token contract +contract ERC20TokenLimitPlugin is BasePlugin, IExecutionHook { + using UserOperationLib for PackedUserOperation; + using EnumerableSet for EnumerableSet.AddressSet; + using AssociatedLinkedListSetLib for AssociatedLinkedListSet; + + struct ERC20SpendLimit { + address token; + uint256[] limits; + } + + string internal constant _NAME = "ERC20 Token Limit Plugin"; + string internal constant _VERSION = "1.0.0"; + string internal constant _AUTHOR = "ERC-6900 Authors"; + + mapping(uint8 functionId => mapping(address token => mapping(address account => uint256 limit))) public limits; + AssociatedLinkedListSet internal _tokenList; + + error ExceededTokenLimit(); + error ExceededNumberOfEntities(); + error SelectorNotAllowed(); + + function updateLimits(uint8 functionId, address token, uint256 newLimit) external { + _tokenList.tryAdd(msg.sender, SetValue.wrap(bytes30(bytes20(token)))); + limits[functionId][token][msg.sender] = newLimit; + } + + /// @inheritdoc IExecutionHook + function preExecutionHook(uint8 functionId, address, uint256, bytes calldata data) + external + override + returns (bytes memory) + { + (bytes4 selector, bytes memory callData) = _getSelectorAndCalldata(data); + + if (selector == IStandardExecutor.execute.selector) { + (address token,, bytes memory innerCalldata) = abi.decode(callData, (address, uint256, bytes)); + if (_tokenList.contains(msg.sender, SetValue.wrap(bytes30(bytes20(token))))) { + _decrementLimit(functionId, token, innerCalldata); + } + } else if (selector == IStandardExecutor.executeBatch.selector) { + Call[] memory calls = abi.decode(callData, (Call[])); + for (uint256 i = 0; i < calls.length; i++) { + if (_tokenList.contains(msg.sender, SetValue.wrap(bytes30(bytes20(calls[i].target))))) { + _decrementLimit(functionId, calls[i].target, calls[i].data); + } + } + } + + return ""; + } + + /// @inheritdoc IPlugin + function onInstall(bytes calldata data) external override { + (uint8 startFunctionId, ERC20SpendLimit[] memory spendLimits) = + abi.decode(data, (uint8, ERC20SpendLimit[])); + + if (startFunctionId + spendLimits.length > type(uint8).max) { + revert ExceededNumberOfEntities(); + } + + for (uint8 i = 0; i < spendLimits.length; i++) { + _tokenList.tryAdd(msg.sender, SetValue.wrap(bytes30(bytes20(spendLimits[i].token)))); + for (uint256 j = 0; j < spendLimits[i].limits.length; j++) { + limits[i + startFunctionId][spendLimits[i].token][msg.sender] = spendLimits[i].limits[j]; + } + } + } + + /// @inheritdoc IPlugin + function onUninstall(bytes calldata data) external override { + (address token, uint8 functionId) = abi.decode(data, (address, uint8)); + delete limits[functionId][token][msg.sender]; + } + + function getTokensForAccount(address account) external view returns (address[] memory tokens) { + SetValue[] memory set = _tokenList.getAll(account); + tokens = new address[](set.length); + for (uint256 i = 0; i < tokens.length; i++) { + tokens[i] = address(bytes20(bytes32(SetValue.unwrap(set[i])))); + } + return tokens; + } + + /// @inheritdoc IExecutionHook + function postExecutionHook(uint8, bytes calldata) external pure override { + revert NotImplemented(); + } + + /// @inheritdoc IPlugin + // solhint-disable-next-line no-empty-blocks + function pluginManifest() external pure override returns (PluginManifest memory) {} + + /// @inheritdoc IPlugin + function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { + PluginMetadata memory metadata; + metadata.name = _NAME; + metadata.version = _VERSION; + metadata.author = _AUTHOR; + + metadata.permissionRequest = new string[](1); + metadata.permissionRequest[0] = "erc20-token-limit"; + return metadata; + } + + /// @inheritdoc BasePlugin + function supportsInterface(bytes4 interfaceId) public view override(BasePlugin, IERC165) returns (bool) { + return super.supportsInterface(interfaceId); + } + + function _decrementLimit(uint8 functionId, address token, bytes memory innerCalldata) internal { + bytes4 selector; + uint256 spend; + assembly { + selector := mload(add(innerCalldata, 32)) // 0:32 is arr len, 32:36 is selector + spend := mload(add(innerCalldata, 68)) // 36:68 is recipient, 68:100 is spend + } + if (selector == IERC20.transfer.selector || selector == IERC20.approve.selector) { + uint256 limit = limits[functionId][token][msg.sender]; + if (spend > limit) { + revert ExceededTokenLimit(); + } + // solhint-disable-next-line reentrancy + limits[functionId][token][msg.sender] = limit - spend; + } else { + revert SelectorNotAllowed(); + } + } +} diff --git a/test/mocks/MockERC20.sol b/test/mocks/MockERC20.sol new file mode 100644 index 00000000..131e0d1a --- /dev/null +++ b/test/mocks/MockERC20.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract MockERC20 is ERC20 { + constructor() ERC20("MockERC20", "MERC") {} + + function mint(address to, uint256 amount) external { + _mint(to, amount); + } +} diff --git a/test/plugin/ERC20TokenLimitPlugin.t.sol b/test/plugin/ERC20TokenLimitPlugin.t.sol new file mode 100644 index 00000000..96a18c20 --- /dev/null +++ b/test/plugin/ERC20TokenLimitPlugin.t.sol @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; +import {MockERC20} from "../mocks/MockERC20.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; +import {FunctionReference} from "../../src/helpers/FunctionReferenceLib.sol"; +import {ERC20TokenLimitPlugin} from "../../src/plugins/ERC20TokenLimitPlugin.sol"; +import {MockPlugin} from "../mocks/MockPlugin.sol"; +import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; +import {FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; +import {IStandardExecutor, Call} from "../../src/interfaces/IStandardExecutor.sol"; +import {PluginManifest} from "../../src/interfaces/IPlugin.sol"; +import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; + +import {MSCAFactoryFixture} from "../mocks/MSCAFactoryFixture.sol"; +import {AccountTestBase} from "../utils/AccountTestBase.sol"; + +contract ERC20TokenLimitPluginTest is AccountTestBase { + address public recipient = address(1); + MockERC20 public erc20; + address payable public bundler = payable(address(2)); + PluginManifest internal _m; + MockPlugin public validationPlugin = new MockPlugin(_m); + FunctionReference public validationFunction; + + UpgradeableModularAccount public acct; + ERC20TokenLimitPlugin public plugin = new ERC20TokenLimitPlugin(); + uint256 public spendLimit = 10 ether; + + function setUp() public { + // Set up a validator with hooks from the erc20 spend limit plugin attached + MSCAFactoryFixture factory = new MSCAFactoryFixture(entryPoint, _deploySingleOwnerPlugin()); + + acct = factory.createAccount(address(this), 0); + + erc20 = new MockERC20(); + erc20.mint(address(acct), 10 ether); + + ExecutionHook[] memory permissionHooks = new ExecutionHook[](1); + permissionHooks[0] = ExecutionHook({ + hookFunction: FunctionReferenceLib.pack(address(plugin), 0), + isPreHook: true, + isPostHook: false + }); + + // arr idx 0 => functionId of 0 has that spend + uint256[] memory limits = new uint256[](1); + limits[0] = spendLimit; + + ERC20TokenLimitPlugin.ERC20SpendLimit[] memory limit = new ERC20TokenLimitPlugin.ERC20SpendLimit[](1); + limit[0] = ERC20TokenLimitPlugin.ERC20SpendLimit({token: address(erc20), limits: limits}); + + bytes[] memory permissionInitDatas = new bytes[](1); + permissionInitDatas[0] = abi.encode(uint8(0), limit); + + vm.prank(address(acct)); + acct.installValidation( + ValidationConfigLib.pack(address(validationPlugin), 0, true, true), + new bytes4[](0), + new bytes(0), + new bytes(0), + abi.encode(permissionHooks, permissionInitDatas) + ); + + validationFunction = FunctionReferenceLib.pack(address(validationPlugin), 0); + } + + function _getPackedUO(bytes memory callData) internal view returns (PackedUserOperation memory uo) { + uo = PackedUserOperation({ + sender: address(acct), + nonce: 0, + initCode: "", + callData: abi.encodePacked(UpgradeableModularAccount.executeUserOp.selector, callData), + accountGasLimits: bytes32(bytes16(uint128(200000))) | bytes32(uint256(200000)), + preVerificationGas: 200000, + gasFees: bytes32(uint256(uint128(0))), + paymasterAndData: "", + signature: _encodeSignature(FunctionReferenceLib.pack(address(validationPlugin), 0), 1, "") + }); + } + + function _getExecuteWithSpend(uint256 value) internal view returns (bytes memory) { + return abi.encodeCall( + UpgradeableModularAccount.execute, + (address(erc20), 0, abi.encodeCall(IERC20.transfer, (recipient, value))) + ); + } + + function test_userOp_executeLimit() public { + vm.startPrank(address(entryPoint)); + assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether); + acct.executeUserOp(_getPackedUO(_getExecuteWithSpend(5 ether)), bytes32(0)); + assertEq(plugin.limits(0, address(erc20), address(acct)), 5 ether); + } + + function test_userOp_executeBatchLimit() public { + Call[] memory calls = new Call[](3); + calls[0] = + Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.transfer, (recipient, 1 wei))}); + calls[1] = + Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.transfer, (recipient, 1 ether))}); + calls[2] = Call({ + target: address(erc20), + value: 0, + data: abi.encodeCall(IERC20.transfer, (recipient, 5 ether + 100000)) + }); + + vm.startPrank(address(entryPoint)); + assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether); + acct.executeUserOp(_getPackedUO(abi.encodeCall(IStandardExecutor.executeBatch, (calls))), bytes32(0)); + assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether - 6 ether - 100001); + } + + function test_userOp_executeBatch_approveAndTransferLimit() public { + Call[] memory calls = new Call[](3); + calls[0] = + Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.approve, (recipient, 1 wei))}); + calls[1] = + Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.transfer, (recipient, 1 ether))}); + calls[2] = Call({ + target: address(erc20), + value: 0, + data: abi.encodeCall(IERC20.approve, (recipient, 5 ether + 100000)) + }); + + vm.startPrank(address(entryPoint)); + assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether); + acct.executeUserOp(_getPackedUO(abi.encodeCall(IStandardExecutor.executeBatch, (calls))), bytes32(0)); + assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether - 6 ether - 100001); + } + + function test_userOp_executeBatch_approveAndTransferLimit_fail() public { + Call[] memory calls = new Call[](3); + calls[0] = + Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.approve, (recipient, 1 wei))}); + calls[1] = + Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.transfer, (recipient, 1 ether))}); + calls[2] = Call({ + target: address(erc20), + value: 0, + data: abi.encodeCall(IERC20.approve, (recipient, 9 ether + 100000)) + }); + + vm.startPrank(address(entryPoint)); + assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether); + PackedUserOperation[] memory uos = new PackedUserOperation[](1); + uos[0] = _getPackedUO(abi.encodeCall(IStandardExecutor.executeBatch, (calls))); + entryPoint.handleOps(uos, bundler); + // no spend consumed + assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether); + } + + function test_runtime_executeLimit() public { + assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether); + acct.executeWithAuthorization( + _getExecuteWithSpend(5 ether), + _encodeSignature(FunctionReferenceLib.pack(address(validationPlugin), 0), 1, "") + ); + assertEq(plugin.limits(0, address(erc20), address(acct)), 5 ether); + } + + function test_runtime_executeBatchLimit() public { + Call[] memory calls = new Call[](3); + calls[0] = + Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.approve, (recipient, 1 wei))}); + calls[1] = + Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.transfer, (recipient, 1 ether))}); + calls[2] = Call({ + target: address(erc20), + value: 0, + data: abi.encodeCall(IERC20.approve, (recipient, 5 ether + 100000)) + }); + + assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether); + acct.executeWithAuthorization( + abi.encodeCall(IStandardExecutor.executeBatch, (calls)), + _encodeSignature(FunctionReferenceLib.pack(address(validationPlugin), 0), 1, "") + ); + assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether - 6 ether - 100001); + } +} From 24cc73383060cce977c718636422a577a54ccacc Mon Sep 17 00:00:00 2001 From: fangting-alchemy <119372438+fangting-alchemy@users.noreply.github.com> Date: Tue, 16 Jul 2024 17:49:57 -0700 Subject: [PATCH 080/111] feat: Validation revamp - Introduce validation composability and allow multiple validation installation on account (#93) --- README.md | 2 +- src/account/AccountLoupe.sol | 10 +- src/account/AccountStorage.sol | 43 ++-- src/account/PluginManager2.sol | 38 ++-- src/account/PluginManagerInternals.sol | 22 +- src/account/UpgradeableModularAccount.sol | 123 ++++++------ src/helpers/FunctionReferenceLib.sol | 37 ---- src/helpers/PluginEntityLib.sol | 35 ++++ src/helpers/ValidationConfigLib.sol | 74 +++---- ...taHelpers.sol => ValidationResHelpers.sol} | 20 +- src/interfaces/IAccountLoupe.sol | 15 +- src/interfaces/IExecutionHook.sol | 16 +- src/interfaces/IPlugin.sol | 4 +- src/interfaces/IPluginManager.sol | 6 +- src/interfaces/IValidation.sol | 34 ++-- src/interfaces/IValidationHook.sol | 24 +-- src/plugins/ERC20TokenLimitPlugin.sol | 32 +-- src/plugins/NativeTokenLimitPlugin.sol | 36 ++-- src/plugins/owner/SingleOwnerPlugin.sol | 188 ------------------ .../validation/ISingleSignerValidation.sol | 28 +++ .../validation/SingleSignerValidation.sol | 145 ++++++++++++++ .../permissionhooks/AllowlistPlugin.sol | 10 +- standard/ERCs/erc-6900.md | 58 +++--- test/account/AccountExecHooks.t.sol | 12 +- test/account/AccountLoupe.t.sol | 54 +++-- test/account/AccountReturnData.t.sol | 8 +- test/account/GlobalValidationTest.t.sol | 9 +- test/account/MultiValidation.t.sol | 36 ++-- test/account/PerHookData.t.sol | 59 +++--- test/account/SelfCallAuthorization.t.sol | 11 +- test/account/UpgradeableModularAccount.t.sol | 32 +-- test/account/ValidationIntersection.t.sol | 58 +++--- test/libraries/FunctionReferenceLib.t.sol | 40 ---- ...ure.sol => SingleSignerFactoryFixture.sol} | 28 +-- test/mocks/plugins/ComprehensivePlugin.sol | 46 ++--- .../plugins/MockAccessControlHookPlugin.sol | 10 +- test/mocks/plugins/ReturnDataPluginMocks.sol | 13 +- test/mocks/plugins/ValidationPluginMocks.sol | 33 +-- test/plugin/ERC20TokenLimitPlugin.t.sol | 19 +- test/plugin/NativeTokenLimitPlugin.t.sol | 19 +- test/plugin/SingleOwnerPlugin.t.sol | 186 ----------------- test/plugin/TokenReceiverPlugin.t.sol | 5 +- test/samples/AllowlistPlugin.t.sol | 19 +- test/utils/AccountTestBase.sol | 51 ++--- test/utils/CustomValidationTestBase.sol | 6 +- test/utils/OptimizedTest.sol | 16 +- test/utils/TestConstants.sol | 2 +- test/validation/SingleSignerValidation.t.sol | 142 +++++++++++++ 48 files changed, 912 insertions(+), 1002 deletions(-) delete mode 100644 src/helpers/FunctionReferenceLib.sol create mode 100644 src/helpers/PluginEntityLib.sol rename src/helpers/{ValidationDataHelpers.sol => ValidationResHelpers.sol} (72%) delete mode 100644 src/plugins/owner/SingleOwnerPlugin.sol create mode 100644 src/plugins/validation/ISingleSignerValidation.sol create mode 100644 src/plugins/validation/SingleSignerValidation.sol delete mode 100644 test/libraries/FunctionReferenceLib.t.sol rename test/mocks/{MSCAFactoryFixture.sol => SingleSignerFactoryFixture.sol} (79%) delete mode 100644 test/plugin/SingleOwnerPlugin.t.sol create mode 100644 test/validation/SingleSignerValidation.t.sol diff --git a/README.md b/README.md index fb3e9dbc..1c90c710 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Reference implementation for [ERC-6900](https://eips.ethereum.org/EIPS/eip-6900). It is an early draft implementation. -The implementation includes an upgradable modular account with two plugins (`SingleOwnerPlugin` and `TokenReceiverPlugin`). It is compliant with ERC-6900 with the latest updates. +The implementation includes an upgradable modular account with two plugins (`SingleSignerValidation` and `TokenReceiverPlugin`). It is compliant with ERC-6900 with the latest updates. ## Important Callouts diff --git a/src/account/AccountLoupe.sol b/src/account/AccountLoupe.sol index d652f45c..cca17149 100644 --- a/src/account/AccountLoupe.sol +++ b/src/account/AccountLoupe.sol @@ -6,7 +6,7 @@ import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import {IAccountLoupe, ExecutionHook} from "../interfaces/IAccountLoupe.sol"; -import {FunctionReference, IPluginManager} from "../interfaces/IPluginManager.sol"; +import {PluginEntity, IPluginManager} from "../interfaces/IPluginManager.sol"; import {IStandardExecutor} from "../interfaces/IStandardExecutor.sol"; import {getAccountStorage, toExecutionHook, toSelector} from "./AccountStorage.sol"; @@ -29,7 +29,7 @@ abstract contract AccountLoupe is IAccountLoupe { } /// @inheritdoc IAccountLoupe - function getSelectors(FunctionReference validationFunction) external view returns (bytes4[] memory) { + function getSelectors(PluginEntity validationFunction) external view returns (bytes4[] memory) { uint256 length = getAccountStorage().validationData[validationFunction].selectors.length(); bytes4[] memory selectors = new bytes4[](length); @@ -61,7 +61,7 @@ abstract contract AccountLoupe is IAccountLoupe { } /// @inheritdoc IAccountLoupe - function getPermissionHooks(FunctionReference validationFunction) + function getPermissionHooks(PluginEntity validationFunction) external view override @@ -79,11 +79,11 @@ abstract contract AccountLoupe is IAccountLoupe { } /// @inheritdoc IAccountLoupe - function getPreValidationHooks(FunctionReference validationFunction) + function getPreValidationHooks(PluginEntity validationFunction) external view override - returns (FunctionReference[] memory preValidationHooks) + returns (PluginEntity[] memory preValidationHooks) { preValidationHooks = getAccountStorage().validationData[validationFunction].preValidationHooks; } diff --git a/src/account/AccountStorage.sol b/src/account/AccountStorage.sol index 242ddff8..df081bd0 100644 --- a/src/account/AccountStorage.sol +++ b/src/account/AccountStorage.sol @@ -5,7 +5,7 @@ import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; -import {FunctionReference} from "../interfaces/IPluginManager.sol"; +import {PluginEntity} from "../interfaces/IPluginManager.sol"; // bytes = keccak256("ERC6900.UpgradeableModularAccount.Storage") bytes32 constant _ACCOUNT_STORAGE_SLOT = 0x9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e40; @@ -31,7 +31,7 @@ struct ValidationData { // Whether or not this validation is a signature validator. bool isSignatureValidation; // The pre validation hooks for this function selector. - FunctionReference[] preValidationHooks; + PluginEntity[] preValidationHooks; // Permission hooks for this validation function. EnumerableSet.Bytes32Set permissionHooks; // The set of selectors that may be validated by this validation function. @@ -46,7 +46,7 @@ struct AccountStorage { EnumerableMap.AddressToUintMap pluginManifestHashes; // Execution functions and their associated functions mapping(bytes4 => SelectorData) selectorData; - mapping(FunctionReference validationFunction => ValidationData) validationData; + mapping(PluginEntity validationFunction => ValidationData) validationData; // For ERC165 introspection mapping(bytes4 => uint256) supportedIfaces; } @@ -59,32 +59,32 @@ function getAccountStorage() pure returns (AccountStorage storage _storage) { using EnumerableSet for EnumerableSet.Bytes32Set; -function toSetValue(FunctionReference functionReference) pure returns (bytes32) { - return bytes32(FunctionReference.unwrap(functionReference)); +function toSetValue(PluginEntity functionReference) pure returns (bytes32) { + return bytes32(PluginEntity.unwrap(functionReference)); } -function toFunctionReference(bytes32 setValue) pure returns (FunctionReference) { - return FunctionReference.wrap(bytes21(setValue)); +function toPluginEntity(bytes32 setValue) pure returns (PluginEntity) { + return PluginEntity.wrap(bytes24(setValue)); } // ExecutionHook layout: -// 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF______________________ Hook Function Reference -// 0x__________________________________________AA____________________ is pre hook -// 0x____________________________________________BB__________________ is post hook +// 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF______________________ Hook Plugin Entity +// 0x________________________________________________AA____________________ is pre hook +// 0x__________________________________________________BB__________________ is post hook function toSetValue(ExecutionHook memory executionHook) pure returns (bytes32) { - return bytes32(FunctionReference.unwrap(executionHook.hookFunction)) - | bytes32(executionHook.isPreHook ? uint256(1) << 80 : 0) - | bytes32(executionHook.isPostHook ? uint256(1) << 72 : 0); + return bytes32(PluginEntity.unwrap(executionHook.hookFunction)) + | bytes32(executionHook.isPreHook ? uint256(1) << 56 : 0) + | bytes32(executionHook.isPostHook ? uint256(1) << 48 : 0); } function toExecutionHook(bytes32 setValue) pure - returns (FunctionReference hookFunction, bool isPreHook, bool isPostHook) + returns (PluginEntity hookFunction, bool isPreHook, bool isPostHook) { - hookFunction = FunctionReference.wrap(bytes21(setValue)); - isPreHook = (uint256(setValue) >> 80) & 0xFF == 1; - isPostHook = (uint256(setValue) >> 72) & 0xFF == 1; + hookFunction = PluginEntity.wrap(bytes24(setValue)); + isPreHook = (uint256(setValue) >> 56) & 0xFF == 1; + isPostHook = (uint256(setValue) >> 48) & 0xFF == 1; } function toSetValue(bytes4 selector) pure returns (bytes32) { @@ -96,15 +96,12 @@ function toSelector(bytes32 setValue) pure returns (bytes4) { } /// @dev Helper function to get all elements of a set into memory. -function toFunctionReferenceArray(EnumerableSet.Bytes32Set storage set) - view - returns (FunctionReference[] memory) -{ +function toPluginEntityArray(EnumerableSet.Bytes32Set storage set) view returns (PluginEntity[] memory) { uint256 length = set.length(); - FunctionReference[] memory result = new FunctionReference[](length); + PluginEntity[] memory result = new PluginEntity[](length); for (uint256 i = 0; i < length; ++i) { bytes32 key = set.at(i); - result[i] = FunctionReference.wrap(bytes21(key)); + result[i] = PluginEntity.wrap(bytes24(key)); } return result; } diff --git a/src/account/PluginManager2.sol b/src/account/PluginManager2.sol index 28eb0ecf..1f121880 100644 --- a/src/account/PluginManager2.sol +++ b/src/account/PluginManager2.sol @@ -4,10 +4,10 @@ pragma solidity ^0.8.25; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {IPlugin} from "../interfaces/IPlugin.sol"; -import {FunctionReference, ValidationConfig} from "../interfaces/IPluginManager.sol"; -import {FunctionReferenceLib} from "../helpers/FunctionReferenceLib.sol"; +import {PluginEntity, ValidationConfig} from "../interfaces/IPluginManager.sol"; +import {PluginEntityLib} from "../helpers/PluginEntityLib.sol"; import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; -import {ValidationData, getAccountStorage, toSetValue, toFunctionReference} from "./AccountStorage.sol"; +import {ValidationData, getAccountStorage, toSetValue, toPluginEntity} from "./AccountStorage.sol"; import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; // Temporary additional functions for a user-controlled install flow for validation functions. @@ -18,10 +18,10 @@ abstract contract PluginManager2 { // Index marking the start of the data for the validation function. uint8 internal constant _RESERVED_VALIDATION_DATA_INDEX = 255; - error PreValidationAlreadySet(FunctionReference validationFunction, FunctionReference preValidationFunction); - error ValidationAlreadySet(bytes4 selector, FunctionReference validationFunction); - error ValidationNotSet(bytes4 selector, FunctionReference validationFunction); - error PermissionAlreadySet(FunctionReference validationFunction, ExecutionHook hook); + error PreValidationAlreadySet(PluginEntity validationFunction, PluginEntity preValidationFunction); + error ValidationAlreadySet(bytes4 selector, PluginEntity validationFunction); + error ValidationNotSet(bytes4 selector, PluginEntity validationFunction); + error PermissionAlreadySet(PluginEntity validationFunction, ExecutionHook hook); error PreValidationHookLimitExceeded(); function _installValidation( @@ -35,16 +35,16 @@ abstract contract PluginManager2 { getAccountStorage().validationData[validationConfig.functionReference()]; if (preValidationHooks.length > 0) { - (FunctionReference[] memory preValidationFunctions, bytes[] memory initDatas) = - abi.decode(preValidationHooks, (FunctionReference[], bytes[])); + (PluginEntity[] memory preValidationFunctions, bytes[] memory initDatas) = + abi.decode(preValidationHooks, (PluginEntity[], bytes[])); for (uint256 i = 0; i < preValidationFunctions.length; ++i) { - FunctionReference preValidationFunction = preValidationFunctions[i]; + PluginEntity preValidationFunction = preValidationFunctions[i]; _validationData.preValidationHooks.push(preValidationFunction); if (initDatas[i].length > 0) { - (address preValidationPlugin,) = FunctionReferenceLib.unpack(preValidationFunction); + (address preValidationPlugin,) = PluginEntityLib.unpack(preValidationFunction); IPlugin(preValidationPlugin).onInstall(initDatas[i]); } } @@ -67,7 +67,7 @@ abstract contract PluginManager2 { } if (initDatas[i].length > 0) { - (address executionPlugin,) = FunctionReferenceLib.unpack(permissionFunction.hookFunction); + (address executionPlugin,) = PluginEntityLib.unpack(permissionFunction.hookFunction); IPlugin(executionPlugin).onInstall(initDatas[i]); } } @@ -90,7 +90,7 @@ abstract contract PluginManager2 { } function _uninstallValidation( - FunctionReference validationFunction, + PluginEntity validationFunction, bytes calldata uninstallData, bytes calldata preValidationHookUninstallData, bytes calldata permissionHookUninstallData @@ -104,11 +104,11 @@ abstract contract PluginManager2 { bytes[] memory preValidationHookUninstallDatas = abi.decode(preValidationHookUninstallData, (bytes[])); // Clear pre validation hooks - FunctionReference[] storage preValidationHooks = _validationData.preValidationHooks; + PluginEntity[] storage preValidationHooks = _validationData.preValidationHooks; for (uint256 i = 0; i < preValidationHooks.length; ++i) { - FunctionReference preValidationFunction = preValidationHooks[i]; + PluginEntity preValidationFunction = preValidationHooks[i]; if (preValidationHookUninstallDatas[0].length > 0) { - (address preValidationPlugin,) = FunctionReferenceLib.unpack(preValidationFunction); + (address preValidationPlugin,) = PluginEntityLib.unpack(preValidationFunction); IPlugin(preValidationPlugin).onUninstall(preValidationHookUninstallDatas[0]); } } @@ -122,9 +122,9 @@ abstract contract PluginManager2 { EnumerableSet.Bytes32Set storage permissionHooks = _validationData.permissionHooks; uint256 i = 0; while (permissionHooks.length() > 0) { - FunctionReference permissionHook = toFunctionReference(permissionHooks.at(0)); + PluginEntity permissionHook = toPluginEntity(permissionHooks.at(0)); permissionHooks.remove(toSetValue(permissionHook)); - (address permissionHookPlugin,) = FunctionReferenceLib.unpack(permissionHook); + (address permissionHookPlugin,) = PluginEntityLib.unpack(permissionHook); IPlugin(permissionHookPlugin).onUninstall(permissionHookUninstallDatas[i++]); } } @@ -137,7 +137,7 @@ abstract contract PluginManager2 { } if (uninstallData.length > 0) { - (address plugin,) = FunctionReferenceLib.unpack(validationFunction); + (address plugin,) = PluginEntityLib.unpack(validationFunction); IPlugin(plugin).onUninstall(uninstallData); } } diff --git a/src/account/PluginManagerInternals.sol b/src/account/PluginManagerInternals.sol index 4df90e1f..ccc4c68b 100644 --- a/src/account/PluginManagerInternals.sol +++ b/src/account/PluginManagerInternals.sol @@ -5,17 +5,17 @@ import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165C import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; -import {FunctionReferenceLib} from "../helpers/FunctionReferenceLib.sol"; +import {PluginEntityLib} from "../helpers/PluginEntityLib.sol"; import {IPlugin, ManifestExecutionHook, ManifestValidation, PluginManifest} from "../interfaces/IPlugin.sol"; import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; -import {FunctionReference, IPluginManager} from "../interfaces/IPluginManager.sol"; +import {PluginEntity, IPluginManager} from "../interfaces/IPluginManager.sol"; import {KnownSelectors} from "../helpers/KnownSelectors.sol"; import {AccountStorage, getAccountStorage, SelectorData, toSetValue} from "./AccountStorage.sol"; abstract contract PluginManagerInternals is IPluginManager { using EnumerableSet for EnumerableSet.Bytes32Set; using EnumerableMap for EnumerableMap.AddressToUintMap; - using FunctionReferenceLib for FunctionReference; + using PluginEntityLib for PluginEntity; error ArrayLengthMismatch(); error Erc4337FunctionNotAllowed(bytes4 selector); @@ -23,13 +23,13 @@ abstract contract PluginManagerInternals is IPluginManager { error InvalidPluginManifest(); error IPluginFunctionNotAllowed(bytes4 selector); error NativeFunctionNotAllowed(bytes4 selector); - error NullFunctionReference(); + error NullPluginEntity(); error NullPlugin(); error PluginAlreadyInstalled(address plugin); error PluginInstallCallbackFailed(address plugin, bytes revertReason); error PluginInterfaceNotSupported(address plugin); error PluginNotInstalled(address plugin); - error ValidationFunctionAlreadySet(bytes4 selector, FunctionReference validationFunction); + error ValidationFunctionAlreadySet(bytes4 selector, PluginEntity validationFunction); // Storage update operations @@ -78,7 +78,7 @@ abstract contract PluginManagerInternals is IPluginManager { function _addValidationFunction(address plugin, ManifestValidation memory mv) internal { AccountStorage storage _storage = getAccountStorage(); - FunctionReference validationFunction = FunctionReferenceLib.pack(plugin, mv.functionId); + PluginEntity validationFunction = PluginEntityLib.pack(plugin, mv.entityId); if (mv.isDefault) { _storage.validationData[validationFunction].isGlobal = true; @@ -99,7 +99,7 @@ abstract contract PluginManagerInternals is IPluginManager { function _removeValidationFunction(address plugin, ManifestValidation memory mv) internal { AccountStorage storage _storage = getAccountStorage(); - FunctionReference validationFunction = FunctionReferenceLib.pack(plugin, mv.functionId); + PluginEntity validationFunction = PluginEntityLib.pack(plugin, mv.entityId); _storage.validationData[validationFunction].isGlobal = false; _storage.validationData[validationFunction].isSignatureValidation = false; @@ -113,7 +113,7 @@ abstract contract PluginManagerInternals is IPluginManager { function _addExecHooks( EnumerableSet.Bytes32Set storage hooks, - FunctionReference hookFunction, + PluginEntity hookFunction, bool isPreExecHook, bool isPostExecHook ) internal { @@ -126,7 +126,7 @@ abstract contract PluginManagerInternals is IPluginManager { function _removeExecHooks( EnumerableSet.Bytes32Set storage hooks, - FunctionReference hookFunction, + PluginEntity hookFunction, bool isPreExecHook, bool isPostExecHook ) internal { @@ -183,7 +183,7 @@ abstract contract PluginManagerInternals is IPluginManager { for (uint256 i = 0; i < length; ++i) { ManifestExecutionHook memory mh = manifest.executionHooks[i]; EnumerableSet.Bytes32Set storage execHooks = _storage.selectorData[mh.executionSelector].executionHooks; - FunctionReference hookFunction = FunctionReferenceLib.pack(plugin, mh.functionId); + PluginEntity hookFunction = PluginEntityLib.pack(plugin, mh.entityId); _addExecHooks(execHooks, hookFunction, mh.isPreHook, mh.isPostHook); } @@ -223,7 +223,7 @@ abstract contract PluginManagerInternals is IPluginManager { uint256 length = manifest.executionHooks.length; for (uint256 i = 0; i < length; ++i) { ManifestExecutionHook memory mh = manifest.executionHooks[i]; - FunctionReference hookFunction = FunctionReferenceLib.pack(plugin, mh.functionId); + PluginEntity hookFunction = PluginEntityLib.pack(plugin, mh.entityId); EnumerableSet.Bytes32Set storage execHooks = _storage.selectorData[mh.executionSelector].executionHooks; _removeExecHooks(execHooks, hookFunction, mh.isPreHook, mh.isPostHook); } diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index d2d1b2cc..acbd6de5 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -10,15 +10,15 @@ import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import {FunctionReferenceLib} from "../helpers/FunctionReferenceLib.sol"; +import {PluginEntityLib} from "../helpers/PluginEntityLib.sol"; import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; import {SparseCalldataSegmentLib} from "../helpers/SparseCalldataSegmentLib.sol"; -import {_coalescePreValidation, _coalesceValidation} from "../helpers/ValidationDataHelpers.sol"; +import {_coalescePreValidation, _coalesceValidation} from "../helpers/ValidationResHelpers.sol"; import {IPlugin, PluginManifest} from "../interfaces/IPlugin.sol"; import {IValidation} from "../interfaces/IValidation.sol"; import {IValidationHook} from "../interfaces/IValidationHook.sol"; import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; -import {FunctionReference, IPluginManager, ValidationConfig} from "../interfaces/IPluginManager.sol"; +import {PluginEntity, IPluginManager, ValidationConfig} from "../interfaces/IPluginManager.sol"; import {IStandardExecutor, Call} from "../interfaces/IStandardExecutor.sol"; import {AccountExecutor} from "./AccountExecutor.sol"; import {AccountLoupe} from "./AccountLoupe.sol"; @@ -41,13 +41,13 @@ contract UpgradeableModularAccount is UUPSUpgradeable { using EnumerableSet for EnumerableSet.Bytes32Set; - using FunctionReferenceLib for FunctionReference; + using PluginEntityLib for PluginEntity; using ValidationConfigLib for ValidationConfig; using SparseCalldataSegmentLib for bytes; struct PostExecToRun { bytes preExecHookReturnData; - FunctionReference postExecHook; + PluginEntity postExecHook; } IEntryPoint private immutable _ENTRY_POINT; @@ -68,18 +68,18 @@ contract UpgradeableModularAccount is error NativeTokenSpendingNotPermitted(address plugin); error NonCanonicalEncoding(); error NotEntryPoint(); - error PostExecHookReverted(address plugin, uint8 functionId, bytes revertReason); - error PreExecHookReverted(address plugin, uint8 functionId, bytes revertReason); - error PreRuntimeValidationHookFailed(address plugin, uint8 functionId, bytes revertReason); + error PostExecHookReverted(address plugin, uint32 entityId, bytes revertReason); + error PreExecHookReverted(address plugin, uint32 entityId, bytes revertReason); + error PreRuntimeValidationHookFailed(address plugin, uint32 entityId, bytes revertReason); error RequireUserOperationContext(); error RuntimeValidationFunctionMissing(bytes4 selector); - error RuntimeValidationFunctionReverted(address plugin, uint8 functionId, bytes revertReason); + error RuntimeValidationFunctionReverted(address plugin, uint32 entityId, bytes revertReason); error SelfCallRecursionDepthExceeded(); - error SignatureValidationInvalid(address plugin, uint8 functionId); - error UnexpectedAggregator(address plugin, uint8 functionId, address aggregator); + error SignatureValidationInvalid(address plugin, uint32 entityId); + error UnexpectedAggregator(address plugin, uint32 entityId, address aggregator); error UnrecognizedFunction(bytes4 selector); error UserOpValidationFunctionMissing(bytes4 selector); - error ValidationDoesNotApply(bytes4 selector, address plugin, uint8 functionId, bool isGlobal); + error ValidationDoesNotApply(bytes4 selector, address plugin, uint32 entityId, bool isGlobal); error ValidationSignatureSegmentMissing(); error SignatureSegmentOutOfOrder(); @@ -164,7 +164,7 @@ contract UpgradeableModularAccount is revert NotEntryPoint(); } - FunctionReference userOpValidationFunction = FunctionReference.wrap(bytes21(userOp.signature[:21])); + PluginEntity userOpValidationFunction = PluginEntity.wrap(bytes24(userOp.signature[:24])); PostExecToRun[] memory postPermissionHooks = _doPreHooks(getAccountStorage().validationData[userOpValidationFunction].permissionHooks, msg.data); @@ -217,13 +217,13 @@ contract UpgradeableModularAccount is returns (bytes memory) { // Revert if the provided `authorization` less than 21 bytes long, rather than right-padding. - FunctionReference runtimeValidationFunction = FunctionReference.wrap(bytes21(authorization[:21])); + PluginEntity runtimeValidationFunction = PluginEntity.wrap(bytes24(authorization[:24])); // Check if the runtime validation function is allowed to be called - bool isGlobalValidation = uint8(authorization[21]) == 1; + bool isGlobalValidation = uint8(authorization[24]) == 1; _checkIfValidationAppliesCallData(data, runtimeValidationFunction, isGlobalValidation); - _doRuntimeValidation(runtimeValidationFunction, data, authorization[22:]); + _doRuntimeValidation(runtimeValidationFunction, data, authorization[25:]); // If runtime validation passes, do runtime permission checks PostExecToRun[] memory postPermissionHooks = @@ -301,7 +301,7 @@ contract UpgradeableModularAccount is /// @inheritdoc IPluginManager /// @notice May be validated by a global validation. function uninstallValidation( - FunctionReference validationFunction, + PluginEntity validationFunction, bytes calldata uninstallData, bytes calldata preValidationHookUninstallData, bytes calldata permissionHookUninstallData @@ -341,15 +341,15 @@ contract UpgradeableModularAccount is function isValidSignature(bytes32 hash, bytes calldata signature) public view override returns (bytes4) { AccountStorage storage _storage = getAccountStorage(); - FunctionReference sigValidation = FunctionReference.wrap(bytes21(signature)); + PluginEntity sigValidation = PluginEntity.wrap(bytes24(signature)); - (address plugin, uint8 functionId) = sigValidation.unpack(); + (address plugin, uint32 entityId) = sigValidation.unpack(); if (!_storage.validationData[sigValidation].isSignatureValidation) { - revert SignatureValidationInvalid(plugin, functionId); + revert SignatureValidationInvalid(plugin, entityId); } if ( - IValidation(plugin).validateSignature(functionId, msg.sender, hash, signature[21:]) + IValidation(plugin).validateSignature(address(this), entityId, msg.sender, hash, signature[24:]) == _1271_MAGIC_VALUE ) { return _1271_MAGIC_VALUE; @@ -377,8 +377,8 @@ contract UpgradeableModularAccount is } // Revert if the provided `authorization` less than 21 bytes long, rather than right-padding. - FunctionReference userOpValidationFunction = FunctionReference.wrap(bytes21(userOp.signature[:21])); - bool isGlobalValidation = uint8(userOp.signature[21]) == 1; + PluginEntity userOpValidationFunction = PluginEntity.wrap(bytes24(userOp.signature[:24])); + bool isGlobalValidation = uint8(userOp.signature[24]) == 1; _checkIfValidationAppliesCallData(userOp.callData, userOpValidationFunction, isGlobalValidation); @@ -393,12 +393,12 @@ contract UpgradeableModularAccount is revert RequireUserOperationContext(); } - validationData = _doUserOpValidation(userOpValidationFunction, userOp, userOp.signature[22:], userOpHash); + validationData = _doUserOpValidation(userOpValidationFunction, userOp, userOp.signature[25:], userOpHash); } // To support gas estimation, we don't fail early when the failure is caused by a signature failure function _doUserOpValidation( - FunctionReference userOpValidationFunction, + PluginEntity userOpValidationFunction, PackedUserOperation memory userOp, bytes calldata signature, bytes32 userOpHash @@ -407,10 +407,10 @@ contract UpgradeableModularAccount is bytes calldata signatureSegment; (signatureSegment, signature) = signature.getNextSegment(); - uint256 validationData; + uint256 validationRes; // Do preUserOpValidation hooks - FunctionReference[] memory preUserOpValidationHooks = + PluginEntity[] memory preUserOpValidationHooks = getAccountStorage().validationData[userOpValidationFunction].preValidationHooks; for (uint256 i = 0; i < preUserOpValidationHooks.length; ++i) { @@ -434,15 +434,15 @@ contract UpgradeableModularAccount is userOp.signature = ""; } - (address plugin, uint8 functionId) = preUserOpValidationHooks[i].unpack(); - uint256 currentValidationData = - IValidationHook(plugin).preUserOpValidationHook(functionId, userOp, userOpHash); + (address plugin, uint32 entityId) = preUserOpValidationHooks[i].unpack(); + uint256 currentValidationRes = + IValidationHook(plugin).preUserOpValidationHook(entityId, userOp, userOpHash); - if (uint160(currentValidationData) > 1) { + if (uint160(currentValidationRes) > 1) { // If the aggregator is not 0 or 1, it is an unexpected value - revert UnexpectedAggregator(plugin, functionId, address(uint160(currentValidationData))); + revert UnexpectedAggregator(plugin, entityId, address(uint160(currentValidationRes))); } - validationData = _coalescePreValidation(validationData, currentValidationData); + validationRes = _coalescePreValidation(validationRes, currentValidationRes); } // Run the user op validationFunction @@ -453,22 +453,22 @@ contract UpgradeableModularAccount is userOp.signature = signatureSegment.getBody(); - (address plugin, uint8 functionId) = userOpValidationFunction.unpack(); - uint256 currentValidationData = IValidation(plugin).validateUserOp(functionId, userOp, userOpHash); + (address plugin, uint32 entityId) = userOpValidationFunction.unpack(); + uint256 currentValidationRes = IValidation(plugin).validateUserOp(entityId, userOp, userOpHash); if (preUserOpValidationHooks.length != 0) { // If we have other validation data we need to coalesce with - validationData = _coalesceValidation(validationData, currentValidationData); + validationRes = _coalesceValidation(validationRes, currentValidationRes); } else { - validationData = currentValidationData; + validationRes = currentValidationRes; } } - return validationData; + return validationRes; } function _doRuntimeValidation( - FunctionReference runtimeValidationFunction, + PluginEntity runtimeValidationFunction, bytes calldata callData, bytes calldata authorizationData ) internal { @@ -477,7 +477,7 @@ contract UpgradeableModularAccount is (authSegment, authorizationData) = authorizationData.getNextSegment(); // run all preRuntimeValidation hooks - FunctionReference[] memory preRuntimeValidationHooks = + PluginEntity[] memory preRuntimeValidationHooks = getAccountStorage().validationData[runtimeValidationFunction].preValidationHooks; for (uint256 i = 0; i < preRuntimeValidationHooks.length; ++i) { @@ -501,15 +501,15 @@ contract UpgradeableModularAccount is currentAuthData = ""; } - (address hookPlugin, uint8 hookFunctionId) = preRuntimeValidationHooks[i].unpack(); + (address hookPlugin, uint32 hookEntityId) = preRuntimeValidationHooks[i].unpack(); try IValidationHook(hookPlugin).preRuntimeValidationHook( - hookFunctionId, msg.sender, msg.value, callData, currentAuthData + hookEntityId, msg.sender, msg.value, callData, currentAuthData ) // forgefmt: disable-start // solhint-disable-next-line no-empty-blocks {} catch (bytes memory revertReason) { // forgefmt: disable-end - revert PreRuntimeValidationHookFailed(hookPlugin, hookFunctionId, revertReason); + revert PreRuntimeValidationHookFailed(hookPlugin, hookEntityId, revertReason); } } @@ -517,14 +517,16 @@ contract UpgradeableModularAccount is revert ValidationSignatureSegmentMissing(); } - (address plugin, uint8 functionId) = runtimeValidationFunction.unpack(); + (address plugin, uint32 entityId) = runtimeValidationFunction.unpack(); - try IValidation(plugin).validateRuntime(functionId, msg.sender, msg.value, callData, authSegment.getBody()) + try IValidation(plugin).validateRuntime( + address(this), entityId, msg.sender, msg.value, callData, authSegment.getBody() + ) // forgefmt: disable-start // solhint-disable-next-line no-empty-blocks {} catch (bytes memory revertReason) { // forgefmt: disable-end - revert RuntimeValidationFunctionReverted(plugin, functionId, revertReason); + revert RuntimeValidationFunctionReverted(plugin, entityId, revertReason); } } @@ -540,7 +542,7 @@ contract UpgradeableModularAccount is // be sure that the set of hooks to run will not be affected by state changes mid-execution. for (uint256 i = 0; i < hooksLength; ++i) { bytes32 key = executionHooks.at(i); - (FunctionReference hookFunction,, bool isPostHook) = toExecutionHook(key); + (PluginEntity hookFunction,, bool isPostHook) = toExecutionHook(key); if (isPostHook) { postHooksToRun[i].postExecHook = hookFunction; } @@ -550,7 +552,7 @@ contract UpgradeableModularAccount is // exists. for (uint256 i = 0; i < hooksLength; ++i) { bytes32 key = executionHooks.at(i); - (FunctionReference hookFunction, bool isPreHook, bool isPostHook) = toExecutionHook(key); + (PluginEntity hookFunction, bool isPreHook, bool isPostHook) = toExecutionHook(key); if (isPreHook) { bytes memory preExecHookReturnData; @@ -565,18 +567,18 @@ contract UpgradeableModularAccount is } } - function _runPreExecHook(FunctionReference preExecHook, bytes memory data) + function _runPreExecHook(PluginEntity preExecHook, bytes memory data) internal returns (bytes memory preExecHookReturnData) { - (address plugin, uint8 functionId) = preExecHook.unpack(); - try IExecutionHook(plugin).preExecutionHook(functionId, msg.sender, msg.value, data) returns ( + (address plugin, uint32 entityId) = preExecHook.unpack(); + try IExecutionHook(plugin).preExecutionHook(entityId, msg.sender, msg.value, data) returns ( bytes memory returnData ) { preExecHookReturnData = returnData; } catch (bytes memory revertReason) { // TODO: same issue with EP0.6 - we can't do bytes4 error codes in plugins - revert PreExecHookReverted(plugin, functionId, revertReason); + revert PreExecHookReverted(plugin, entityId, revertReason); } } @@ -594,11 +596,11 @@ contract UpgradeableModularAccount is continue; } - (address plugin, uint8 functionId) = postHookToRun.postExecHook.unpack(); + (address plugin, uint32 entityId) = postHookToRun.postExecHook.unpack(); // solhint-disable-next-line no-empty-blocks - try IExecutionHook(plugin).postExecutionHook(functionId, postHookToRun.preExecHookReturnData) {} + try IExecutionHook(plugin).postExecutionHook(entityId, postHookToRun.preExecHookReturnData) {} catch (bytes memory revertReason) { - revert PostExecHookReverted(plugin, functionId, revertReason); + revert PostExecHookReverted(plugin, entityId, revertReason); } } } @@ -608,7 +610,7 @@ contract UpgradeableModularAccount is function _checkIfValidationAppliesCallData( bytes calldata callData, - FunctionReference validationFunction, + PluginEntity validationFunction, bool isGlobal ) internal view { bytes4 outerSelector = bytes4(callData[:4]); @@ -659,11 +661,10 @@ contract UpgradeableModularAccount is } } - function _checkIfValidationAppliesSelector( - bytes4 selector, - FunctionReference validationFunction, - bool isGlobal - ) internal view { + function _checkIfValidationAppliesSelector(bytes4 selector, PluginEntity validationFunction, bool isGlobal) + internal + view + { AccountStorage storage _storage = getAccountStorage(); // Check that the provided validation function is applicable to the selector diff --git a/src/helpers/FunctionReferenceLib.sol b/src/helpers/FunctionReferenceLib.sol deleted file mode 100644 index 7bddd94b..00000000 --- a/src/helpers/FunctionReferenceLib.sol +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.25; - -import {FunctionReference} from "../interfaces/IPluginManager.sol"; - -library FunctionReferenceLib { - // Empty or unset function reference. - FunctionReference internal constant _EMPTY_FUNCTION_REFERENCE = FunctionReference.wrap(bytes21(0)); - // Magic value for hooks that should always revert. - FunctionReference internal constant _PRE_HOOK_ALWAYS_DENY = FunctionReference.wrap(bytes21(uint168(2))); - - function pack(address addr, uint8 functionId) internal pure returns (FunctionReference) { - return FunctionReference.wrap(bytes21(bytes20(addr)) | bytes21(uint168(functionId))); - } - - function unpack(FunctionReference fr) internal pure returns (address addr, uint8 functionId) { - bytes21 underlying = FunctionReference.unwrap(fr); - addr = address(bytes20(underlying)); - functionId = uint8(bytes1(underlying << 160)); - } - - function isEmpty(FunctionReference fr) internal pure returns (bool) { - return FunctionReference.unwrap(fr) == bytes21(0); - } - - function notEmpty(FunctionReference fr) internal pure returns (bool) { - return FunctionReference.unwrap(fr) != bytes21(0); - } - - function eq(FunctionReference a, FunctionReference b) internal pure returns (bool) { - return FunctionReference.unwrap(a) == FunctionReference.unwrap(b); - } - - function notEq(FunctionReference a, FunctionReference b) internal pure returns (bool) { - return FunctionReference.unwrap(a) != FunctionReference.unwrap(b); - } -} diff --git a/src/helpers/PluginEntityLib.sol b/src/helpers/PluginEntityLib.sol new file mode 100644 index 00000000..423b7f70 --- /dev/null +++ b/src/helpers/PluginEntityLib.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.25; + +import {PluginEntity} from "../interfaces/IPluginManager.sol"; + +library PluginEntityLib { + // Magic value for hooks that should always revert. + PluginEntity internal constant _PRE_HOOK_ALWAYS_DENY = PluginEntity.wrap(bytes24(uint192(2))); + + function pack(address addr, uint32 entityId) internal pure returns (PluginEntity) { + return PluginEntity.wrap(bytes24(bytes20(addr)) | bytes24(uint192(entityId))); + } + + function unpack(PluginEntity fr) internal pure returns (address addr, uint32 entityId) { + bytes24 underlying = PluginEntity.unwrap(fr); + addr = address(bytes20(underlying)); + entityId = uint32(bytes4(underlying << 160)); + } + + function isEmpty(PluginEntity fr) internal pure returns (bool) { + return PluginEntity.unwrap(fr) == bytes24(0); + } + + function notEmpty(PluginEntity fr) internal pure returns (bool) { + return PluginEntity.unwrap(fr) != bytes24(0); + } + + function eq(PluginEntity a, PluginEntity b) internal pure returns (bool) { + return PluginEntity.unwrap(a) == PluginEntity.unwrap(b); + } + + function notEq(PluginEntity a, PluginEntity b) internal pure returns (bool) { + return PluginEntity.unwrap(a) != PluginEntity.unwrap(b); + } +} diff --git a/src/helpers/ValidationConfigLib.sol b/src/helpers/ValidationConfigLib.sol index 71639f80..95e8ea90 100644 --- a/src/helpers/ValidationConfigLib.sol +++ b/src/helpers/ValidationConfigLib.sol @@ -1,48 +1,48 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.25; -import {FunctionReference, ValidationConfig} from "../interfaces/IPluginManager.sol"; +import {PluginEntity, ValidationConfig} from "../interfaces/IPluginManager.sol"; // Validation config is a packed representation of a validation function and flags for its configuration. // Layout: // 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA________________________ // Address -// 0x________________________________________BB______________________ // Function ID -// 0x__________________________________________CC____________________ // isGlobal -// 0x____________________________________________DD__________________ // isSignatureValidation -// 0x______________________________________________000000000000000000 // unused +// 0x________________________________________BBBBBBBB________________ // Entity ID +// 0x________________________________________________CC______________ // isGlobal +// 0x__________________________________________________DD____________ // isSignatureValidation +// 0x____________________________________________________000000000000 // unused library ValidationConfigLib { - function pack(FunctionReference _validationFunction, bool _isGlobal, bool _isSignatureValidation) + function pack(PluginEntity _validationFunction, bool _isGlobal, bool _isSignatureValidation) internal pure returns (ValidationConfig) { return ValidationConfig.wrap( - bytes23( - bytes23(FunctionReference.unwrap(_validationFunction)) - // isGlobal flag stored in the 22nd byte - | bytes23(bytes32(_isGlobal ? uint256(1) << 80 : 0)) - // isSignatureValidation flag stored in the 23rd byte - | bytes23(bytes32(_isSignatureValidation ? uint256(1) << 72 : 0)) + bytes26( + bytes26(PluginEntity.unwrap(_validationFunction)) + // isGlobal flag stored in the 25th byte + | bytes26(bytes32(_isGlobal ? uint256(1) << 56 : 0)) + // isSignatureValidation flag stored in the 26th byte + | bytes26(bytes32(_isSignatureValidation ? uint256(1) << 48 : 0)) ) ); } - function pack(address _plugin, uint8 _functionId, bool _isGlobal, bool _isSignatureValidation) + function pack(address _plugin, uint32 _entityId, bool _isGlobal, bool _isSignatureValidation) internal pure returns (ValidationConfig) { return ValidationConfig.wrap( - bytes23( + bytes26( // plugin address stored in the first 20 bytes - bytes23(bytes20(_plugin)) - // functionId stored in the 21st byte - | bytes23(bytes32(uint256(_functionId) << 168)) - // isGlobal flag stored in the 22nd byte - | bytes23(bytes32(_isGlobal ? uint256(1) << 80 : 0)) - // isSignatureValidation flag stored in the 23rd byte - | bytes23(bytes32(_isSignatureValidation ? uint256(1) << 72 : 0)) + bytes26(bytes20(_plugin)) + // entityId stored in the 21st - 24th byte + | bytes26(bytes24(uint192(_entityId))) + // isGlobal flag stored in the 25th byte + | bytes26(bytes32(_isGlobal ? uint256(1) << 56 : 0)) + // isSignatureValidation flag stored in the 26th byte + | bytes26(bytes32(_isSignatureValidation ? uint256(1) << 48 : 0)) ) ); } @@ -50,43 +50,43 @@ library ValidationConfigLib { function unpackUnderlying(ValidationConfig config) internal pure - returns (address _plugin, uint8 _functionId, bool _isGlobal, bool _isSignatureValidation) + returns (address _plugin, uint32 _entityId, bool _isGlobal, bool _isSignatureValidation) { - bytes23 configBytes = ValidationConfig.unwrap(config); + bytes26 configBytes = ValidationConfig.unwrap(config); _plugin = address(bytes20(configBytes)); - _functionId = uint8(configBytes[20]); - _isGlobal = uint8(configBytes[21]) == 1; - _isSignatureValidation = uint8(configBytes[22]) == 1; + _entityId = uint32(bytes4(configBytes << 160)); + _isGlobal = uint8(configBytes[24]) == 1; + _isSignatureValidation = uint8(configBytes[25]) == 1; } function unpack(ValidationConfig config) internal pure - returns (FunctionReference _validationFunction, bool _isGlobal, bool _isSignatureValidation) + returns (PluginEntity _validationFunction, bool _isGlobal, bool _isSignatureValidation) { - bytes23 configBytes = ValidationConfig.unwrap(config); - _validationFunction = FunctionReference.wrap(bytes21(configBytes)); - _isGlobal = uint8(configBytes[21]) == 1; - _isSignatureValidation = uint8(configBytes[22]) == 1; + bytes26 configBytes = ValidationConfig.unwrap(config); + _validationFunction = PluginEntity.wrap(bytes24(configBytes)); + _isGlobal = uint8(configBytes[24]) == 1; + _isSignatureValidation = uint8(configBytes[25]) == 1; } function plugin(ValidationConfig config) internal pure returns (address) { return address(bytes20(ValidationConfig.unwrap(config))); } - function functionId(ValidationConfig config) internal pure returns (uint8) { - return uint8(ValidationConfig.unwrap(config)[20]); + function entityId(ValidationConfig config) internal pure returns (uint32) { + return uint32(bytes4(ValidationConfig.unwrap(config) << 160)); } - function functionReference(ValidationConfig config) internal pure returns (FunctionReference) { - return FunctionReference.wrap(bytes21(ValidationConfig.unwrap(config))); + function functionReference(ValidationConfig config) internal pure returns (PluginEntity) { + return PluginEntity.wrap(bytes24(ValidationConfig.unwrap(config))); } function isGlobal(ValidationConfig config) internal pure returns (bool) { - return uint8(ValidationConfig.unwrap(config)[21]) == 1; + return uint8(ValidationConfig.unwrap(config)[24]) == 1; } function isSignatureValidation(ValidationConfig config) internal pure returns (bool) { - return uint8(ValidationConfig.unwrap(config)[22]) == 1; + return uint8(ValidationConfig.unwrap(config)[25]) == 1; } } diff --git a/src/helpers/ValidationDataHelpers.sol b/src/helpers/ValidationResHelpers.sol similarity index 72% rename from src/helpers/ValidationDataHelpers.sol rename to src/helpers/ValidationResHelpers.sol index 3f61b19c..854d442c 100644 --- a/src/helpers/ValidationDataHelpers.sol +++ b/src/helpers/ValidationResHelpers.sol @@ -2,31 +2,31 @@ pragma solidity ^0.8.25; // solhint-disable-next-line private-vars-leading-underscore -function _coalescePreValidation(uint256 validationData1, uint256 validationData2) +function _coalescePreValidation(uint256 validationRes1, uint256 validationRes2) pure returns (uint256 resValidationData) { - uint48 validUntil1 = uint48(validationData1 >> 160); + uint48 validUntil1 = uint48(validationRes1 >> 160); if (validUntil1 == 0) { validUntil1 = type(uint48).max; } - uint48 validUntil2 = uint48(validationData2 >> 160); + uint48 validUntil2 = uint48(validationRes2 >> 160); if (validUntil2 == 0) { validUntil2 = type(uint48).max; } resValidationData = ((validUntil1 > validUntil2) ? uint256(validUntil2) << 160 : uint256(validUntil1) << 160); - uint48 validAfter1 = uint48(validationData1 >> 208); - uint48 validAfter2 = uint48(validationData2 >> 208); + uint48 validAfter1 = uint48(validationRes1 >> 208); + uint48 validAfter2 = uint48(validationRes2 >> 208); resValidationData |= ((validAfter1 < validAfter2) ? uint256(validAfter2) << 208 : uint256(validAfter1) << 208); // Once we know that the authorizer field is 0 or 1, we can safely bubble up SIG_FAIL with bitwise OR - resValidationData |= uint160(validationData1) | uint160(validationData2); + resValidationData |= uint160(validationRes1) | uint160(validationRes2); } // solhint-disable-next-line private-vars-leading-underscore -function _coalesceValidation(uint256 preValidationData, uint256 validationData) +function _coalesceValidation(uint256 preValidationData, uint256 validationRes) pure returns (uint256 resValidationData) { @@ -34,17 +34,17 @@ function _coalesceValidation(uint256 preValidationData, uint256 validationData) if (validUntil1 == 0) { validUntil1 = type(uint48).max; } - uint48 validUntil2 = uint48(validationData >> 160); + uint48 validUntil2 = uint48(validationRes >> 160); if (validUntil2 == 0) { validUntil2 = type(uint48).max; } resValidationData = ((validUntil1 > validUntil2) ? uint256(validUntil2) << 160 : uint256(validUntil1) << 160); uint48 validAfter1 = uint48(preValidationData >> 208); - uint48 validAfter2 = uint48(validationData >> 208); + uint48 validAfter2 = uint48(validationRes >> 208); resValidationData |= ((validAfter1 < validAfter2) ? uint256(validAfter2) << 208 : uint256(validAfter1) << 208); // If prevalidation failed, bubble up failure - resValidationData |= uint160(preValidationData) == 1 ? 1 : uint160(validationData); + resValidationData |= uint160(preValidationData) == 1 ? 1 : uint160(validationRes); } diff --git a/src/interfaces/IAccountLoupe.sol b/src/interfaces/IAccountLoupe.sol index d74c5940..3e7d9f11 100644 --- a/src/interfaces/IAccountLoupe.sol +++ b/src/interfaces/IAccountLoupe.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: CC0-1.0 pragma solidity ^0.8.25; -import {FunctionReference} from "../interfaces/IPluginManager.sol"; +import {PluginEntity} from "../interfaces/IPluginManager.sol"; /// @notice Pre and post hooks for a given selector. /// @dev It's possible for one of either `preExecHook` or `postExecHook` to be empty. struct ExecutionHook { - FunctionReference hookFunction; + PluginEntity hookFunction; bool isPreHook; bool isPostHook; } @@ -21,7 +21,7 @@ interface IAccountLoupe { /// @notice Get the selectors for a validation function. /// @param validationFunction The validation function to get the selectors for. /// @return The allowed selectors for this validation function. - function getSelectors(FunctionReference validationFunction) external view returns (bytes4[] memory); + function getSelectors(PluginEntity validationFunction) external view returns (bytes4[] memory); /// @notice Get the pre and post execution hooks for a selector. /// @param selector The selector to get the hooks for. @@ -31,18 +31,15 @@ interface IAccountLoupe { /// @notice Get the pre and post execution hooks for a validation function. /// @param validationFunction The validation function to get the hooks for. /// @return The pre and post execution hooks for this validation function. - function getPermissionHooks(FunctionReference validationFunction) - external - view - returns (ExecutionHook[] memory); + function getPermissionHooks(PluginEntity validationFunction) external view returns (ExecutionHook[] memory); /// @notice Get the pre user op and runtime validation hooks associated with a selector. /// @param validationFunction The validation function to get the hooks for. /// @return preValidationHooks The pre validation hooks for this selector. - function getPreValidationHooks(FunctionReference validationFunction) + function getPreValidationHooks(PluginEntity validationFunction) external view - returns (FunctionReference[] memory preValidationHooks); + returns (PluginEntity[] memory preValidationHooks); /// @notice Get an array of all installed plugins. /// @return The addresses of all installed plugins. diff --git a/src/interfaces/IExecutionHook.sol b/src/interfaces/IExecutionHook.sol index 3240c489..9cb16482 100644 --- a/src/interfaces/IExecutionHook.sol +++ b/src/interfaces/IExecutionHook.sol @@ -4,22 +4,22 @@ pragma solidity ^0.8.25; import {IPlugin} from "./IPlugin.sol"; interface IExecutionHook is IPlugin { - /// @notice Run the pre execution hook specified by the `functionId`. + /// @notice Run the pre execution hook specified by the `entityId`. /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param functionId An identifier that routes the call to different internal implementations, should there be - /// more than one. + /// @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. /// @return Context to pass to a post execution hook, if present. An empty bytes array MAY be returned. - function preExecutionHook(uint8 functionId, address sender, uint256 value, bytes calldata data) + function preExecutionHook(uint32 entityId, address sender, uint256 value, bytes calldata data) external returns (bytes memory); - /// @notice Run the post execution hook specified by the `functionId`. + /// @notice Run the post execution hook specified by the `entityId`. /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param functionId An identifier that routes the call to different internal implementations, should there be - /// more than one. + /// @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(uint8 functionId, bytes calldata preExecHookData) external; + function postExecutionHook(uint32 entityId, bytes calldata preExecHookData) external; } diff --git a/src/interfaces/IPlugin.sol b/src/interfaces/IPlugin.sol index eb10e96b..824a1ddf 100644 --- a/src/interfaces/IPlugin.sol +++ b/src/interfaces/IPlugin.sol @@ -15,7 +15,7 @@ struct ManifestExecutionFunction { // todo: do we need these at all? Or do we fully switch to `installValidation`? struct ManifestValidation { - uint8 functionId; + uint32 entityId; bool isDefault; bool isSignatureValidation; bytes4[] selectors; @@ -24,7 +24,7 @@ struct ManifestValidation { struct ManifestExecutionHook { // TODO(erc6900 spec): These fields can be packed into a single word bytes4 executionSelector; - uint8 functionId; + uint32 entityId; bool isPreHook; bool isPostHook; } diff --git a/src/interfaces/IPluginManager.sol b/src/interfaces/IPluginManager.sol index bf1296e1..d2df72b5 100644 --- a/src/interfaces/IPluginManager.sol +++ b/src/interfaces/IPluginManager.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: CC0-1.0 pragma solidity ^0.8.25; -type FunctionReference is bytes21; +type PluginEntity is bytes24; -type ValidationConfig is bytes23; +type ValidationConfig is bytes26; interface IPluginManager { event PluginInstalled(address indexed plugin, bytes32 manifestHash); @@ -45,7 +45,7 @@ interface IPluginManager { /// data /// @param permissionHookUninstallData Optional data to be decoded and used by the plugin to clear account data function uninstallValidation( - FunctionReference validationFunction, + PluginEntity validationFunction, bytes calldata uninstallData, bytes calldata preValidationHookUninstallData, bytes calldata permissionHookUninstallData diff --git a/src/interfaces/IValidation.sol b/src/interfaces/IValidation.sol index 38c8a139..471cd2c1 100644 --- a/src/interfaces/IValidation.sol +++ b/src/interfaces/IValidation.sol @@ -6,26 +6,28 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import {IPlugin} from "./IPlugin.sol"; interface IValidation is IPlugin { - /// @notice Run the user operation validationFunction specified by the `functionId`. - /// @param functionId An identifier that routes the call to different internal implementations, should there be - /// more than one. + /// @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(uint8 functionId, PackedUserOperation calldata userOp, bytes32 userOpHash) + function validateUserOp(uint32 entityId, PackedUserOperation calldata userOp, bytes32 userOpHash) external returns (uint256); - /// @notice Run the runtime validationFunction specified by the `functionId`. + /// @notice Run the runtime validationFunction specified by the `entityId`. /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param functionId An identifier that routes the call to different internal implementations, should there be - /// more than one. + /// @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( - uint8 functionId, + address account, + uint32 entityId, address sender, uint256 value, bytes calldata data, @@ -34,14 +36,18 @@ interface IValidation is IPlugin { /// @notice Validates a signature using ERC-1271. /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param functionId An identifier that routes the call to different internal implementations, should there be - /// more than one. + /// @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(uint8 functionId, address sender, bytes32 hash, bytes calldata signature) - external - view - returns (bytes4); + function validateSignature( + address account, + uint32 entityId, + address sender, + bytes32 hash, + bytes calldata signature + ) external view returns (bytes4); } diff --git a/src/interfaces/IValidationHook.sol b/src/interfaces/IValidationHook.sol index 8300bbb8..dd7e2500 100644 --- a/src/interfaces/IValidationHook.sol +++ b/src/interfaces/IValidationHook.sol @@ -6,26 +6,26 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import {IPlugin} from "./IPlugin.sol"; interface IValidationHook is IPlugin { - /// @notice Run the pre user operation validation hook specified by the `functionId`. + /// @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 functionId An identifier that routes the call to different internal implementations, should there be - /// more than one. + /// @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(uint8 functionId, PackedUserOperation calldata userOp, bytes32 userOpHash) + function preUserOpValidationHook(uint32 entityId, PackedUserOperation calldata userOp, bytes32 userOpHash) external returns (uint256); - /// @notice Run the pre runtime validation hook specified by the `functionId`. + /// @notice Run the pre runtime validation hook specified by the `entityId`. /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param functionId An identifier that routes the call to different internal implementations, should there be - /// more than one. + /// @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( - uint8 functionId, + uint32 entityId, address sender, uint256 value, bytes calldata data, @@ -34,14 +34,14 @@ interface IValidationHook is IPlugin { // TODO: support this hook type within the account & in the manifest - /// @notice Run the pre signature validation hook specified by the `functionId`. + /// @notice Run the pre signature validation hook specified by the `entityId`. /// @dev To indicate the call should revert, the function MUST revert. - /// @param functionId An identifier that routes the call to different internal implementations, should there be - /// more than one. + /// @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(uint8 functionId, address sender, bytes32 hash, bytes calldata + // function preSignatureValidationHook(uint32 entityId, address sender, bytes32 hash, bytes calldata // signature) // external // view diff --git a/src/plugins/ERC20TokenLimitPlugin.sol b/src/plugins/ERC20TokenLimitPlugin.sol index 1df5bcfd..4e9c17ac 100644 --- a/src/plugins/ERC20TokenLimitPlugin.sol +++ b/src/plugins/ERC20TokenLimitPlugin.sol @@ -38,20 +38,20 @@ contract ERC20TokenLimitPlugin is BasePlugin, IExecutionHook { string internal constant _VERSION = "1.0.0"; string internal constant _AUTHOR = "ERC-6900 Authors"; - mapping(uint8 functionId => mapping(address token => mapping(address account => uint256 limit))) public limits; + mapping(uint32 entityId => mapping(address token => mapping(address account => uint256 limit))) public limits; AssociatedLinkedListSet internal _tokenList; error ExceededTokenLimit(); error ExceededNumberOfEntities(); error SelectorNotAllowed(); - function updateLimits(uint8 functionId, address token, uint256 newLimit) external { + function updateLimits(uint32 entityId, address token, uint256 newLimit) external { _tokenList.tryAdd(msg.sender, SetValue.wrap(bytes30(bytes20(token)))); - limits[functionId][token][msg.sender] = newLimit; + limits[entityId][token][msg.sender] = newLimit; } /// @inheritdoc IExecutionHook - function preExecutionHook(uint8 functionId, address, uint256, bytes calldata data) + function preExecutionHook(uint32 entityId, address, uint256, bytes calldata data) external override returns (bytes memory) @@ -61,13 +61,13 @@ contract ERC20TokenLimitPlugin is BasePlugin, IExecutionHook { if (selector == IStandardExecutor.execute.selector) { (address token,, bytes memory innerCalldata) = abi.decode(callData, (address, uint256, bytes)); if (_tokenList.contains(msg.sender, SetValue.wrap(bytes30(bytes20(token))))) { - _decrementLimit(functionId, token, innerCalldata); + _decrementLimit(entityId, token, innerCalldata); } } else if (selector == IStandardExecutor.executeBatch.selector) { Call[] memory calls = abi.decode(callData, (Call[])); for (uint256 i = 0; i < calls.length; i++) { if (_tokenList.contains(msg.sender, SetValue.wrap(bytes30(bytes20(calls[i].target))))) { - _decrementLimit(functionId, calls[i].target, calls[i].data); + _decrementLimit(entityId, calls[i].target, calls[i].data); } } } @@ -77,25 +77,25 @@ contract ERC20TokenLimitPlugin is BasePlugin, IExecutionHook { /// @inheritdoc IPlugin function onInstall(bytes calldata data) external override { - (uint8 startFunctionId, ERC20SpendLimit[] memory spendLimits) = - abi.decode(data, (uint8, ERC20SpendLimit[])); + (uint32 startEntityId, ERC20SpendLimit[] memory spendLimits) = + abi.decode(data, (uint32, ERC20SpendLimit[])); - if (startFunctionId + spendLimits.length > type(uint8).max) { + if (startEntityId + spendLimits.length > type(uint32).max) { revert ExceededNumberOfEntities(); } for (uint8 i = 0; i < spendLimits.length; i++) { _tokenList.tryAdd(msg.sender, SetValue.wrap(bytes30(bytes20(spendLimits[i].token)))); for (uint256 j = 0; j < spendLimits[i].limits.length; j++) { - limits[i + startFunctionId][spendLimits[i].token][msg.sender] = spendLimits[i].limits[j]; + limits[i + startEntityId][spendLimits[i].token][msg.sender] = spendLimits[i].limits[j]; } } } /// @inheritdoc IPlugin function onUninstall(bytes calldata data) external override { - (address token, uint8 functionId) = abi.decode(data, (address, uint8)); - delete limits[functionId][token][msg.sender]; + (address token, uint32 entityId) = abi.decode(data, (address, uint32)); + delete limits[entityId][token][msg.sender]; } function getTokensForAccount(address account) external view returns (address[] memory tokens) { @@ -108,7 +108,7 @@ contract ERC20TokenLimitPlugin is BasePlugin, IExecutionHook { } /// @inheritdoc IExecutionHook - function postExecutionHook(uint8, bytes calldata) external pure override { + function postExecutionHook(uint32, bytes calldata) external pure override { revert NotImplemented(); } @@ -133,7 +133,7 @@ contract ERC20TokenLimitPlugin is BasePlugin, IExecutionHook { return super.supportsInterface(interfaceId); } - function _decrementLimit(uint8 functionId, address token, bytes memory innerCalldata) internal { + function _decrementLimit(uint32 entityId, address token, bytes memory innerCalldata) internal { bytes4 selector; uint256 spend; assembly { @@ -141,12 +141,12 @@ contract ERC20TokenLimitPlugin is BasePlugin, IExecutionHook { spend := mload(add(innerCalldata, 68)) // 36:68 is recipient, 68:100 is spend } if (selector == IERC20.transfer.selector || selector == IERC20.approve.selector) { - uint256 limit = limits[functionId][token][msg.sender]; + uint256 limit = limits[entityId][token][msg.sender]; if (spend > limit) { revert ExceededTokenLimit(); } // solhint-disable-next-line reentrancy - limits[functionId][token][msg.sender] = limit - spend; + limits[entityId][token][msg.sender] = limit - spend; } else { revert SelectorNotAllowed(); } diff --git a/src/plugins/NativeTokenLimitPlugin.sol b/src/plugins/NativeTokenLimitPlugin.sol index 2b512c22..de0d9f3d 100644 --- a/src/plugins/NativeTokenLimitPlugin.sol +++ b/src/plugins/NativeTokenLimitPlugin.sol @@ -34,8 +34,8 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { error ExceededNativeTokenLimit(); error ExceededNumberOfEntities(); - function updateLimits(uint8 functionId, uint256 newLimit) external { - limits[functionId][msg.sender] = newLimit; + function updateLimits(uint32 entityId, uint256 newLimit) external { + limits[entityId][msg.sender] = newLimit; } function updateSpecialPaymaster(address paymaster, bool allowed) external { @@ -43,7 +43,7 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { } /// @inheritdoc IValidationHook - function preUserOpValidationHook(uint8 functionId, PackedUserOperation calldata userOp, bytes32) + function preUserOpValidationHook(uint32 entityId, PackedUserOperation calldata userOp, bytes32) external returns (uint256) { @@ -63,54 +63,54 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { uint256 totalGas = userOp.preVerificationGas + vgl + cgl + pvgl + ppogl; uint256 usage = totalGas * UserOperationLib.unpackMaxFeePerGas(userOp); - uint256 limit = limits[functionId][msg.sender]; + uint256 limit = limits[entityId][msg.sender]; if (usage > limit) { revert ExceededNativeTokenLimit(); } - limits[functionId][msg.sender] = limit - usage; + limits[entityId][msg.sender] = limit - usage; } return 0; } /// @inheritdoc IExecutionHook - function preExecutionHook(uint8 functionId, address, uint256, bytes calldata data) + function preExecutionHook(uint32 entityId, address, uint256, bytes calldata data) external override returns (bytes memory) { - return _checkAndDecrementLimit(functionId, data); + return _checkAndDecrementLimit(entityId, data); } /// @inheritdoc IPlugin function onInstall(bytes calldata data) external override { - (uint8 startFunctionId, uint256[] memory spendLimits) = abi.decode(data, (uint8, uint256[])); + (uint32 startEntityId, uint256[] memory spendLimits) = abi.decode(data, (uint32, uint256[])); - if (startFunctionId + spendLimits.length > type(uint8).max) { + if (startEntityId + spendLimits.length > type(uint32).max) { revert ExceededNumberOfEntities(); } for (uint256 i = 0; i < spendLimits.length; i++) { - limits[i + startFunctionId][msg.sender] = spendLimits[i]; + limits[i + startEntityId][msg.sender] = spendLimits[i]; } } /// @inheritdoc IPlugin function onUninstall(bytes calldata data) external override { - // This is the highest functionId that's being used by the account - uint8 functionId = abi.decode(data, (uint8)); - for (uint256 i = 0; i < functionId; i++) { + // This is the highest entityId that's being used by the account + uint32 entityId = abi.decode(data, (uint32)); + for (uint256 i = 0; i < entityId; i++) { delete limits[i][msg.sender]; } } /// @inheritdoc IExecutionHook - function postExecutionHook(uint8, bytes calldata) external pure override { + function postExecutionHook(uint32, bytes calldata) external pure override { revert NotImplemented(); } // No implementation, no revert // Runtime spends no account gas, and we check native token spend limits in exec hooks - function preRuntimeValidationHook(uint8, address, uint256, bytes calldata, bytes calldata) + function preRuntimeValidationHook(uint32, address, uint256, bytes calldata, bytes calldata) external pure override @@ -142,7 +142,7 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { return interfaceId == type(IExecutionHook).interfaceId || super.supportsInterface(interfaceId); } - function _checkAndDecrementLimit(uint8 functionId, bytes calldata data) internal returns (bytes memory) { + function _checkAndDecrementLimit(uint32 entityId, bytes calldata data) internal returns (bytes memory) { (bytes4 selector, bytes memory callData) = _getSelectorAndCalldata(data); uint256 value; @@ -156,11 +156,11 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { } } - uint256 limit = limits[functionId][msg.sender]; + uint256 limit = limits[entityId][msg.sender]; if (value > limit) { revert ExceededNativeTokenLimit(); } - limits[functionId][msg.sender] = limit - value; + limits[entityId][msg.sender] = limit - value; return ""; } diff --git a/src/plugins/owner/SingleOwnerPlugin.sol b/src/plugins/owner/SingleOwnerPlugin.sol deleted file mode 100644 index dbb5d56a..00000000 --- a/src/plugins/owner/SingleOwnerPlugin.sol +++ /dev/null @@ -1,188 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.25; - -import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; -import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; -import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; - -import {PluginManifest, PluginMetadata, SelectorPermission} from "../../interfaces/IPlugin.sol"; -import {IPlugin} from "../../interfaces/IPlugin.sol"; -import {IValidation} from "../../interfaces/IValidation.sol"; -import {BasePlugin, IERC165} from "../BasePlugin.sol"; - -/// @title Single Owner Plugin -/// @author ERC-6900 Authors -/// @notice This plugin allows an EOA or smart contract to own a modular account. -/// It also supports [ERC-1271](https://eips.ethereum.org/EIPS/eip-1271) signature -/// validation for both validating the signature on user operations and in -/// exposing its own `isValidSignature` method. This only works when the owner of -/// modular account also support ERC-1271. -/// -/// ERC-4337's bundler validation rules limit the types of contracts that can be -/// used as owners to validate user operation signatures. For example, the -/// contract's `isValidSignature` function may not use any forbidden opcodes -/// such as `TIMESTAMP` or `NUMBER`, and the contract may not be an ERC-1967 -/// proxy as it accesses a constant implementation slot not associated with -/// the account, violating storage access rules. This also means that the -/// owner of a modular account may not be another modular account if you want to -/// send user operations through a bundler. -contract SingleOwnerPlugin is IValidation, BasePlugin { - using ECDSA for bytes32; - using MessageHashUtils for bytes32; - - string internal constant _NAME = "Single Owner Plugin"; - string internal constant _VERSION = "1.0.0"; - string internal constant _AUTHOR = "ERC-6900 Authors"; - - uint256 internal constant _SIG_VALIDATION_PASSED = 0; - uint256 internal constant _SIG_VALIDATION_FAILED = 1; - - // bytes4(keccak256("isValidSignature(bytes32,bytes)")) - bytes4 internal constant _1271_MAGIC_VALUE = 0x1626ba7e; - bytes4 internal constant _1271_INVALID = 0xffffffff; - - mapping(uint8 id => mapping(address account => address)) public owners; - - /// @notice This event is emitted when ownership of the account changes. - /// @param account The account whose ownership changed. - /// @param previousOwner The address of the previous owner. - /// @param newOwner The address of the new owner. - event OwnershipTransferred(address indexed account, address indexed previousOwner, address indexed newOwner); - - error AlreadyInitialized(); - error NotAuthorized(); - error NotInitialized(); - - // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Execution functions ┃ - // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - - /// @notice Transfer ownership of an account and ID to `newOwner`. The caller address (`msg.sender`) is user to - /// identify the account. - /// @param newOwner The address of the new owner. - function transferOwnership(uint8 id, address newOwner) external { - _transferOwnership(id, newOwner); - } - - // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Plugin interface functions ┃ - // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - - /// @inheritdoc IPlugin - function onInstall(bytes calldata data) external override { - (uint8 id, address owner) = abi.decode(data, (uint8, address)); - _transferOwnership(id, owner); - } - - /// @inheritdoc IPlugin - function onUninstall(bytes calldata data) external override { - uint8 id = abi.decode(data, (uint8)); - _transferOwnership(id, address(0)); - } - - /// @inheritdoc IValidation - function validateRuntime(uint8 functionId, address sender, uint256, bytes calldata, bytes calldata) - external - view - override - { - // Validate that the sender is the owner of the account or self. - if (sender != owners[functionId][msg.sender]) { - revert NotAuthorized(); - } - return; - } - - /// @inheritdoc IValidation - function validateUserOp(uint8 functionId, PackedUserOperation calldata userOp, bytes32 userOpHash) - external - view - override - returns (uint256) - { - // Validate the user op signature against the owner. - if ( - SignatureChecker.isValidSignatureNow( - owners[functionId][msg.sender], userOpHash.toEthSignedMessageHash(), userOp.signature - ) - ) { - return _SIG_VALIDATION_PASSED; - } - return _SIG_VALIDATION_FAILED; - } - - // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Execution view functions ┃ - // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - - /// @inheritdoc IValidation - /// @dev The signature is valid if it is signed by the owner's private key - /// (if the owner is an EOA) or if it is a valid ERC-1271 signature from the - /// owner (if the owner is a contract). Note that unlike the signature - /// validation used in `validateUserOp`, this does///*not** wrap the digest in - /// an "Ethereum Signed Message" envelope before checking the signature in - /// the EOA-owner case. - function validateSignature(uint8 functionId, address, bytes32 digest, bytes calldata signature) - external - view - override - returns (bytes4) - { - if (SignatureChecker.isValidSignatureNow(owners[functionId][msg.sender], digest, signature)) { - return _1271_MAGIC_VALUE; - } - return _1271_INVALID; - } - - // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Plugin view functions ┃ - // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - - /// @inheritdoc IPlugin - function pluginManifest() external pure override returns (PluginManifest memory) { - PluginManifest memory manifest; - return manifest; - } - - /// @inheritdoc IPlugin - function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { - PluginMetadata memory metadata; - metadata.name = _NAME; - metadata.version = _VERSION; - metadata.author = _AUTHOR; - - // Permission strings - string memory modifyOwnershipPermission = "Modify Ownership"; - - // Permission descriptions - metadata.permissionDescriptors = new SelectorPermission[](1); - metadata.permissionDescriptors[0] = SelectorPermission({ - functionSelector: this.transferOwnership.selector, - permissionDescription: modifyOwnershipPermission - }); - - return metadata; - } - - // ┏━━━━━━━━━━━━━━━┓ - // ┃ EIP-165 ┃ - // ┗━━━━━━━━━━━━━━━┛ - - /// @inheritdoc BasePlugin - function supportsInterface(bytes4 interfaceId) public view override(BasePlugin, IERC165) returns (bool) { - return interfaceId == type(IValidation).interfaceId || super.supportsInterface(interfaceId); - } - - // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Internal / Private functions ┃ - // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - - // Transfers ownership and emits and event. - function _transferOwnership(uint8 id, address newOwner) internal { - address previousOwner = owners[id][msg.sender]; - owners[id][msg.sender] = newOwner; - // Todo: include id in event - emit OwnershipTransferred(msg.sender, previousOwner, newOwner); - } -} diff --git a/src/plugins/validation/ISingleSignerValidation.sol b/src/plugins/validation/ISingleSignerValidation.sol new file mode 100644 index 00000000..2653b752 --- /dev/null +++ b/src/plugins/validation/ISingleSignerValidation.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.25; + +import {IValidation} from "../../interfaces/IValidation.sol"; + +interface ISingleSignerValidation is IValidation { + /// @notice This event is emitted when Signer of the account's validation changes. + /// @param account The account whose validation Signer changed. + /// @param entityId The entityId for the account and the signer. + /// @param previousSigner The address of the previous signer. + /// @param newSigner The address of the new signer. + event SignerTransferred( + address indexed account, uint32 indexed entityId, address previousSigner, address newSigner + ); + + error NotAuthorized(); + + /// @notice Transfer Signer of the account's validation to `newSigner`. + /// @param entityId The entityId for the account and the signer. + /// @param newSigner The address of the new signer. + function transferSigner(uint32 entityId, address newSigner) external; + + /// @notice Get the signer of the `account`'s validation. + /// @param entityId The entityId for the account and the signer. + /// @param account The account to get the signer of. + /// @return The address of the signer. + function signerOf(uint32 entityId, address account) external view returns (address); +} diff --git a/src/plugins/validation/SingleSignerValidation.sol b/src/plugins/validation/SingleSignerValidation.sol new file mode 100644 index 00000000..5ae4517d --- /dev/null +++ b/src/plugins/validation/SingleSignerValidation.sol @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.25; + +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; +import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; + +import {IPlugin, PluginManifest, PluginMetadata} from "../../interfaces/IPlugin.sol"; +import {IValidation} from "../../interfaces/IValidation.sol"; +import {BasePlugin} from "../BasePlugin.sol"; +import {ISingleSignerValidation} from "./ISingleSignerValidation.sol"; + +/// @title ECSDA Validation +/// @author ERC-6900 Authors +/// @notice This validation enables any ECDSA (secp256k1 curve) signature validation. It handles installation by +/// each entity (entityId). +/// Note: Uninstallation will NOT disable all installed validation entities. None of the functions are installed on +/// the account. Account states are to be retrieved from this global singleton directly. +/// +/// - This validation supports ERC-1271. The signature is valid if it is signed by the owner's private key +/// (if the owner is an EOA) or if it is a valid ERC-1271 signature from the +/// owner (if the owner is a contract). +/// +/// - This validation supports composition that other validation can relay on entities in this validation +/// to validate partially or fully. +contract SingleSignerValidation is ISingleSignerValidation, BasePlugin { + using ECDSA for bytes32; + using MessageHashUtils for bytes32; + + string public constant NAME = "SingleSigner Validation"; + string public constant VERSION = "1.0.0"; + string public constant AUTHOR = "ERC-6900 Authors"; + + uint256 internal constant _SIG_VALIDATION_PASSED = 0; + uint256 internal constant _SIG_VALIDATION_FAILED = 1; + + // bytes4(keccak256("isValidSignature(bytes32,bytes)")) + bytes4 internal constant _1271_MAGIC_VALUE = 0x1626ba7e; + bytes4 internal constant _1271_INVALID = 0xffffffff; + + mapping(uint32 entityId => mapping(address account => address)) public signer; + + /// @inheritdoc ISingleSignerValidation + function signerOf(uint32 entityId, address account) external view returns (address) { + return signer[entityId][account]; + } + + /// @inheritdoc ISingleSignerValidation + function transferSigner(uint32 entityId, address newSigner) external { + _transferSigner(entityId, newSigner); + } + + // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ + // ┃ Plugin interface functions ┃ + // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + + /// @inheritdoc IPlugin + function pluginManifest() external pure override returns (PluginManifest memory) { + PluginManifest memory manifest; + return manifest; + } + + /// @inheritdoc IPlugin + function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { + PluginMetadata memory metadata; + metadata.name = NAME; + metadata.version = VERSION; + metadata.author = AUTHOR; + return metadata; + } + + /// @inheritdoc IPlugin + function onInstall(bytes calldata data) external override { + (uint32 entityId, address newSigner) = abi.decode(data, (uint32, address)); + _transferSigner(entityId, newSigner); + } + + /// @inheritdoc IPlugin + function onUninstall(bytes calldata data) external override { + // ToDo: what does it mean in the world of composable validation world to uninstall one type of validation + // We can either get rid of all SingleSigner signers. What about the nested ones? + _transferSigner(abi.decode(data, (uint32)), address(0)); + } + + /// @inheritdoc IValidation + function validateUserOp(uint32 entityId, PackedUserOperation calldata userOp, bytes32 userOpHash) + external + view + override + returns (uint256) + { + // Validate the user op signature against the owner. + (address sigSigner,,) = (userOpHash.toEthSignedMessageHash()).tryRecover(userOp.signature); + if (sigSigner == address(0) || sigSigner != signer[entityId][userOp.sender]) { + return _SIG_VALIDATION_FAILED; + } + return _SIG_VALIDATION_PASSED; + } + + /// @inheritdoc IValidation + function validateRuntime( + address account, + uint32 entityId, + address sender, + uint256, + bytes calldata, + bytes calldata + ) external view override { + // Validate that the sender is the owner of the account or self. + if (sender != signer[entityId][account]) { + revert NotAuthorized(); + } + return; + } + + /// @inheritdoc IValidation + /// @dev The signature is valid if it is signed by the owner's private key + /// (if the owner is an EOA) or if it is a valid ERC-1271 signature from the + /// owner (if the owner is a contract). Note that unlike the signature + /// validation used in `validateUserOp`, this does///*not** wrap the digest in + /// an "Ethereum Signed Message" envelope before checking the signature in + /// the EOA-owner case. + function validateSignature(address account, uint32 entityId, address, bytes32 digest, bytes calldata signature) + external + view + override + returns (bytes4) + { + if (SignatureChecker.isValidSignatureNow(signer[entityId][account], digest, signature)) { + return _1271_MAGIC_VALUE; + } + return _1271_INVALID; + } + + // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ + // ┃ Internal / Private functions ┃ + // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + + function _transferSigner(uint32 entityId, address newSigner) internal { + address previousSigner = signer[entityId][msg.sender]; + signer[entityId][msg.sender] = newSigner; + emit SignerTransferred(msg.sender, entityId, previousSigner, newSigner); + } +} diff --git a/src/samples/permissionhooks/AllowlistPlugin.sol b/src/samples/permissionhooks/AllowlistPlugin.sol index 209d8370..2d86ca18 100644 --- a/src/samples/permissionhooks/AllowlistPlugin.sol +++ b/src/samples/permissionhooks/AllowlistPlugin.sol @@ -9,7 +9,7 @@ import {IStandardExecutor, Call} from "../../interfaces/IStandardExecutor.sol"; import {BasePlugin} from "../../plugins/BasePlugin.sol"; contract AllowlistPlugin is IValidationHook, BasePlugin { - enum FunctionId { + enum EntityId { PRE_VALIDATION_HOOK } @@ -68,25 +68,25 @@ contract AllowlistPlugin is IValidationHook, BasePlugin { selectorAllowlist[target][selector][msg.sender] = allowed; } - function preUserOpValidationHook(uint8 functionId, PackedUserOperation calldata userOp, bytes32) + function preUserOpValidationHook(uint32 entityId, PackedUserOperation calldata userOp, bytes32) external view override returns (uint256) { - if (functionId == uint8(FunctionId.PRE_VALIDATION_HOOK)) { + if (entityId == uint32(EntityId.PRE_VALIDATION_HOOK)) { _checkAllowlistCalldata(userOp.callData); return 0; } revert NotImplemented(); } - function preRuntimeValidationHook(uint8 functionId, address, uint256, bytes calldata data, bytes calldata) + function preRuntimeValidationHook(uint32 entityId, address, uint256, bytes calldata data, bytes calldata) external view override { - if (functionId == uint8(FunctionId.PRE_VALIDATION_HOOK)) { + if (entityId == uint32(EntityId.PRE_VALIDATION_HOOK)) { _checkAllowlistCalldata(data); return; } diff --git a/standard/ERCs/erc-6900.md b/standard/ERCs/erc-6900.md index 06b51e4d..fed6a625 100644 --- a/standard/ERCs/erc-6900.md +++ b/standard/ERCs/erc-6900.md @@ -106,10 +106,10 @@ Plugin manager interface. Modular Smart Contract Accounts **MUST** implement thi ```solidity // Treats the first 20 bytes as an address, and the last byte as a function identifier. -type FunctionReference is bytes21; +type PluginEntity is bytes21; interface IPluginManager { - event PluginInstalled(address indexed plugin, bytes32 manifestHash, FunctionReference[] dependencies); + event PluginInstalled(address indexed plugin, bytes32 manifestHash, PluginEntity[] dependencies); event PluginUninstalled(address indexed plugin, bool indexed onUninstallSucceeded); @@ -118,13 +118,13 @@ interface IPluginManager { /// @param manifestHash The hash of the plugin manifest. /// @param pluginInstallData Optional data to be decoded and used by the plugin to setup initial plugin data /// for the modular account. - /// @param dependencies The dependencies of the plugin, as described in the manifest. Each FunctionReference + /// @param dependencies The dependencies of the plugin, as described in the manifest. Each PluginEntity /// MUST be composed of an installed plugin's address and a function ID of its validation function. function installPlugin( address plugin, bytes32 manifestHash, bytes calldata pluginInstallData, - FunctionReference[] calldata dependencies + PluginEntity[] calldata dependencies ) external; /// @notice Uninstall a plugin from the modular account. @@ -215,13 +215,13 @@ interface IAccountLoupe { /// @notice Config for an execution function, given a selector. struct ExecutionFunctionConfig { address plugin; - FunctionReference validationFunction; + PluginEntity validationFunction; } /// @notice Pre and post hooks for a given selector. /// @dev It's possible for one of either `preExecHook` or `postExecHook` to be empty. struct ExecutionHooks { - FunctionReference hookFunction; + PluginEntity hookFunction; bool isPreHook; bool isPostHook; } @@ -243,7 +243,7 @@ interface IAccountLoupe { function getPreValidationHooks(bytes4 selector) external view - returns (FunctionReference[] memory preValidationHooks); + returns (PluginEntity[] memory preValidationHooks); /// @notice Get an array of all installed plugins. /// @return The addresses of all installed plugins. @@ -267,56 +267,56 @@ interface IPlugin { /// @param data Optional bytes array to be decoded and used by the plugin to clear plugin data for the modular account. function onUninstall(bytes calldata data) external; - /// @notice Run the pre user operation validation hook specified by the `functionId`. + /// @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 functionId An identifier that routes the call to different internal implementations, should there be more than one. + /// @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(uint8 functionId, PackedUserOperation memory userOp, bytes32 userOpHash) external returns (uint256); + function preUserOpValidationHook(uint8 entityId, PackedUserOperation memory userOp, bytes32 userOpHash) external returns (uint256); - /// @notice Run the user operation validationFunction specified by the `functionId`. - /// @param functionId An identifier that routes the call to different internal implementations, should there be + /// @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 userOpValidationFunction(uint8 functionId, PackedUserOperation calldata userOp, bytes32 userOpHash) + function userOpValidationFunction(uint8 entityId, PackedUserOperation calldata userOp, bytes32 userOpHash) external returns (uint256); - /// @notice Run the pre runtime validation hook specified by the `functionId`. + /// @notice Run the pre runtime validation hook specified by the `entityId`. /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param functionId An identifier that routes the call to different internal implementations, should there be more than one. + /// @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(uint8 functionId, address sender, uint256 value, bytes calldata data) external; + function preRuntimeValidationHook(uint8 entityId, address sender, uint256 value, bytes calldata data) external; - /// @notice Run the runtime validationFunction specified by the `functionId`. + /// @notice Run the runtime validationFunction specified by the `entityId`. /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param functionId An identifier that routes the call to different internal implementations, should there be + /// @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 runtimeValidationFunction(uint8 functionId, address sender, uint256 value, bytes calldata data) + function runtimeValidationFunction(uint8 entityId, address sender, uint256 value, bytes calldata data) external; - /// @notice Run the pre execution hook specified by the `functionId`. + /// @notice Run the pre execution hook specified by the `entityId`. /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param functionId An identifier that routes the call to different internal implementations, should there be more than one. + /// @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. /// @return Context to pass to a post execution hook, if present. An empty bytes array MAY be returned. - function preExecutionHook(uint8 functionId, address sender, uint256 value, bytes calldata data) external returns (bytes memory); + function preExecutionHook(uint32 entityId, address sender, uint256 value, bytes calldata data) external returns (bytes memory); - /// @notice Run the post execution hook specified by the `functionId`. + /// @notice Run the post execution hook specified by the `entityId`. /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param functionId An identifier that routes the call to different internal implementations, should there be more than one. + /// @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(uint8 functionId, bytes calldata preExecHookData) external; + function postExecutionHook(uint32 entityId, bytes calldata preExecHookData) external; /// @notice Describe the contents and intended configuration of the plugin. /// @dev This manifest MUST stay constant over time. @@ -359,7 +359,7 @@ enum ManifestAssociatedFunctionType { /// of the function at `dependencies[dependencyIndex]` during the call to `installPlugin(config)`. struct ManifestFunction { ManifestAssociatedFunctionType functionType; - uint8 functionId; + uint8 entityId; uint256 dependencyIndex; } @@ -371,7 +371,7 @@ struct ManifestAssociatedFunction { struct ManifestExecutionHook { // TODO(erc6900 spec): These fields can be packed into a single word bytes4 executionSelector; - uint8 functionId; + uint8 entityId; bool isPreHook; bool isPostHook; } @@ -443,7 +443,7 @@ The following behavior MUST be followed: #### Calls to `installPlugin` -The function `installPlugin` accepts 4 parameters: the address of the plugin to install, the Keccak-256 hash of the plugin's manifest, ABI-encoded data to pass to the plugin's `onInstall` callback, and an array of function references that represent the plugin's install dependencies. +The function `installPlugin` accepts 3 parameters: the address of the plugin to install, the Keccak-256 hash of the plugin's manifest, ABI-encoded data to pass to the plugin's `onInstall` callback. The function MUST retrieve the plugin's manifest by calling `pluginManifest()` using `staticcall`. @@ -513,7 +513,7 @@ Additionally, when the modular account natively implements functions in `IPlugin The steps to perform are: - If the call is not from the `EntryPoint`, then find an associated runtime validation function. If one does not exist, execution MUST revert. The modular account MUST execute all pre runtime validation hooks, then the runtime validation function, with the `call` opcode. All of these functions MUST receive the caller, value, and execution function's calldata as parameters. If any of these functions revert, execution MUST revert. If any pre execution hooks are set to `PRE_HOOK_ALWAYS_DENY`, execution MUST revert. If the validation function is set to `RUNTIME_VALIDATION_ALWAYS_ALLOW`, the runtime validation function MUST be bypassed. -- If there are pre execution hooks defined for the execution function, execute those hooks with the caller, value, and execution function's calldata as parameters. If any of these hooks returns data, it MUST be preserved until the call to the post execution hook. The operation MUST be done with the `call` opcode. If there are duplicate pre execution hooks (i.e., hooks with identical `FunctionReference`s), run the hook only once. If any of these functions revert, execution MUST revert. +- If there are pre execution hooks defined for the execution function, execute those hooks with the caller, value, and execution function's calldata as parameters. If any of these hooks returns data, it MUST be preserved until the call to the post execution hook. The operation MUST be done with the `call` opcode. If there are duplicate pre execution hooks (i.e., hooks with identical `PluginEntity`s), run the hook only once. If any of these functions revert, execution MUST revert. - Run the execution function. - If any post execution hooks are defined, run the functions. If a pre execution hook returned data to the account, that data MUST be passed as a parameter to the associated post execution hook. The operation MUST be done with the `call` opcode. If there are duplicate post execution hooks, run them once for each unique associated pre execution hook. For post execution hooks without an associated pre execution hook, run the hook only once. If any of these functions revert, execution MUST revert. diff --git a/test/account/AccountExecHooks.t.sol b/test/account/AccountExecHooks.t.sol index 14ad57fc..98d16dcf 100644 --- a/test/account/AccountExecHooks.t.sol +++ b/test/account/AccountExecHooks.t.sol @@ -18,9 +18,9 @@ contract AccountExecHooksTest is AccountTestBase { bytes32 public manifestHash2; bytes4 internal constant _EXEC_SELECTOR = bytes4(uint32(1)); - uint8 internal constant _PRE_HOOK_FUNCTION_ID_1 = 1; - uint8 internal constant _POST_HOOK_FUNCTION_ID_2 = 2; - uint8 internal constant _BOTH_HOOKS_FUNCTION_ID_3 = 3; + uint32 internal constant _PRE_HOOK_FUNCTION_ID_1 = 1; + uint32 internal constant _POST_HOOK_FUNCTION_ID_2 = 2; + uint32 internal constant _BOTH_HOOKS_FUNCTION_ID_3 = 3; PluginManifest internal _m1; @@ -45,7 +45,7 @@ contract AccountExecHooksTest is AccountTestBase { _installPlugin1WithHooks( ManifestExecutionHook({ executionSelector: _EXEC_SELECTOR, - functionId: _PRE_HOOK_FUNCTION_ID_1, + entityId: _PRE_HOOK_FUNCTION_ID_1, isPreHook: true, isPostHook: false }) @@ -83,7 +83,7 @@ contract AccountExecHooksTest is AccountTestBase { _installPlugin1WithHooks( ManifestExecutionHook({ executionSelector: _EXEC_SELECTOR, - functionId: _BOTH_HOOKS_FUNCTION_ID_3, + entityId: _BOTH_HOOKS_FUNCTION_ID_3, isPreHook: true, isPostHook: true }) @@ -131,7 +131,7 @@ contract AccountExecHooksTest is AccountTestBase { _installPlugin1WithHooks( ManifestExecutionHook({ executionSelector: _EXEC_SELECTOR, - functionId: _POST_HOOK_FUNCTION_ID_2, + entityId: _POST_HOOK_FUNCTION_ID_2, isPreHook: false, isPostHook: true }) diff --git a/test/account/AccountLoupe.t.sol b/test/account/AccountLoupe.t.sol index 69563b51..f0670848 100644 --- a/test/account/AccountLoupe.t.sol +++ b/test/account/AccountLoupe.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; -import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; +import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; import {IPluginManager} from "../../src/interfaces/IPluginManager.sol"; import {IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; @@ -69,9 +69,8 @@ contract AccountLoupeTest is CustomValidationTestBase { } function test_pluginLoupe_getSelectors() public { - FunctionReference comprehensivePluginValidation = FunctionReferenceLib.pack( - address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.VALIDATION) - ); + PluginEntity comprehensivePluginValidation = + PluginEntityLib.pack(address(comprehensivePlugin), uint32(ComprehensivePlugin.EntityId.VALIDATION)); bytes4[] memory selectors = account1.getSelectors(comprehensivePluginValidation); @@ -83,22 +82,22 @@ contract AccountLoupeTest is CustomValidationTestBase { ExecutionHook[] memory hooks = account1.getExecutionHooks(comprehensivePlugin.foo.selector); ExecutionHook[3] memory expectedHooks = [ ExecutionHook({ - hookFunction: FunctionReferenceLib.pack( - address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.BOTH_EXECUTION_HOOKS) + hookFunction: PluginEntityLib.pack( + address(comprehensivePlugin), uint32(ComprehensivePlugin.EntityId.BOTH_EXECUTION_HOOKS) ), isPreHook: true, isPostHook: true }), ExecutionHook({ - hookFunction: FunctionReferenceLib.pack( - address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.PRE_EXECUTION_HOOK) + hookFunction: PluginEntityLib.pack( + address(comprehensivePlugin), uint32(ComprehensivePlugin.EntityId.PRE_EXECUTION_HOOK) ), isPreHook: true, isPostHook: false }), ExecutionHook({ - hookFunction: FunctionReferenceLib.pack( - address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.POST_EXECUTION_HOOK) + hookFunction: PluginEntityLib.pack( + address(comprehensivePlugin), uint32(ComprehensivePlugin.EntityId.POST_EXECUTION_HOOK) ), isPreHook: false, isPostHook: true @@ -108,8 +107,7 @@ contract AccountLoupeTest is CustomValidationTestBase { assertEq(hooks.length, 3); for (uint256 i = 0; i < hooks.length; i++) { assertEq( - FunctionReference.unwrap(hooks[i].hookFunction), - FunctionReference.unwrap(expectedHooks[i].hookFunction) + PluginEntity.unwrap(hooks[i].hookFunction), PluginEntity.unwrap(expectedHooks[i].hookFunction) ); assertEq(hooks[i].isPreHook, expectedHooks[i].isPreHook); assertEq(hooks[i].isPostHook, expectedHooks[i].isPostHook); @@ -117,22 +115,22 @@ contract AccountLoupeTest is CustomValidationTestBase { } function test_pluginLoupe_getValidationHooks() public { - FunctionReference[] memory hooks = account1.getPreValidationHooks(_ownerValidation); + PluginEntity[] memory hooks = account1.getPreValidationHooks(_signerValidation); assertEq(hooks.length, 2); assertEq( - FunctionReference.unwrap(hooks[0]), - FunctionReference.unwrap( - FunctionReferenceLib.pack( - address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.PRE_VALIDATION_HOOK_1) + PluginEntity.unwrap(hooks[0]), + PluginEntity.unwrap( + PluginEntityLib.pack( + address(comprehensivePlugin), uint32(ComprehensivePlugin.EntityId.PRE_VALIDATION_HOOK_1) ) ) ); assertEq( - FunctionReference.unwrap(hooks[1]), - FunctionReference.unwrap( - FunctionReferenceLib.pack( - address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.PRE_VALIDATION_HOOK_2) + PluginEntity.unwrap(hooks[1]), + PluginEntity.unwrap( + PluginEntityLib.pack( + address(comprehensivePlugin), uint32(ComprehensivePlugin.EntityId.PRE_VALIDATION_HOOK_2) ) ) ); @@ -144,19 +142,19 @@ contract AccountLoupeTest is CustomValidationTestBase { internal virtual override - returns (FunctionReference, bool, bool, bytes4[] memory, bytes memory, bytes memory, bytes memory) + returns (PluginEntity, bool, bool, bytes4[] memory, bytes memory, bytes memory, bytes memory) { - FunctionReference[] memory preValidationHooks = new FunctionReference[](2); - preValidationHooks[0] = FunctionReferenceLib.pack( - address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.PRE_VALIDATION_HOOK_1) + PluginEntity[] memory preValidationHooks = new PluginEntity[](2); + preValidationHooks[0] = PluginEntityLib.pack( + address(comprehensivePlugin), uint32(ComprehensivePlugin.EntityId.PRE_VALIDATION_HOOK_1) ); - preValidationHooks[1] = FunctionReferenceLib.pack( - address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.PRE_VALIDATION_HOOK_2) + preValidationHooks[1] = PluginEntityLib.pack( + address(comprehensivePlugin), uint32(ComprehensivePlugin.EntityId.PRE_VALIDATION_HOOK_2) ); bytes[] memory installDatas = new bytes[](2); return ( - _ownerValidation, + _signerValidation, true, true, new bytes4[](0), diff --git a/test/account/AccountReturnData.t.sol b/test/account/AccountReturnData.t.sol index 1738c722..bb081325 100644 --- a/test/account/AccountReturnData.t.sol +++ b/test/account/AccountReturnData.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; +import {PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; import {Call} from "../../src/interfaces/IStandardExecutor.sol"; import { @@ -10,7 +10,7 @@ import { ResultConsumerPlugin } from "../mocks/plugins/ReturnDataPluginMocks.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; -import {TEST_DEFAULT_OWNER_FUNCTION_ID} from "../utils/TestConstants.sol"; +import {TEST_DEFAULT_VALIDATION_ENTITY_ID} from "../utils/TestConstants.sol"; // Tests all the different ways that return data can be read from plugins through an account contract AccountReturnDataTest is AccountTestBase { @@ -58,7 +58,7 @@ contract AccountReturnDataTest is AccountTestBase { (address(regularResultContract), 0, abi.encodeCall(RegularResultContract.foo, ())) ), _encodeSignature( - FunctionReferenceLib.pack(address(singleOwnerPlugin), TEST_DEFAULT_OWNER_FUNCTION_ID), + PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, "" ) @@ -86,7 +86,7 @@ contract AccountReturnDataTest is AccountTestBase { bytes memory retData = account1.executeWithAuthorization( abi.encodeCall(account1.executeBatch, (calls)), _encodeSignature( - FunctionReferenceLib.pack(address(singleOwnerPlugin), TEST_DEFAULT_OWNER_FUNCTION_ID), + PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, "" ) diff --git a/test/account/GlobalValidationTest.t.sol b/test/account/GlobalValidationTest.t.sol index 9f40f806..7ef7fdc5 100644 --- a/test/account/GlobalValidationTest.t.sol +++ b/test/account/GlobalValidationTest.t.sol @@ -5,7 +5,7 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; +import {PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; @@ -26,7 +26,8 @@ contract GlobalValidationTest is AccountTestBase { account2 = UpgradeableModularAccount(payable(factory.getAddress(owner2, 0))); vm.deal(address(account2), 100 ether); - _ownerValidation = FunctionReferenceLib.pack(address(singleOwnerPlugin), TEST_DEFAULT_OWNER_FUNCTION_ID); + _signerValidation = + PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID); ethRecipient = makeAddr("ethRecipient"); vm.deal(ethRecipient, 1 wei); @@ -48,7 +49,7 @@ contract GlobalValidationTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature(_signerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -65,7 +66,7 @@ contract GlobalValidationTest is AccountTestBase { vm.prank(owner2); account2.executeWithAuthorization( abi.encodeCall(UpgradeableModularAccount.execute, (ethRecipient, 1 wei, "")), - _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, "") + _encodeSignature(_signerValidation, GLOBAL_VALIDATION, "") ); assertEq(ethRecipient.balance, 2 wei); diff --git a/test/account/MultiValidation.t.sol b/test/account/MultiValidation.t.sol index 5254c50d..641fcefb 100644 --- a/test/account/MultiValidation.t.sol +++ b/test/account/MultiValidation.t.sol @@ -8,26 +8,26 @@ import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/Messa import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {FunctionReference} from "../../src/interfaces/IPluginManager.sol"; +import {PluginEntity} from "../../src/interfaces/IPluginManager.sol"; import {IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; -import {FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; +import {PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; -import {SingleOwnerPlugin} from "../../src/plugins/owner/SingleOwnerPlugin.sol"; +import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; -import {TEST_DEFAULT_OWNER_FUNCTION_ID} from "../utils/TestConstants.sol"; +import {TEST_DEFAULT_VALIDATION_ENTITY_ID} from "../utils/TestConstants.sol"; contract MultiValidationTest is AccountTestBase { using ECDSA for bytes32; using MessageHashUtils for bytes32; - SingleOwnerPlugin public validator2; + SingleSignerValidation public validator2; address public owner2; uint256 public owner2Key; function setUp() public { - validator2 = new SingleOwnerPlugin(); + validator2 = new SingleSignerValidation(); (owner2, owner2Key) = makeAddrAndKey("owner2"); } @@ -35,16 +35,16 @@ contract MultiValidationTest is AccountTestBase { function test_overlappingValidationInstall() public { vm.prank(address(entryPoint)); account1.installValidation( - ValidationConfigLib.pack(address(validator2), TEST_DEFAULT_OWNER_FUNCTION_ID, true, true), + ValidationConfigLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID, true, true), new bytes4[](0), - abi.encode(TEST_DEFAULT_OWNER_FUNCTION_ID, owner2), + abi.encode(TEST_DEFAULT_VALIDATION_ENTITY_ID, owner2), "", "" ); - FunctionReference[] memory validations = new FunctionReference[](2); - validations[0] = FunctionReferenceLib.pack(address(singleOwnerPlugin), TEST_DEFAULT_OWNER_FUNCTION_ID); - validations[1] = FunctionReferenceLib.pack(address(validator2), TEST_DEFAULT_OWNER_FUNCTION_ID); + PluginEntity[] memory validations = new PluginEntity[](2); + validations[0] = PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID); + validations[1] = PluginEntityLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID); bytes4[] memory selectors0 = account1.getSelectors(validations[0]); bytes4[] memory selectors1 = account1.getSelectors(validations[1]); @@ -64,16 +64,14 @@ contract MultiValidationTest is AccountTestBase { abi.encodeWithSelector( UpgradeableModularAccount.RuntimeValidationFunctionReverted.selector, address(validator2), - 0, + 1, abi.encodeWithSignature("NotAuthorized()") ) ); account1.executeWithAuthorization( abi.encodeCall(IStandardExecutor.execute, (address(0), 0, "")), _encodeSignature( - FunctionReferenceLib.pack(address(validator2), TEST_DEFAULT_OWNER_FUNCTION_ID), - GLOBAL_VALIDATION, - "" + PluginEntityLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, "" ) ); @@ -81,9 +79,7 @@ contract MultiValidationTest is AccountTestBase { account1.executeWithAuthorization( abi.encodeCall(IStandardExecutor.execute, (address(0), 0, "")), _encodeSignature( - FunctionReferenceLib.pack(address(validator2), TEST_DEFAULT_OWNER_FUNCTION_ID), - GLOBAL_VALIDATION, - "" + PluginEntityLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, "" ) ); } @@ -109,7 +105,7 @@ contract MultiValidationTest is AccountTestBase { bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); userOp.signature = _encodeSignature( - FunctionReferenceLib.pack(address(validator2), TEST_DEFAULT_OWNER_FUNCTION_ID), + PluginEntityLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, abi.encodePacked(r, s, v) ); @@ -124,7 +120,7 @@ contract MultiValidationTest is AccountTestBase { userOp.nonce = 1; (v, r, s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); userOp.signature = _encodeSignature( - FunctionReferenceLib.pack(address(validator2), TEST_DEFAULT_OWNER_FUNCTION_ID), + PluginEntityLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, abi.encodePacked(r, s, v) ); diff --git a/test/account/PerHookData.t.sol b/test/account/PerHookData.t.sol index 9c90cf5c..bcb53171 100644 --- a/test/account/PerHookData.t.sol +++ b/test/account/PerHookData.t.sol @@ -6,7 +6,7 @@ import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntry import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; +import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; import {MockAccessControlHookPlugin} from "../mocks/plugins/MockAccessControlHookPlugin.sol"; import {Counter} from "../mocks/Counter.sol"; @@ -37,8 +37,9 @@ contract PerHookDataTest is CustomValidationTestBase { PreValidationHookData[] memory preValidationHookData = new PreValidationHookData[](1); preValidationHookData[0] = PreValidationHookData({index: 0, validationData: abi.encodePacked(_counter)}); - userOp.signature = - _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature( + _signerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v) + ); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -59,8 +60,9 @@ contract PerHookDataTest is CustomValidationTestBase { validationData: abi.encodePacked(address(0x1234123412341234123412341234123412341234)) }); - userOp.signature = - _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature( + _signerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v) + ); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -80,7 +82,7 @@ contract PerHookDataTest is CustomValidationTestBase { (PackedUserOperation memory userOp, bytes32 userOpHash) = _getCounterUserOP(); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature(_signerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -104,8 +106,9 @@ contract PerHookDataTest is CustomValidationTestBase { preValidationHookData[0] = PreValidationHookData({index: 0, validationData: abi.encodePacked(_counter)}); preValidationHookData[1] = PreValidationHookData({index: 1, validationData: abi.encodePacked(_counter)}); - userOp.signature = - _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature( + _signerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v) + ); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -142,8 +145,9 @@ contract PerHookDataTest is CustomValidationTestBase { PreValidationHookData[] memory preValidationHookData = new PreValidationHookData[](1); preValidationHookData[0] = PreValidationHookData({index: 0, validationData: abi.encodePacked(beneficiary)}); - userOp.signature = - _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature( + _signerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v) + ); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -166,8 +170,9 @@ contract PerHookDataTest is CustomValidationTestBase { PreValidationHookData[] memory preValidationHookData = new PreValidationHookData[](1); preValidationHookData[0] = PreValidationHookData({index: 0, validationData: ""}); - userOp.signature = - _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature( + _signerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v) + ); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -195,7 +200,7 @@ contract PerHookDataTest is CustomValidationTestBase { UpgradeableModularAccount.execute, (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) ), - _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, preValidationHookData, "") + _encodeSignature(_signerValidation, GLOBAL_VALIDATION, preValidationHookData, "") ); assertEq(_counter.number(), 1); @@ -213,7 +218,7 @@ contract PerHookDataTest is CustomValidationTestBase { abi.encodeWithSelector( UpgradeableModularAccount.PreRuntimeValidationHookFailed.selector, _accessControlHookPlugin, - uint8(MockAccessControlHookPlugin.FunctionId.PRE_VALIDATION_HOOK), + uint32(MockAccessControlHookPlugin.EntityId.PRE_VALIDATION_HOOK), abi.encodeWithSignature("Error(string)", "Proof doesn't match target") ) ); @@ -222,7 +227,7 @@ contract PerHookDataTest is CustomValidationTestBase { UpgradeableModularAccount.execute, (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) ), - _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, preValidationHookData, "") + _encodeSignature(_signerValidation, GLOBAL_VALIDATION, preValidationHookData, "") ); } @@ -232,7 +237,7 @@ contract PerHookDataTest is CustomValidationTestBase { abi.encodeWithSelector( UpgradeableModularAccount.PreRuntimeValidationHookFailed.selector, _accessControlHookPlugin, - uint8(MockAccessControlHookPlugin.FunctionId.PRE_VALIDATION_HOOK), + uint32(MockAccessControlHookPlugin.EntityId.PRE_VALIDATION_HOOK), abi.encodeWithSignature("Error(string)", "Proof doesn't match target") ) ); @@ -241,7 +246,7 @@ contract PerHookDataTest is CustomValidationTestBase { UpgradeableModularAccount.execute, (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) ), - _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, "") + _encodeSignature(_signerValidation, GLOBAL_VALIDATION, "") ); } @@ -259,7 +264,7 @@ contract PerHookDataTest is CustomValidationTestBase { UpgradeableModularAccount.execute, (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) ), - _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, preValidationHookData, "") + _encodeSignature(_signerValidation, GLOBAL_VALIDATION, preValidationHookData, "") ); } @@ -274,13 +279,13 @@ contract PerHookDataTest is CustomValidationTestBase { abi.encodeWithSelector( UpgradeableModularAccount.PreRuntimeValidationHookFailed.selector, _accessControlHookPlugin, - uint8(MockAccessControlHookPlugin.FunctionId.PRE_VALIDATION_HOOK), + uint32(MockAccessControlHookPlugin.EntityId.PRE_VALIDATION_HOOK), abi.encodeWithSignature("Error(string)", "Target not allowed") ) ); account1.executeWithAuthorization( abi.encodeCall(UpgradeableModularAccount.execute, (beneficiary, 1 wei, "")), - _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, preValidationHookData, "") + _encodeSignature(_signerValidation, GLOBAL_VALIDATION, preValidationHookData, "") ); } @@ -295,7 +300,7 @@ contract PerHookDataTest is CustomValidationTestBase { UpgradeableModularAccount.execute, (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) ), - _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, preValidationHookData, "") + _encodeSignature(_signerValidation, GLOBAL_VALIDATION, preValidationHookData, "") ); } @@ -325,13 +330,13 @@ contract PerHookDataTest is CustomValidationTestBase { internal virtual override - returns (FunctionReference, bool, bool, bytes4[] memory, bytes memory, bytes memory, bytes memory) + returns (PluginEntity, bool, bool, bytes4[] memory, bytes memory, bytes memory, bytes memory) { - FunctionReference accessControlHook = FunctionReferenceLib.pack( - address(_accessControlHookPlugin), uint8(MockAccessControlHookPlugin.FunctionId.PRE_VALIDATION_HOOK) + PluginEntity accessControlHook = PluginEntityLib.pack( + address(_accessControlHookPlugin), uint32(MockAccessControlHookPlugin.EntityId.PRE_VALIDATION_HOOK) ); - FunctionReference[] memory preValidationHooks = new FunctionReference[](1); + PluginEntity[] memory preValidationHooks = new PluginEntity[](1); preValidationHooks[0] = accessControlHook; bytes[] memory preValidationHookData = new bytes[](1); @@ -341,11 +346,11 @@ contract PerHookDataTest is CustomValidationTestBase { bytes memory packedPreValidationHooks = abi.encode(preValidationHooks, preValidationHookData); return ( - _ownerValidation, + _signerValidation, true, true, new bytes4[](0), - abi.encode(TEST_DEFAULT_OWNER_FUNCTION_ID, owner1), + abi.encode(TEST_DEFAULT_VALIDATION_ENTITY_ID, owner1), packedPreValidationHooks, "" ); diff --git a/test/account/SelfCallAuthorization.t.sol b/test/account/SelfCallAuthorization.t.sol index 5bcf64b3..c490eea4 100644 --- a/test/account/SelfCallAuthorization.t.sol +++ b/test/account/SelfCallAuthorization.t.sol @@ -7,7 +7,7 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {IStandardExecutor, Call} from "../../src/interfaces/IStandardExecutor.sol"; -import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; +import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; @@ -16,7 +16,7 @@ import {ComprehensivePlugin} from "../mocks/plugins/ComprehensivePlugin.sol"; contract SelfCallAuthorizationTest is AccountTestBase { ComprehensivePlugin public comprehensivePlugin; - FunctionReference public comprehensivePluginValidation; + PluginEntity public comprehensivePluginValidation; function setUp() public { // install the comprehensive plugin to get new exec functions with different validations configured. @@ -27,9 +27,8 @@ contract SelfCallAuthorizationTest is AccountTestBase { vm.prank(address(entryPoint)); account1.installPlugin(address(comprehensivePlugin), manifestHash, ""); - comprehensivePluginValidation = FunctionReferenceLib.pack( - address(comprehensivePlugin), uint8(ComprehensivePlugin.FunctionId.VALIDATION) - ); + comprehensivePluginValidation = + PluginEntityLib.pack(address(comprehensivePlugin), uint32(ComprehensivePlugin.EntityId.VALIDATION)); } function test_selfCallFails_userOp() public { @@ -304,7 +303,7 @@ contract SelfCallAuthorizationTest is AccountTestBase { UpgradeableModularAccount.installValidation, (ValidationConfigLib.pack(comprehensivePluginValidation, false, false), selectors, "", "", "") ), - _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, "") + _encodeSignature(_signerValidation, GLOBAL_VALIDATION, "") ); } diff --git a/test/account/UpgradeableModularAccount.t.sol b/test/account/UpgradeableModularAccount.t.sol index dda78c66..54af25c6 100644 --- a/test/account/UpgradeableModularAccount.t.sol +++ b/test/account/UpgradeableModularAccount.t.sol @@ -14,14 +14,14 @@ import {PluginManifest} from "../../src/interfaces/IPlugin.sol"; import {IAccountLoupe} from "../../src/interfaces/IAccountLoupe.sol"; import {IPluginManager} from "../../src/interfaces/IPluginManager.sol"; import {Call} from "../../src/interfaces/IStandardExecutor.sol"; -import {SingleOwnerPlugin} from "../../src/plugins/owner/SingleOwnerPlugin.sol"; +import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; import {TokenReceiverPlugin} from "../../src/plugins/TokenReceiverPlugin.sol"; import {Counter} from "../mocks/Counter.sol"; import {ComprehensivePlugin} from "../mocks/plugins/ComprehensivePlugin.sol"; import {MockPlugin} from "../mocks/MockPlugin.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; -import {TEST_DEFAULT_OWNER_FUNCTION_ID} from "../utils/TestConstants.sol"; +import {TEST_DEFAULT_VALIDATION_ENTITY_ID} from "../utils/TestConstants.sol"; contract UpgradeableModularAccountTest is AccountTestBase { using ECDSA for bytes32; @@ -77,7 +77,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature(_signerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -95,9 +95,9 @@ contract UpgradeableModularAccountTest is AccountTestBase { callData: abi.encodeCall( UpgradeableModularAccount.execute, ( - address(singleOwnerPlugin), + address(singleSignerValidation), 0, - abi.encodeCall(SingleOwnerPlugin.transferOwnership, (TEST_DEFAULT_OWNER_FUNCTION_ID, owner2)) + abi.encodeCall(SingleSignerValidation.transferSigner, (TEST_DEFAULT_VALIDATION_ENTITY_ID, owner2)) ) ), accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), @@ -110,7 +110,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature(_signerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -136,7 +136,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature(_signerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -162,7 +162,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature(_signerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -190,7 +190,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature(_signerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -221,7 +221,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { // Generate signature bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); - userOp.signature = _encodeSignature(_ownerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature(_signerValidation, GLOBAL_VALIDATION, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -409,16 +409,16 @@ contract UpgradeableModularAccountTest is AccountTestBase { } function test_transferOwnership() public { - assertEq(singleOwnerPlugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, address(account1)), owner1); + assertEq(singleSignerValidation.signerOf(TEST_DEFAULT_VALIDATION_ENTITY_ID, address(account1)), owner1); vm.prank(address(entryPoint)); account1.execute( - address(singleOwnerPlugin), + address(singleSignerValidation), 0, - abi.encodeCall(SingleOwnerPlugin.transferOwnership, (TEST_DEFAULT_OWNER_FUNCTION_ID, owner2)) + abi.encodeCall(SingleSignerValidation.transferSigner, (TEST_DEFAULT_VALIDATION_ENTITY_ID, owner2)) ); - assertEq(singleOwnerPlugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, address(account1)), owner2); + assertEq(singleSignerValidation.signerOf(TEST_DEFAULT_VALIDATION_ENTITY_ID, address(account1)), owner2); } function test_isValidSignature() public { @@ -426,10 +426,10 @@ contract UpgradeableModularAccountTest is AccountTestBase { (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, message); - // singleOwnerPlugin.ownerOf(address(account1)); + // singleSignerValidation.ownerOf(address(account1)); bytes memory signature = - abi.encodePacked(address(singleOwnerPlugin), TEST_DEFAULT_OWNER_FUNCTION_ID, r, s, v); + abi.encodePacked(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID, r, s, v); bytes4 validationResult = IERC1271(address(account1)).isValidSignature(message, signature); diff --git a/test/account/ValidationIntersection.t.sol b/test/account/ValidationIntersection.t.sol index 7f245031..3bdaa1b6 100644 --- a/test/account/ValidationIntersection.t.sol +++ b/test/account/ValidationIntersection.t.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.19; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; +import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import { @@ -22,28 +22,28 @@ contract ValidationIntersectionTest is AccountTestBase { MockUserOpValidation1HookPlugin public oneHookPlugin; MockUserOpValidation2HookPlugin public twoHookPlugin; - FunctionReference public noHookValidation; - FunctionReference public oneHookValidation; - FunctionReference public twoHookValidation; + PluginEntity public noHookValidation; + PluginEntity public oneHookValidation; + PluginEntity public twoHookValidation; function setUp() public { noHookPlugin = new MockUserOpValidationPlugin(); oneHookPlugin = new MockUserOpValidation1HookPlugin(); twoHookPlugin = new MockUserOpValidation2HookPlugin(); - noHookValidation = FunctionReferenceLib.pack({ + noHookValidation = PluginEntityLib.pack({ addr: address(noHookPlugin), - functionId: uint8(MockBaseUserOpValidationPlugin.FunctionId.USER_OP_VALIDATION) + entityId: uint32(MockBaseUserOpValidationPlugin.EntityId.USER_OP_VALIDATION) }); - oneHookValidation = FunctionReferenceLib.pack({ + oneHookValidation = PluginEntityLib.pack({ addr: address(oneHookPlugin), - functionId: uint8(MockBaseUserOpValidationPlugin.FunctionId.USER_OP_VALIDATION) + entityId: uint32(MockBaseUserOpValidationPlugin.EntityId.USER_OP_VALIDATION) }); - twoHookValidation = FunctionReferenceLib.pack({ + twoHookValidation = PluginEntityLib.pack({ addr: address(twoHookPlugin), - functionId: uint8(MockBaseUserOpValidationPlugin.FunctionId.USER_OP_VALIDATION) + entityId: uint32(MockBaseUserOpValidationPlugin.EntityId.USER_OP_VALIDATION) }); vm.startPrank(address(entryPoint)); @@ -59,10 +59,10 @@ contract ValidationIntersectionTest is AccountTestBase { }); // TODO: change with new install flow // temporary fix to add the pre-validation hook - FunctionReference[] memory preValidationHooks = new FunctionReference[](1); - preValidationHooks[0] = FunctionReferenceLib.pack({ + PluginEntity[] memory preValidationHooks = new PluginEntity[](1); + preValidationHooks[0] = PluginEntityLib.pack({ addr: address(oneHookPlugin), - functionId: uint8(MockBaseUserOpValidationPlugin.FunctionId.PRE_VALIDATION_HOOK_1) + entityId: uint32(MockBaseUserOpValidationPlugin.EntityId.PRE_VALIDATION_HOOK_1) }); bytes[] memory installDatas = new bytes[](1); account1.installValidation( @@ -78,14 +78,14 @@ contract ValidationIntersectionTest is AccountTestBase { pluginInstallData: "" }); // temporary fix to add the pre-validation hook - preValidationHooks = new FunctionReference[](2); - preValidationHooks[0] = FunctionReferenceLib.pack({ + preValidationHooks = new PluginEntity[](2); + preValidationHooks[0] = PluginEntityLib.pack({ addr: address(twoHookPlugin), - functionId: uint8(MockBaseUserOpValidationPlugin.FunctionId.PRE_VALIDATION_HOOK_1) + entityId: uint32(MockBaseUserOpValidationPlugin.EntityId.PRE_VALIDATION_HOOK_1) }); - preValidationHooks[1] = FunctionReferenceLib.pack({ + preValidationHooks[1] = PluginEntityLib.pack({ addr: address(twoHookPlugin), - functionId: uint8(MockBaseUserOpValidationPlugin.FunctionId.PRE_VALIDATION_HOOK_2) + entityId: uint32(MockBaseUserOpValidationPlugin.EntityId.PRE_VALIDATION_HOOK_2) }); installDatas = new bytes[](2); account1.installValidation( @@ -156,7 +156,7 @@ contract ValidationIntersectionTest is AccountTestBase { uint48 end2 = uint48(25); oneHookPlugin.setValidationData( - _packValidationData(address(0), start1, end1), _packValidationData(address(0), start2, end2) + _packValidationRes(address(0), start1, end1), _packValidationRes(address(0), start2, end2) ); PackedUserOperation memory userOp; @@ -167,7 +167,7 @@ contract ValidationIntersectionTest is AccountTestBase { vm.prank(address(entryPoint)); uint256 returnedValidationData = account1.validateUserOp(userOp, uoHash, 1 wei); - assertEq(returnedValidationData, _packValidationData(address(0), start2, end1)); + assertEq(returnedValidationData, _packValidationRes(address(0), start2, end1)); } function test_validationIntersect_timeBounds_intersect_2() public { @@ -178,7 +178,7 @@ contract ValidationIntersectionTest is AccountTestBase { uint48 end2 = uint48(25); oneHookPlugin.setValidationData( - _packValidationData(address(0), start2, end2), _packValidationData(address(0), start1, end1) + _packValidationRes(address(0), start2, end2), _packValidationRes(address(0), start1, end1) ); PackedUserOperation memory userOp; @@ -189,7 +189,7 @@ contract ValidationIntersectionTest is AccountTestBase { vm.prank(address(entryPoint)); uint256 returnedValidationData = account1.validateUserOp(userOp, uoHash, 1 wei); - assertEq(returnedValidationData, _packValidationData(address(0), start2, end1)); + assertEq(returnedValidationData, _packValidationRes(address(0), start2, end1)); } function test_validationIntersect_revert_unexpectedAuthorizer() public { @@ -211,7 +211,7 @@ contract ValidationIntersectionTest is AccountTestBase { abi.encodeWithSelector( UpgradeableModularAccount.UnexpectedAggregator.selector, address(oneHookPlugin), - MockBaseUserOpValidationPlugin.FunctionId.PRE_VALIDATION_HOOK_1, + MockBaseUserOpValidationPlugin.EntityId.PRE_VALIDATION_HOOK_1, badAuthorizer ) ); @@ -247,7 +247,7 @@ contract ValidationIntersectionTest is AccountTestBase { address goodAuthorizer = makeAddr("goodAuthorizer"); oneHookPlugin.setValidationData( - _packValidationData(goodAuthorizer, start1, end1), _packValidationData(address(0), start2, end2) + _packValidationRes(goodAuthorizer, start1, end1), _packValidationRes(address(0), start2, end2) ); PackedUserOperation memory userOp; @@ -258,7 +258,7 @@ contract ValidationIntersectionTest is AccountTestBase { vm.prank(address(entryPoint)); uint256 returnedValidationData = account1.validateUserOp(userOp, uoHash, 1 wei); - assertEq(returnedValidationData, _packValidationData(goodAuthorizer, start2, end1)); + assertEq(returnedValidationData, _packValidationRes(goodAuthorizer, start2, end1)); } function test_validationIntersect_multiplePreValidationHooksIntersect() public { @@ -270,8 +270,8 @@ contract ValidationIntersectionTest is AccountTestBase { twoHookPlugin.setValidationData( 0, // returns OK - _packValidationData(address(0), start1, end1), - _packValidationData(address(0), start2, end2) + _packValidationRes(address(0), start1, end1), + _packValidationRes(address(0), start2, end2) ); PackedUserOperation memory userOp; @@ -282,7 +282,7 @@ contract ValidationIntersectionTest is AccountTestBase { vm.prank(address(entryPoint)); uint256 returnedValidationData = account1.validateUserOp(userOp, uoHash, 1 wei); - assertEq(returnedValidationData, _packValidationData(address(0), start2, end1)); + assertEq(returnedValidationData, _packValidationRes(address(0), start2, end1)); } function test_validationIntersect_multiplePreValidationHooksSigFail() public { @@ -318,7 +318,7 @@ contract ValidationIntersectionTest is AccountTestBase { validAfter = uint48(validationData >> (48 + 160)); } - function _packValidationData(address authorizer, uint48 validAfter, uint48 validUntil) + function _packValidationRes(address authorizer, uint48 validAfter, uint48 validUntil) internal pure returns (uint256) diff --git a/test/libraries/FunctionReferenceLib.t.sol b/test/libraries/FunctionReferenceLib.t.sol deleted file mode 100644 index 6471fbd0..00000000 --- a/test/libraries/FunctionReferenceLib.t.sol +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; - -import {Test} from "forge-std/Test.sol"; - -import {FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; -import {FunctionReference} from "../../src/interfaces/IPluginManager.sol"; - -contract FunctionReferenceLibTest is Test { - using FunctionReferenceLib for FunctionReference; - - function testFuzz_functionReference_packing(address addr, uint8 functionId) public { - // console.log("addr: ", addr); - // console.log("functionId: ", vm.toString(functionId)); - FunctionReference fr = FunctionReferenceLib.pack(addr, functionId); - // console.log("packed: ", vm.toString(FunctionReference.unwrap(fr))); - (address addr2, uint8 functionId2) = FunctionReferenceLib.unpack(fr); - // console.log("addr2: ", addr2); - // console.log("functionId2: ", vm.toString(functionId2)); - assertEq(addr, addr2); - assertEq(functionId, functionId2); - } - - function testFuzz_functionReference_operators(FunctionReference a, FunctionReference b) public { - assertTrue(a.eq(a)); - assertTrue(b.eq(b)); - - if (FunctionReference.unwrap(a) == FunctionReference.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/mocks/MSCAFactoryFixture.sol b/test/mocks/SingleSignerFactoryFixture.sol similarity index 79% rename from test/mocks/MSCAFactoryFixture.sol rename to test/mocks/SingleSignerFactoryFixture.sol index 8ca3a51f..b3da73ec 100644 --- a/test/mocks/MSCAFactoryFixture.sol +++ b/test/mocks/SingleSignerFactoryFixture.sol @@ -5,34 +5,32 @@ import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; -import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; -import {SingleOwnerPlugin} from "../../src/plugins/owner/SingleOwnerPlugin.sol"; +import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; +import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; import {OptimizedTest} from "../utils/OptimizedTest.sol"; -import {TEST_DEFAULT_OWNER_FUNCTION_ID} from "../utils/TestConstants.sol"; +import {TEST_DEFAULT_VALIDATION_ENTITY_ID} from "../utils/TestConstants.sol"; -/** - * @title MSCAFactoryFixture - * @dev a factory that initializes UpgradeableModularAccounts with a single plugin, SingleOwnerPlugin - * intended for unit tests and local development, not for production. - */ -contract MSCAFactoryFixture is OptimizedTest { +contract SingleSignerFactoryFixture is OptimizedTest { UpgradeableModularAccount public accountImplementation; - SingleOwnerPlugin public singleOwnerPlugin; + SingleSignerValidation public singleSignerValidation; bytes32 private immutable _PROXY_BYTECODE_HASH; uint32 public constant UNSTAKE_DELAY = 1 weeks; IEntryPoint public entryPoint; - constructor(IEntryPoint _entryPoint, SingleOwnerPlugin _singleOwnerPlugin) { + address public self; + + constructor(IEntryPoint _entryPoint, SingleSignerValidation _singleSignerValidation) { entryPoint = _entryPoint; accountImplementation = _deployUpgradeableModularAccount(_entryPoint); _PROXY_BYTECODE_HASH = keccak256( abi.encodePacked(type(ERC1967Proxy).creationCode, abi.encode(address(accountImplementation), "")) ); - singleOwnerPlugin = _singleOwnerPlugin; + singleSignerValidation = _singleSignerValidation; + self = address(this); } /** @@ -47,13 +45,15 @@ contract MSCAFactoryFixture is OptimizedTest { // short circuit if exists if (addr.code.length == 0) { - bytes memory pluginInstallData = abi.encode(TEST_DEFAULT_OWNER_FUNCTION_ID, owner); + bytes memory pluginInstallData = abi.encode(TEST_DEFAULT_VALIDATION_ENTITY_ID, owner); // not necessary to check return addr since next call will fail if so new ERC1967Proxy{salt: getSalt(owner, salt)}(address(accountImplementation), ""); // point proxy to actual implementation and init plugins UpgradeableModularAccount(payable(addr)).initializeWithValidation( - ValidationConfigLib.pack(address(singleOwnerPlugin), TEST_DEFAULT_OWNER_FUNCTION_ID, true, true), + ValidationConfigLib.pack( + address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID, true, true + ), new bytes4[](0), pluginInstallData, "", diff --git a/test/mocks/plugins/ComprehensivePlugin.sol b/test/mocks/plugins/ComprehensivePlugin.sol index cd455c88..306e96a4 100644 --- a/test/mocks/plugins/ComprehensivePlugin.sol +++ b/test/mocks/plugins/ComprehensivePlugin.sol @@ -18,7 +18,7 @@ import {IExecutionHook} from "../../../src/interfaces/IExecutionHook.sol"; import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; contract ComprehensivePlugin is IValidation, IValidationHook, IExecutionHook, BasePlugin { - enum FunctionId { + enum EntityId { PRE_VALIDATION_HOOK_1, PRE_VALIDATION_HOOK_2, VALIDATION, @@ -46,85 +46,85 @@ contract ComprehensivePlugin is IValidation, IValidationHook, IExecutionHook, Ba function onUninstall(bytes calldata) external override {} - function preUserOpValidationHook(uint8 functionId, PackedUserOperation calldata, bytes32) + function preUserOpValidationHook(uint32 entityId, PackedUserOperation calldata, bytes32) external pure override returns (uint256) { - if (functionId == uint8(FunctionId.PRE_VALIDATION_HOOK_1)) { + if (entityId == uint32(EntityId.PRE_VALIDATION_HOOK_1)) { return 0; - } else if (functionId == uint8(FunctionId.PRE_VALIDATION_HOOK_2)) { + } else if (entityId == uint32(EntityId.PRE_VALIDATION_HOOK_2)) { return 0; } revert NotImplemented(); } - function validateUserOp(uint8 functionId, PackedUserOperation calldata, bytes32) + function validateUserOp(uint32 entityId, PackedUserOperation calldata, bytes32) external pure override returns (uint256) { - if (functionId == uint8(FunctionId.VALIDATION)) { + if (entityId == uint32(EntityId.VALIDATION)) { return 0; } revert NotImplemented(); } - function preRuntimeValidationHook(uint8 functionId, address, uint256, bytes calldata, bytes calldata) + function preRuntimeValidationHook(uint32 entityId, address, uint256, bytes calldata, bytes calldata) external pure override { - if (functionId == uint8(FunctionId.PRE_VALIDATION_HOOK_1)) { + if (entityId == uint32(EntityId.PRE_VALIDATION_HOOK_1)) { return; - } else if (functionId == uint8(FunctionId.PRE_VALIDATION_HOOK_2)) { + } else if (entityId == uint32(EntityId.PRE_VALIDATION_HOOK_2)) { return; } revert NotImplemented(); } - function validateRuntime(uint8 functionId, address, uint256, bytes calldata, bytes calldata) + function validateRuntime(address, uint32 entityId, address, uint256, bytes calldata, bytes calldata) external pure override { - if (functionId == uint8(FunctionId.VALIDATION)) { + if (entityId == uint32(EntityId.VALIDATION)) { return; } revert NotImplemented(); } - function validateSignature(uint8 functionId, address, bytes32, bytes calldata) + function validateSignature(address, uint32 entityId, address, bytes32, bytes calldata) external pure returns (bytes4) { - if (functionId == uint8(FunctionId.SIG_VALIDATION)) { + if (entityId == uint32(EntityId.SIG_VALIDATION)) { return 0xffffffff; } revert NotImplemented(); } - function preExecutionHook(uint8 functionId, address, uint256, bytes calldata) + function preExecutionHook(uint32 entityId, address, uint256, bytes calldata) external pure override returns (bytes memory) { - if (functionId == uint8(FunctionId.PRE_EXECUTION_HOOK)) { + if (entityId == uint32(EntityId.PRE_EXECUTION_HOOK)) { return ""; - } else if (functionId == uint8(FunctionId.BOTH_EXECUTION_HOOKS)) { + } else if (entityId == uint32(EntityId.BOTH_EXECUTION_HOOKS)) { return ""; } revert NotImplemented(); } - function postExecutionHook(uint8 functionId, bytes calldata) external pure override { - if (functionId == uint8(FunctionId.POST_EXECUTION_HOOK)) { + function postExecutionHook(uint32 entityId, bytes calldata) external pure override { + if (entityId == uint32(EntityId.POST_EXECUTION_HOOK)) { return; - } else if (functionId == uint8(FunctionId.BOTH_EXECUTION_HOOKS)) { + } else if (entityId == uint32(EntityId.BOTH_EXECUTION_HOOKS)) { return; } revert NotImplemented(); @@ -145,7 +145,7 @@ contract ComprehensivePlugin is IValidation, IValidationHook, IExecutionHook, Ba manifest.validationFunctions = new ManifestValidation[](1); manifest.validationFunctions[0] = ManifestValidation({ - functionId: uint8(FunctionId.VALIDATION), + entityId: uint32(EntityId.VALIDATION), isDefault: true, isSignatureValidation: false, selectors: validationSelectors @@ -154,19 +154,19 @@ contract ComprehensivePlugin is IValidation, IValidationHook, IExecutionHook, Ba manifest.executionHooks = new ManifestExecutionHook[](3); manifest.executionHooks[0] = ManifestExecutionHook({ executionSelector: this.foo.selector, - functionId: uint8(FunctionId.BOTH_EXECUTION_HOOKS), + entityId: uint32(EntityId.BOTH_EXECUTION_HOOKS), isPreHook: true, isPostHook: true }); manifest.executionHooks[1] = ManifestExecutionHook({ executionSelector: this.foo.selector, - functionId: uint8(FunctionId.PRE_EXECUTION_HOOK), + entityId: uint32(EntityId.PRE_EXECUTION_HOOK), isPreHook: true, isPostHook: false }); manifest.executionHooks[2] = ManifestExecutionHook({ executionSelector: this.foo.selector, - functionId: uint8(FunctionId.POST_EXECUTION_HOOK), + entityId: uint32(EntityId.POST_EXECUTION_HOOK), isPreHook: false, isPostHook: true }); diff --git a/test/mocks/plugins/MockAccessControlHookPlugin.sol b/test/mocks/plugins/MockAccessControlHookPlugin.sol index c17868a8..6bd593f2 100644 --- a/test/mocks/plugins/MockAccessControlHookPlugin.sol +++ b/test/mocks/plugins/MockAccessControlHookPlugin.sol @@ -13,7 +13,7 @@ import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; // This is just a mock - it does not enforce this over `executeBatch` and other methods of making calls, and should // not be used in production.. contract MockAccessControlHookPlugin is IValidationHook, BasePlugin { - enum FunctionId { + enum EntityId { PRE_VALIDATION_HOOK } @@ -28,13 +28,13 @@ contract MockAccessControlHookPlugin is IValidationHook, BasePlugin { delete allowedTargets[msg.sender]; } - function preUserOpValidationHook(uint8 functionId, PackedUserOperation calldata userOp, bytes32) + function preUserOpValidationHook(uint32 entityId, PackedUserOperation calldata userOp, bytes32) external view override returns (uint256) { - if (functionId == uint8(FunctionId.PRE_VALIDATION_HOOK)) { + if (entityId == uint32(EntityId.PRE_VALIDATION_HOOK)) { if (bytes4(userOp.callData[:4]) == IStandardExecutor.execute.selector) { address target = abi.decode(userOp.callData[4:36], (address)); @@ -49,13 +49,13 @@ contract MockAccessControlHookPlugin is IValidationHook, BasePlugin { } function preRuntimeValidationHook( - uint8 functionId, + uint32 entityId, address, uint256, bytes calldata data, bytes calldata authorization ) external view override { - if (functionId == uint8(FunctionId.PRE_VALIDATION_HOOK)) { + if (entityId == uint32(EntityId.PRE_VALIDATION_HOOK)) { if (bytes4(data[:4]) == IStandardExecutor.execute.selector) { address target = abi.decode(data[4:36], (address)); diff --git a/test/mocks/plugins/ReturnDataPluginMocks.sol b/test/mocks/plugins/ReturnDataPluginMocks.sol index 211f79af..3adbb6e4 100644 --- a/test/mocks/plugins/ReturnDataPluginMocks.sol +++ b/test/mocks/plugins/ReturnDataPluginMocks.sol @@ -72,17 +72,20 @@ contract ResultConsumerPlugin is BasePlugin, IValidation { // Validation function implementations. We only care about the runtime validation function, to authorize // itself. - function validateUserOp(uint8, PackedUserOperation calldata, bytes32) external pure returns (uint256) { + function validateUserOp(uint32, PackedUserOperation calldata, bytes32) external pure returns (uint256) { revert NotImplemented(); } - function validateRuntime(uint8, address sender, uint256, bytes calldata, bytes calldata) external view { + function validateRuntime(address, uint32, address sender, uint256, bytes calldata, bytes calldata) + external + view + { if (sender != address(this)) { revert NotAuthorized(); } } - function validateSignature(uint8, address, bytes32, bytes calldata) external pure returns (bytes4) { + function validateSignature(address, uint32, address, bytes32, bytes calldata) external pure returns (bytes4) { revert NotImplemented(); } @@ -99,7 +102,7 @@ contract ResultConsumerPlugin is BasePlugin, IValidation { // This result should be allowed based on the manifest permission request bytes memory returnData = IStandardExecutor(msg.sender).executeWithAuthorization( abi.encodeCall(IStandardExecutor.execute, (target, 0, abi.encodeCall(RegularResultContract.foo, ()))), - abi.encodePacked(this, uint8(0), uint8(0), uint32(1), uint8(255)) // Validation function of self, + abi.encodePacked(this, uint32(0), uint8(0), uint32(1), uint8(255)) // Validation function of self, // selector-associated, with no auth data ); @@ -121,7 +124,7 @@ contract ResultConsumerPlugin is BasePlugin, IValidation { manifest.validationFunctions = new ManifestValidation[](1); manifest.validationFunctions[0] = ManifestValidation({ - functionId: 0, + entityId: 0, isDefault: true, isSignatureValidation: false, selectors: validationSelectors diff --git a/test/mocks/plugins/ValidationPluginMocks.sol b/test/mocks/plugins/ValidationPluginMocks.sol index a59d5ee3..8d9b6ce2 100644 --- a/test/mocks/plugins/ValidationPluginMocks.sol +++ b/test/mocks/plugins/ValidationPluginMocks.sol @@ -14,7 +14,7 @@ import {IValidationHook} from "../../../src/interfaces/IValidationHook.sol"; import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; abstract contract MockBaseUserOpValidationPlugin is IValidation, IValidationHook, BasePlugin { - enum FunctionId { + enum EntityId { USER_OP_VALIDATION, PRE_VALIDATION_HOOK_1, PRE_VALIDATION_HOOK_2 @@ -32,40 +32,45 @@ abstract contract MockBaseUserOpValidationPlugin is IValidation, IValidationHook function onUninstall(bytes calldata) external override {} - function preUserOpValidationHook(uint8 functionId, PackedUserOperation calldata, bytes32) + function preUserOpValidationHook(uint32 entityId, PackedUserOperation calldata, bytes32) external view override returns (uint256) { - if (functionId == uint8(FunctionId.PRE_VALIDATION_HOOK_1)) { + if (entityId == uint32(EntityId.PRE_VALIDATION_HOOK_1)) { return _preUserOpValidationHook1Data; - } else if (functionId == uint8(FunctionId.PRE_VALIDATION_HOOK_2)) { + } else if (entityId == uint32(EntityId.PRE_VALIDATION_HOOK_2)) { return _preUserOpValidationHook2Data; } revert NotImplemented(); } - function validateUserOp(uint8 functionId, PackedUserOperation calldata, bytes32) + function validateUserOp(uint32 entityId, PackedUserOperation calldata, bytes32) external view override returns (uint256) { - if (functionId == uint8(FunctionId.USER_OP_VALIDATION)) { + if (entityId == uint32(EntityId.USER_OP_VALIDATION)) { return _userOpValidationFunctionData; } revert NotImplemented(); } - function validateSignature(uint8, address, bytes32, bytes calldata) external pure override returns (bytes4) { + function validateSignature(address, uint32, address, bytes32, bytes calldata) + external + pure + override + returns (bytes4) + { revert NotImplemented(); } // Empty stubs function pluginMetadata() external pure override returns (PluginMetadata memory) {} - function preRuntimeValidationHook(uint8, address, uint256, bytes calldata, bytes calldata) + function preRuntimeValidationHook(uint32, address, uint256, bytes calldata, bytes calldata) external pure override @@ -73,7 +78,11 @@ abstract contract MockBaseUserOpValidationPlugin is IValidation, IValidationHook revert NotImplemented(); } - function validateRuntime(uint8, address, uint256, bytes calldata, bytes calldata) external pure override { + function validateRuntime(address, uint32, address, uint256, bytes calldata, bytes calldata) + external + pure + override + { revert NotImplemented(); } } @@ -108,7 +117,7 @@ contract MockUserOpValidationPlugin is MockBaseUserOpValidationPlugin { manifest.validationFunctions = new ManifestValidation[](1); manifest.validationFunctions[0] = ManifestValidation({ - functionId: uint8(FunctionId.USER_OP_VALIDATION), + entityId: uint32(EntityId.USER_OP_VALIDATION), isDefault: false, isSignatureValidation: false, selectors: validationSelectors @@ -151,7 +160,7 @@ contract MockUserOpValidation1HookPlugin is MockBaseUserOpValidationPlugin { manifest.validationFunctions = new ManifestValidation[](2); manifest.validationFunctions[0] = ManifestValidation({ - functionId: uint8(FunctionId.USER_OP_VALIDATION), + entityId: uint32(EntityId.USER_OP_VALIDATION), isDefault: false, isSignatureValidation: false, selectors: validationSelectors @@ -197,7 +206,7 @@ contract MockUserOpValidation2HookPlugin is MockBaseUserOpValidationPlugin { manifest.validationFunctions = new ManifestValidation[](1); manifest.validationFunctions[0] = ManifestValidation({ - functionId: uint8(FunctionId.USER_OP_VALIDATION), + entityId: uint32(EntityId.USER_OP_VALIDATION), isDefault: false, isSignatureValidation: false, selectors: validationSelectors diff --git a/test/plugin/ERC20TokenLimitPlugin.t.sol b/test/plugin/ERC20TokenLimitPlugin.t.sol index 96a18c20..9693f98f 100644 --- a/test/plugin/ERC20TokenLimitPlugin.t.sol +++ b/test/plugin/ERC20TokenLimitPlugin.t.sol @@ -6,16 +6,15 @@ import {MockERC20} from "../mocks/MockERC20.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {FunctionReference} from "../../src/helpers/FunctionReferenceLib.sol"; +import {PluginEntity} from "../../src/helpers/PluginEntityLib.sol"; import {ERC20TokenLimitPlugin} from "../../src/plugins/ERC20TokenLimitPlugin.sol"; import {MockPlugin} from "../mocks/MockPlugin.sol"; import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; -import {FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; +import {PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; import {IStandardExecutor, Call} from "../../src/interfaces/IStandardExecutor.sol"; import {PluginManifest} from "../../src/interfaces/IPlugin.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; -import {MSCAFactoryFixture} from "../mocks/MSCAFactoryFixture.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; contract ERC20TokenLimitPluginTest is AccountTestBase { @@ -24,7 +23,7 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { address payable public bundler = payable(address(2)); PluginManifest internal _m; MockPlugin public validationPlugin = new MockPlugin(_m); - FunctionReference public validationFunction; + PluginEntity public validationFunction; UpgradeableModularAccount public acct; ERC20TokenLimitPlugin public plugin = new ERC20TokenLimitPlugin(); @@ -32,8 +31,6 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { function setUp() public { // Set up a validator with hooks from the erc20 spend limit plugin attached - MSCAFactoryFixture factory = new MSCAFactoryFixture(entryPoint, _deploySingleOwnerPlugin()); - acct = factory.createAccount(address(this), 0); erc20 = new MockERC20(); @@ -41,7 +38,7 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { ExecutionHook[] memory permissionHooks = new ExecutionHook[](1); permissionHooks[0] = ExecutionHook({ - hookFunction: FunctionReferenceLib.pack(address(plugin), 0), + hookFunction: PluginEntityLib.pack(address(plugin), 0), isPreHook: true, isPostHook: false }); @@ -65,7 +62,7 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { abi.encode(permissionHooks, permissionInitDatas) ); - validationFunction = FunctionReferenceLib.pack(address(validationPlugin), 0); + validationFunction = PluginEntityLib.pack(address(validationPlugin), 0); } function _getPackedUO(bytes memory callData) internal view returns (PackedUserOperation memory uo) { @@ -78,7 +75,7 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { preVerificationGas: 200000, gasFees: bytes32(uint256(uint128(0))), paymasterAndData: "", - signature: _encodeSignature(FunctionReferenceLib.pack(address(validationPlugin), 0), 1, "") + signature: _encodeSignature(PluginEntityLib.pack(address(validationPlugin), 0), 1, "") }); } @@ -157,7 +154,7 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether); acct.executeWithAuthorization( _getExecuteWithSpend(5 ether), - _encodeSignature(FunctionReferenceLib.pack(address(validationPlugin), 0), 1, "") + _encodeSignature(PluginEntityLib.pack(address(validationPlugin), 0), 1, "") ); assertEq(plugin.limits(0, address(erc20), address(acct)), 5 ether); } @@ -177,7 +174,7 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether); acct.executeWithAuthorization( abi.encodeCall(IStandardExecutor.executeBatch, (calls)), - _encodeSignature(FunctionReferenceLib.pack(address(validationPlugin), 0), 1, "") + _encodeSignature(PluginEntityLib.pack(address(validationPlugin), 0), 1, "") ); assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether - 6 ether - 100001); } diff --git a/test/plugin/NativeTokenLimitPlugin.t.sol b/test/plugin/NativeTokenLimitPlugin.t.sol index 5fbf2a3d..33635216 100644 --- a/test/plugin/NativeTokenLimitPlugin.t.sol +++ b/test/plugin/NativeTokenLimitPlugin.t.sol @@ -4,16 +4,15 @@ pragma solidity ^0.8.19; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {FunctionReference} from "../../src/helpers/FunctionReferenceLib.sol"; +import {PluginEntity} from "../../src/helpers/PluginEntityLib.sol"; import {NativeTokenLimitPlugin} from "../../src/plugins/NativeTokenLimitPlugin.sol"; import {MockPlugin} from "../mocks/MockPlugin.sol"; import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; -import {FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; +import {PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; import {IStandardExecutor, Call} from "../../src/interfaces/IStandardExecutor.sol"; import {PluginManifest} from "../../src/interfaces/IPlugin.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; -import {MSCAFactoryFixture} from "../mocks/MSCAFactoryFixture.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; contract NativeTokenLimitPluginTest is AccountTestBase { @@ -21,7 +20,7 @@ contract NativeTokenLimitPluginTest is AccountTestBase { address payable public bundler = payable(address(2)); PluginManifest internal _m; MockPlugin public validationPlugin = new MockPlugin(_m); - FunctionReference public validationFunction; + PluginEntity public validationFunction; UpgradeableModularAccount public acct; NativeTokenLimitPlugin public plugin = new NativeTokenLimitPlugin(); @@ -30,18 +29,16 @@ contract NativeTokenLimitPluginTest is AccountTestBase { function setUp() public { // Set up a validator with hooks from the gas spend limit plugin attached - MSCAFactoryFixture factory = new MSCAFactoryFixture(entryPoint, _deploySingleOwnerPlugin()); - acct = factory.createAccount(address(this), 0); vm.deal(address(acct), 10 ether); - FunctionReference[] memory preValidationHooks = new FunctionReference[](1); - preValidationHooks[0] = FunctionReferenceLib.pack(address(plugin), 0); + PluginEntity[] memory preValidationHooks = new PluginEntity[](1); + preValidationHooks[0] = PluginEntityLib.pack(address(plugin), 0); ExecutionHook[] memory permissionHooks = new ExecutionHook[](1); permissionHooks[0] = ExecutionHook({ - hookFunction: FunctionReferenceLib.pack(address(plugin), 0), + hookFunction: PluginEntityLib.pack(address(plugin), 0), isPreHook: true, isPostHook: false }); @@ -64,7 +61,7 @@ contract NativeTokenLimitPluginTest is AccountTestBase { abi.encode(permissionHooks, permissionInitDatas) ); - validationFunction = FunctionReferenceLib.pack(address(validationPlugin), 0); + validationFunction = PluginEntityLib.pack(address(validationPlugin), 0); } function _getExecuteWithValue(uint256 value) internal view returns (bytes memory) { @@ -85,7 +82,7 @@ contract NativeTokenLimitPluginTest is AccountTestBase { preVerificationGas: gas3, gasFees: bytes32(uint256(uint128(gasPrice))), paymasterAndData: "", - signature: _encodeSignature(FunctionReferenceLib.pack(address(validationPlugin), 0), 1, "") + signature: _encodeSignature(PluginEntityLib.pack(address(validationPlugin), 0), 1, "") }); } diff --git a/test/plugin/SingleOwnerPlugin.t.sol b/test/plugin/SingleOwnerPlugin.t.sol deleted file mode 100644 index ddfe4d41..00000000 --- a/test/plugin/SingleOwnerPlugin.t.sol +++ /dev/null @@ -1,186 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; - -import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.sol"; -import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; -import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; - -import {SingleOwnerPlugin} from "../../src/plugins/owner/SingleOwnerPlugin.sol"; - -import {ContractOwner} from "../mocks/ContractOwner.sol"; -import {OptimizedTest} from "../utils/OptimizedTest.sol"; -import {TEST_DEFAULT_OWNER_FUNCTION_ID} from "../utils/TestConstants.sol"; - -contract SingleOwnerPluginTest is OptimizedTest { - using ECDSA for bytes32; - using MessageHashUtils for bytes32; - - SingleOwnerPlugin public plugin; - EntryPoint public entryPoint; - - bytes4 internal constant _1271_MAGIC_VALUE = 0x1626ba7e; - address public a; - address public b; - - address public owner1; - address public owner2; - ContractOwner public contractOwner; - - // Event declarations (needed for vm.expectEmit) - event OwnershipTransferred(address indexed account, address indexed previousOwner, address indexed newOwner); - - function setUp() public { - plugin = _deploySingleOwnerPlugin(); - entryPoint = new EntryPoint(); - - a = makeAddr("a"); - b = makeAddr("b"); - owner1 = makeAddr("owner1"); - owner2 = makeAddr("owner2"); - contractOwner = new ContractOwner(); - } - - // Tests: - // - uninitialized owner is zero address - // - transferOwnership result is returned via owner afterwards - // - transferOwnership emits OwnershipTransferred event - // - owner() returns correct value after transferOwnership - // - owner() does not return a different account's owner - // - requireFromOwner succeeds when called by owner - // - requireFromOwner reverts when called by non-owner - - function test_uninitializedOwner() public { - vm.startPrank(a); - assertEq(address(0), plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); - } - - function test_ownerInitialization() public { - vm.startPrank(a); - assertEq(address(0), plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); - plugin.transferOwnership(TEST_DEFAULT_OWNER_FUNCTION_ID, owner1); - assertEq(owner1, plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); - } - - function test_ownerInitializationEvent() public { - vm.startPrank(a); - assertEq(address(0), plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); - - vm.expectEmit(true, true, true, true); - emit OwnershipTransferred(a, address(0), owner1); - - plugin.transferOwnership(TEST_DEFAULT_OWNER_FUNCTION_ID, owner1); - assertEq(owner1, plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); - } - - function test_ownerMigration() public { - vm.startPrank(a); - assertEq(address(0), plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); - plugin.transferOwnership(TEST_DEFAULT_OWNER_FUNCTION_ID, owner1); - assertEq(owner1, plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); - plugin.transferOwnership(TEST_DEFAULT_OWNER_FUNCTION_ID, owner2); - assertEq(owner2, plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); - } - - function test_ownerMigrationEvents() public { - vm.startPrank(a); - assertEq(address(0), plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); - - vm.expectEmit(true, true, true, true); - emit OwnershipTransferred(a, address(0), owner1); - - plugin.transferOwnership(TEST_DEFAULT_OWNER_FUNCTION_ID, owner1); - assertEq(owner1, plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); - - vm.expectEmit(true, true, true, true); - emit OwnershipTransferred(a, owner1, owner2); - - plugin.transferOwnership(TEST_DEFAULT_OWNER_FUNCTION_ID, owner2); - assertEq(owner2, plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); - } - - function test_ownerForSender() public { - vm.startPrank(a); - assertEq(address(0), plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); - plugin.transferOwnership(TEST_DEFAULT_OWNER_FUNCTION_ID, owner1); - assertEq(owner1, plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); - vm.startPrank(b); - assertEq(address(0), plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, b)); - plugin.transferOwnership(TEST_DEFAULT_OWNER_FUNCTION_ID, owner2); - assertEq(owner2, plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, b)); - } - - function test_requireOwner() public { - vm.startPrank(a); - assertEq(address(0), plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); - plugin.transferOwnership(TEST_DEFAULT_OWNER_FUNCTION_ID, owner1); - assertEq(owner1, plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); - plugin.validateRuntime(TEST_DEFAULT_OWNER_FUNCTION_ID, owner1, 0, "", ""); - - vm.startPrank(b); - vm.expectRevert(SingleOwnerPlugin.NotAuthorized.selector); - plugin.validateRuntime(TEST_DEFAULT_OWNER_FUNCTION_ID, owner1, 0, "", ""); - } - - function testFuzz_validateUserOpSig(string memory salt, PackedUserOperation memory userOp) public { - // range bound the possible set of priv keys - (address signer, uint256 privateKey) = makeAddrAndKey(salt); - - vm.startPrank(a); - bytes32 userOpHash = entryPoint.getUserOpHash(userOp); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, userOpHash.toEthSignedMessageHash()); - - // sig cannot cover the whole userop struct since userop struct has sig field - userOp.signature = abi.encodePacked(r, s, v); - - // sig check should fail - uint256 success = plugin.validateUserOp(TEST_DEFAULT_OWNER_FUNCTION_ID, userOp, userOpHash); - assertEq(success, 1); - - // transfer ownership to signer - plugin.transferOwnership(TEST_DEFAULT_OWNER_FUNCTION_ID, signer); - assertEq(signer, plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); - - // sig check should pass - success = plugin.validateUserOp(TEST_DEFAULT_OWNER_FUNCTION_ID, userOp, userOpHash); - assertEq(success, 0); - } - - function testFuzz_isValidSignatureForEOAOwner(string memory salt, bytes32 digest) public { - // range bound the possible set of priv keys - (address signer, uint256 privateKey) = makeAddrAndKey(salt); - - vm.startPrank(a); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest); - - // sig check should fail - assertEq( - plugin.validateSignature( - TEST_DEFAULT_OWNER_FUNCTION_ID, address(this), digest, abi.encodePacked(r, s, v) - ), - bytes4(0xFFFFFFFF) - ); - - // transfer ownership to signer - plugin.transferOwnership(TEST_DEFAULT_OWNER_FUNCTION_ID, signer); - assertEq(signer, plugin.owners(TEST_DEFAULT_OWNER_FUNCTION_ID, a)); - - // sig check should pass - assertEq( - plugin.validateSignature( - TEST_DEFAULT_OWNER_FUNCTION_ID, address(this), digest, abi.encodePacked(r, s, v) - ), - _1271_MAGIC_VALUE - ); - } - - function testFuzz_isValidSignatureForContractOwner(bytes32 digest) public { - vm.startPrank(a); - plugin.transferOwnership(TEST_DEFAULT_OWNER_FUNCTION_ID, address(contractOwner)); - bytes memory signature = contractOwner.sign(digest); - assertEq( - plugin.validateSignature(TEST_DEFAULT_OWNER_FUNCTION_ID, address(this), digest, signature), - _1271_MAGIC_VALUE - ); - } -} diff --git a/test/plugin/TokenReceiverPlugin.t.sol b/test/plugin/TokenReceiverPlugin.t.sol index 2f52a988..1e198f0b 100644 --- a/test/plugin/TokenReceiverPlugin.t.sol +++ b/test/plugin/TokenReceiverPlugin.t.sol @@ -8,7 +8,7 @@ import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Re import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {TokenReceiverPlugin} from "../../src/plugins/TokenReceiverPlugin.sol"; -import {MSCAFactoryFixture} from "../mocks/MSCAFactoryFixture.sol"; +import {SingleSignerFactoryFixture} from "../mocks/SingleSignerFactoryFixture.sol"; import {MockERC721} from "../mocks/MockERC721.sol"; import {MockERC1155} from "../mocks/MockERC1155.sol"; import {OptimizedTest} from "../utils/OptimizedTest.sol"; @@ -33,7 +33,8 @@ contract TokenReceiverPluginTest is OptimizedTest, IERC1155Receiver { function setUp() public { entryPoint = new EntryPoint(); - MSCAFactoryFixture factory = new MSCAFactoryFixture(entryPoint, _deploySingleOwnerPlugin()); + SingleSignerFactoryFixture factory = + new SingleSignerFactoryFixture(entryPoint, _deploySingleSignerValidation()); acct = factory.createAccount(address(this), 0); plugin = _deployTokenReceiverPlugin(); diff --git a/test/samples/AllowlistPlugin.t.sol b/test/samples/AllowlistPlugin.t.sol index d81d5f79..441fbdcb 100644 --- a/test/samples/AllowlistPlugin.t.sol +++ b/test/samples/AllowlistPlugin.t.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.25; import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; import {Call} from "../../src/interfaces/IStandardExecutor.sol"; -import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; +import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {AllowlistPlugin} from "../../src/samples/permissionhooks/AllowlistPlugin.sol"; @@ -184,7 +184,7 @@ contract AllowlistPluginTest is CustomValidationTestBase { return abi.encodeWithSelector( UpgradeableModularAccount.PreRuntimeValidationHookFailed.selector, address(allowlistPlugin), - uint8(AllowlistPlugin.FunctionId.PRE_VALIDATION_HOOK), + uint32(AllowlistPlugin.EntityId.PRE_VALIDATION_HOOK), abi.encodeWithSelector(AllowlistPlugin.SelectorNotAllowed.selector) ); } @@ -192,7 +192,7 @@ contract AllowlistPluginTest is CustomValidationTestBase { return abi.encodeWithSelector( UpgradeableModularAccount.PreRuntimeValidationHookFailed.selector, address(allowlistPlugin), - uint8(AllowlistPlugin.FunctionId.PRE_VALIDATION_HOOK), + uint32(AllowlistPlugin.EntityId.PRE_VALIDATION_HOOK), abi.encodeWithSelector(AllowlistPlugin.TargetNotAllowed.selector) ); } @@ -290,13 +290,12 @@ contract AllowlistPluginTest is CustomValidationTestBase { internal virtual override - returns (FunctionReference, bool, bool, bytes4[] memory, bytes memory, bytes memory, bytes memory) + returns (PluginEntity, bool, bool, bytes4[] memory, bytes memory, bytes memory, bytes memory) { - FunctionReference accessControlHook = FunctionReferenceLib.pack( - address(allowlistPlugin), uint8(AllowlistPlugin.FunctionId.PRE_VALIDATION_HOOK) - ); + PluginEntity accessControlHook = + PluginEntityLib.pack(address(allowlistPlugin), uint32(AllowlistPlugin.EntityId.PRE_VALIDATION_HOOK)); - FunctionReference[] memory preValidationHooks = new FunctionReference[](1); + PluginEntity[] memory preValidationHooks = new PluginEntity[](1); preValidationHooks[0] = accessControlHook; bytes[] memory preValidationHookData = new bytes[](1); @@ -306,11 +305,11 @@ contract AllowlistPluginTest is CustomValidationTestBase { bytes memory packedPreValidationHooks = abi.encode(preValidationHooks, preValidationHookData); return ( - _ownerValidation, + _signerValidation, true, true, new bytes4[](0), - abi.encode(TEST_DEFAULT_OWNER_FUNCTION_ID, owner1), + abi.encode(TEST_DEFAULT_VALIDATION_ENTITY_ID, owner1), packedPreValidationHooks, "" ); diff --git a/test/utils/AccountTestBase.sol b/test/utils/AccountTestBase.sol index a312fbdf..526365d0 100644 --- a/test/utils/AccountTestBase.sol +++ b/test/utils/AccountTestBase.sol @@ -5,38 +5,40 @@ import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.so import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; -import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; +import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; +import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; import {IStandardExecutor, Call} from "../../src/interfaces/IStandardExecutor.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {SingleOwnerPlugin} from "../../src/plugins/owner/SingleOwnerPlugin.sol"; import {OptimizedTest} from "./OptimizedTest.sol"; -import {TEST_DEFAULT_OWNER_FUNCTION_ID as EXT_CONST_TEST_DEFAULT_OWNER_FUNCTION_ID} from "./TestConstants.sol"; +import {TEST_DEFAULT_VALIDATION_ENTITY_ID as EXT_CONST_TEST_DEFAULT_VALIDATION_ENTITY_ID} from + "./TestConstants.sol"; -import {MSCAFactoryFixture} from "../mocks/MSCAFactoryFixture.sol"; +import {SingleSignerFactoryFixture} from "../mocks/SingleSignerFactoryFixture.sol"; /// @dev This contract handles common boilerplate setup for tests using UpgradeableModularAccount with -/// SingleOwnerPlugin. +/// SingleSignerValidation. abstract contract AccountTestBase is OptimizedTest { - using FunctionReferenceLib for FunctionReference; + using PluginEntityLib for PluginEntity; using MessageHashUtils for bytes32; EntryPoint public entryPoint; address payable public beneficiary; - SingleOwnerPlugin public singleOwnerPlugin; - MSCAFactoryFixture public factory; + + SingleSignerValidation public singleSignerValidation; + SingleSignerFactoryFixture public factory; address public owner1; uint256 public owner1Key; UpgradeableModularAccount public account1; - FunctionReference internal _ownerValidation; + PluginEntity internal _signerValidation; uint8 public constant SELECTOR_ASSOCIATED_VALIDATION = 0; uint8 public constant GLOBAL_VALIDATION = 1; // Re-declare the constant to prevent derived test contracts from having to import it - uint8 public constant TEST_DEFAULT_OWNER_FUNCTION_ID = EXT_CONST_TEST_DEFAULT_OWNER_FUNCTION_ID; + uint32 public constant TEST_DEFAULT_VALIDATION_ENTITY_ID = EXT_CONST_TEST_DEFAULT_VALIDATION_ENTITY_ID; uint256 public constant CALL_GAS_LIMIT = 100000; uint256 public constant VERIFICATION_GAS_LIMIT = 1200000; @@ -51,13 +53,14 @@ abstract contract AccountTestBase is OptimizedTest { (owner1, owner1Key) = makeAddrAndKey("owner1"); beneficiary = payable(makeAddr("beneficiary")); - singleOwnerPlugin = _deploySingleOwnerPlugin(); - factory = new MSCAFactoryFixture(entryPoint, singleOwnerPlugin); + singleSignerValidation = _deploySingleSignerValidation(); + factory = new SingleSignerFactoryFixture(entryPoint, singleSignerValidation); account1 = factory.createAccount(owner1, 0); vm.deal(address(account1), 100 ether); - _ownerValidation = FunctionReferenceLib.pack(address(singleOwnerPlugin), TEST_DEFAULT_OWNER_FUNCTION_ID); + _signerValidation = + PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID); } function _runExecUserOp(address target, bytes memory callData) internal { @@ -100,7 +103,7 @@ abstract contract AccountTestBase is OptimizedTest { (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); userOp.signature = _encodeSignature( - FunctionReferenceLib.pack(address(singleOwnerPlugin), TEST_DEFAULT_OWNER_FUNCTION_ID), + PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, abi.encodePacked(r, s, v) ); @@ -153,7 +156,7 @@ abstract contract AccountTestBase is OptimizedTest { account1.executeWithAuthorization( callData, _encodeSignature( - FunctionReferenceLib.pack(address(singleOwnerPlugin), TEST_DEFAULT_OWNER_FUNCTION_ID), + PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, "" ) @@ -168,7 +171,7 @@ abstract contract AccountTestBase is OptimizedTest { account1.executeWithAuthorization( callData, _encodeSignature( - FunctionReferenceLib.pack(address(singleOwnerPlugin), TEST_DEFAULT_OWNER_FUNCTION_ID), + PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, "" ) @@ -182,15 +185,15 @@ abstract contract AccountTestBase is OptimizedTest { abi.encodeCall( account1.execute, ( - address(singleOwnerPlugin), + address(singleSignerValidation), 0, abi.encodeCall( - SingleOwnerPlugin.transferOwnership, (TEST_DEFAULT_OWNER_FUNCTION_ID, address(this)) + SingleSignerValidation.transferSigner, (TEST_DEFAULT_VALIDATION_ENTITY_ID, address(this)) ) ) ), _encodeSignature( - FunctionReferenceLib.pack(address(singleOwnerPlugin), TEST_DEFAULT_OWNER_FUNCTION_ID), + PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, "" ) @@ -204,7 +207,7 @@ abstract contract AccountTestBase is OptimizedTest { // helper function to encode a signature, according to the per-hook and per-validation data format. function _encodeSignature( - FunctionReference validationFunction, + PluginEntity validationFunction, uint8 globalOrNot, PreValidationHookData[] memory preValidationHookData, bytes memory validationData @@ -214,7 +217,7 @@ abstract contract AccountTestBase is OptimizedTest { for (uint256 i = 0; i < preValidationHookData.length; ++i) { sig = abi.encodePacked( sig, - _packValidationDataWithIndex( + _packValidationResWithIndex( preValidationHookData[i].index, preValidationHookData[i].validationData ) ); @@ -222,13 +225,13 @@ abstract contract AccountTestBase is OptimizedTest { // Index of the actual validation data is the length of the preValidationHooksRetrieved - aka // one-past-the-end - sig = abi.encodePacked(sig, _packValidationDataWithIndex(255, validationData)); + sig = abi.encodePacked(sig, _packValidationResWithIndex(255, validationData)); return sig; } // overload for the case where there are no pre-validation hooks - function _encodeSignature(FunctionReference validationFunction, uint8 globalOrNot, bytes memory validationData) + function _encodeSignature(PluginEntity validationFunction, uint8 globalOrNot, bytes memory validationData) internal pure returns (bytes memory) @@ -238,7 +241,7 @@ abstract contract AccountTestBase is OptimizedTest { } // helper function to pack validation data with an index, according to the sparse calldata segment spec. - function _packValidationDataWithIndex(uint8 index, bytes memory validationData) + function _packValidationResWithIndex(uint8 index, bytes memory validationData) internal pure returns (bytes memory) diff --git a/test/utils/CustomValidationTestBase.sol b/test/utils/CustomValidationTestBase.sol index 2244c865..bd119fa1 100644 --- a/test/utils/CustomValidationTestBase.sol +++ b/test/utils/CustomValidationTestBase.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.25; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import {FunctionReference} from "../../src/helpers/FunctionReferenceLib.sol"; +import {PluginEntity} from "../../src/helpers/PluginEntityLib.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; @@ -16,7 +16,7 @@ import {AccountTestBase} from "./AccountTestBase.sol"; abstract contract CustomValidationTestBase is AccountTestBase { function _customValidationSetup() internal { ( - FunctionReference validationFunction, + PluginEntity validationFunction, bool isGlobal, bool isSignatureValidation, bytes4[] memory selectors, @@ -44,7 +44,7 @@ abstract contract CustomValidationTestBase is AccountTestBase { internal virtual returns ( - FunctionReference validationFunction, + PluginEntity validationFunction, bool shared, bool isSignatureValidation, bytes4[] memory selectors, diff --git a/test/utils/OptimizedTest.sol b/test/utils/OptimizedTest.sol index f9431acc..d884193f 100644 --- a/test/utils/OptimizedTest.sol +++ b/test/utils/OptimizedTest.sol @@ -6,7 +6,7 @@ import {Test} from "forge-std/Test.sol"; import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {SingleOwnerPlugin} from "../../src/plugins/owner/SingleOwnerPlugin.sol"; +import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; import {TokenReceiverPlugin} from "../../src/plugins/TokenReceiverPlugin.sol"; /// @dev This contract provides functions to deploy optimized (via IR) precompiled contracts. By compiling just @@ -44,15 +44,17 @@ abstract contract OptimizedTest is Test { : new UpgradeableModularAccount(entryPoint); } - function _deploySingleOwnerPlugin() internal returns (SingleOwnerPlugin) { - return _isOptimizedTest() - ? SingleOwnerPlugin(deployCode("out-optimized/SingleOwnerPlugin.sol/SingleOwnerPlugin.json")) - : new SingleOwnerPlugin(); - } - function _deployTokenReceiverPlugin() internal returns (TokenReceiverPlugin) { return _isOptimizedTest() ? TokenReceiverPlugin(deployCode("out-optimized/TokenReceiverPlugin.sol/TokenReceiverPlugin.json")) : new TokenReceiverPlugin(); } + + function _deploySingleSignerValidation() internal returns (SingleSignerValidation) { + return _isOptimizedTest() + ? SingleSignerValidation( + deployCode("out-optimized/SingleSignerValidation.sol/SingleSignerValidation.json") + ) + : new SingleSignerValidation(); + } } diff --git a/test/utils/TestConstants.sol b/test/utils/TestConstants.sol index 923692a7..c15b2dd3 100644 --- a/test/utils/TestConstants.sol +++ b/test/utils/TestConstants.sol @@ -1,4 +1,4 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.25; -uint8 constant TEST_DEFAULT_OWNER_FUNCTION_ID = 0; +uint32 constant TEST_DEFAULT_VALIDATION_ENTITY_ID = 1; diff --git a/test/validation/SingleSignerValidation.t.sol b/test/validation/SingleSignerValidation.t.sol new file mode 100644 index 00000000..a983ea93 --- /dev/null +++ b/test/validation/SingleSignerValidation.t.sol @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; +import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; + +import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; +import {PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; +import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; + +import {AccountTestBase} from "../utils/AccountTestBase.sol"; +import {TEST_DEFAULT_VALIDATION_ENTITY_ID} from "../utils/TestConstants.sol"; +import {ContractOwner} from "../mocks/ContractOwner.sol"; + +contract SingleSignerValidationTest is AccountTestBase { + using MessageHashUtils for bytes32; + + bytes4 internal constant _1271_MAGIC_VALUE = 0x1626ba7e; + + address public ethRecipient; + address public owner2; + uint256 public owner2Key; + UpgradeableModularAccount public account; + + ContractOwner public contractOwner; + + function setUp() public { + ethRecipient = makeAddr("ethRecipient"); + (owner2, owner2Key) = makeAddrAndKey("owner2"); + account = factory.createAccount(owner1, 0); + vm.deal(address(account), 100 ether); + + contractOwner = new ContractOwner(); + } + + function test_userOpValidation() public { + PackedUserOperation memory userOp = PackedUserOperation({ + sender: address(account), + nonce: 0, + initCode: "", + callData: abi.encodeCall(UpgradeableModularAccount.execute, (ethRecipient, 1 wei, "")), + accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), + preVerificationGas: 0, + gasFees: _encodeGas(1, 1), + paymasterAndData: "", + signature: "" + }); + + // Generate signature + bytes32 userOpHash = entryPoint.getUserOpHash(userOp); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); + userOp.signature = _encodeSignature( + PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), + GLOBAL_VALIDATION, + abi.encodePacked(r, s, v) + ); + + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); + userOps[0] = userOp; + + entryPoint.handleOps(userOps, beneficiary); + + assertEq(ethRecipient.balance, 1 wei); + } + + function test_runtimeValidate() public { + vm.prank(owner1); + account.executeWithAuthorization( + abi.encodeCall(UpgradeableModularAccount.execute, (ethRecipient, 1 wei, "")), + _encodeSignature( + PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), + GLOBAL_VALIDATION, + "" + ) + ); + assertEq(ethRecipient.balance, 1 wei); + } + + function test_runtime_with2SameValidationInstalled() public { + uint32 newEntityId = TEST_DEFAULT_VALIDATION_ENTITY_ID + 1; + vm.prank(address(entryPoint)); + account.installValidation( + ValidationConfigLib.pack(address(singleSignerValidation), newEntityId, true, false), + new bytes4[](0), + abi.encode(newEntityId, owner2), + "", + "" + ); + + vm.prank(owner2); + account.executeWithAuthorization( + abi.encodeCall(UpgradeableModularAccount.execute, (ethRecipient, 1 wei, "")), + _encodeSignature( + PluginEntityLib.pack(address(singleSignerValidation), newEntityId), GLOBAL_VALIDATION, "" + ) + ); + assertEq(ethRecipient.balance, 1 wei); + } + + function testFuzz_isValidSignatureForEOAOwner(string memory salt, bytes32 digest) public { + // range bound the possible set of priv keys + (address signer, uint256 privateKey) = makeAddrAndKey(salt); + + address accountAddr = address(account); + + vm.startPrank(accountAddr); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest); + + // sig check should fail + assertEq( + singleSignerValidation.validateSignature( + accountAddr, TEST_DEFAULT_VALIDATION_ENTITY_ID, address(this), digest, abi.encodePacked(r, s, v) + ), + bytes4(0xFFFFFFFF) + ); + + // transfer ownership to signer + singleSignerValidation.transferSigner(TEST_DEFAULT_VALIDATION_ENTITY_ID, signer); + assertEq(signer, singleSignerValidation.signerOf(TEST_DEFAULT_VALIDATION_ENTITY_ID, accountAddr)); + + // sig check should pass + assertEq( + singleSignerValidation.validateSignature( + accountAddr, TEST_DEFAULT_VALIDATION_ENTITY_ID, address(this), digest, abi.encodePacked(r, s, v) + ), + _1271_MAGIC_VALUE + ); + } + + function testFuzz_isValidSignatureForContractOwner(bytes32 digest) public { + address accountAddr = address(account); + vm.startPrank(accountAddr); + singleSignerValidation.transferSigner(TEST_DEFAULT_VALIDATION_ENTITY_ID, address(contractOwner)); + bytes memory signature = contractOwner.sign(digest); + assertEq( + singleSignerValidation.validateSignature( + accountAddr, TEST_DEFAULT_VALIDATION_ENTITY_ID, address(this), digest, signature + ), + _1271_MAGIC_VALUE + ); + } +} From 39d327e6474757d695217f96eba49febd454dea1 Mon Sep 17 00:00:00 2001 From: Zer0dot Date: Wed, 17 Jul 2024 23:00:14 +0800 Subject: [PATCH 081/111] Allow direct plugin calls with validation & permission hooks (#90) --- .solhint-test.json | 36 +++--- src/account/AccountStorage.sol | 2 +- src/account/PluginManager2.sol | 35 +++--- src/account/UpgradeableModularAccount.sol | 131 ++++++++++++------- src/helpers/ValidationConfigLib.sol | 2 +- test/account/DirectCallsFromPlugin.t.sol | 133 ++++++++++++++++++++ test/account/PermittedCallPermissions.t.sol | 4 +- test/account/SelfCallAuthorization.t.sol | 18 +-- test/mocks/plugins/DirectCallPlugin.sol | 47 +++++++ 9 files changed, 315 insertions(+), 93 deletions(-) create mode 100644 test/account/DirectCallsFromPlugin.t.sol create mode 100644 test/mocks/plugins/DirectCallPlugin.sol diff --git a/.solhint-test.json b/.solhint-test.json index fd2b1007..3224b9d0 100644 --- a/.solhint-test.json +++ b/.solhint-test.json @@ -1,20 +1,20 @@ { - "extends": "solhint:recommended", - "rules": { - "func-name-mixedcase": "off", - "immutable-vars-naming": ["error"], - "no-unused-import": ["error"], - "compiler-version": ["error", ">=0.8.19"], - "custom-errors": "off", - "func-visibility": ["error", { "ignoreConstructors": true }], - "max-line-length": ["error", 120], - "max-states-count": ["warn", 30], - "modifier-name-mixedcase": ["error"], - "private-vars-leading-underscore": ["error"], - "no-inline-assembly": "off", - "avoid-low-level-calls": "off", - "one-contract-per-file": "off", - "no-empty-blocks": "off" - } + "extends": "solhint:recommended", + "rules": { + "func-name-mixedcase": "off", + "immutable-vars-naming": ["error"], + "no-unused-import": ["error"], + "compiler-version": ["error", ">=0.8.19"], + "custom-errors": "off", + "func-visibility": ["error", { "ignoreConstructors": true }], + "max-line-length": ["error", 120], + "max-states-count": ["warn", 30], + "modifier-name-mixedcase": ["error"], + "private-vars-leading-underscore": ["error"], + "no-inline-assembly": "off", + "avoid-low-level-calls": "off", + "one-contract-per-file": "off", + "no-empty-blocks": "off", + "reason-string": ["warn", { "maxLength": 64 }] } - \ No newline at end of file +} diff --git a/src/account/AccountStorage.sol b/src/account/AccountStorage.sol index df081bd0..aa56f75d 100644 --- a/src/account/AccountStorage.sol +++ b/src/account/AccountStorage.sol @@ -30,7 +30,7 @@ struct ValidationData { bool isGlobal; // Whether or not this validation is a signature validator. bool isSignatureValidation; - // The pre validation hooks for this function selector. + // The pre validation hooks for this validation function. PluginEntity[] preValidationHooks; // Permission hooks for this validation function. EnumerableSet.Bytes32Set permissionHooks; diff --git a/src/account/PluginManager2.sol b/src/account/PluginManager2.sol index 1f121880..2ff6a9d1 100644 --- a/src/account/PluginManager2.sol +++ b/src/account/PluginManager2.sol @@ -7,7 +7,7 @@ import {IPlugin} from "../interfaces/IPlugin.sol"; import {PluginEntity, ValidationConfig} from "../interfaces/IPluginManager.sol"; import {PluginEntityLib} from "../helpers/PluginEntityLib.sol"; import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; -import {ValidationData, getAccountStorage, toSetValue, toPluginEntity} from "./AccountStorage.sol"; +import {ValidationData, getAccountStorage, toSetValue} from "./AccountStorage.sol"; import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; // Temporary additional functions for a user-controlled install flow for validation functions. @@ -17,6 +17,7 @@ abstract contract PluginManager2 { // Index marking the start of the data for the validation function. uint8 internal constant _RESERVED_VALIDATION_DATA_INDEX = 255; + uint32 internal constant _SELF_PERMIT_VALIDATION_FUNCTIONID = type(uint32).max; error PreValidationAlreadySet(PluginEntity validationFunction, PluginEntity preValidationFunction); error ValidationAlreadySet(bytes4 selector, PluginEntity validationFunction); @@ -32,7 +33,7 @@ abstract contract PluginManager2 { bytes memory permissionHooks ) internal { ValidationData storage _validationData = - getAccountStorage().validationData[validationConfig.functionReference()]; + getAccountStorage().validationData[validationConfig.pluginEntity()]; if (preValidationHooks.length > 0) { (PluginEntity[] memory preValidationFunctions, bytes[] memory initDatas) = @@ -63,7 +64,7 @@ abstract contract PluginManager2 { ExecutionHook memory permissionFunction = permissionFunctions[i]; if (!_validationData.permissionHooks.add(toSetValue(permissionFunction))) { - revert PermissionAlreadySet(validationConfig.functionReference(), permissionFunction); + revert PermissionAlreadySet(validationConfig.pluginEntity(), permissionFunction); } if (initDatas[i].length > 0) { @@ -73,19 +74,21 @@ abstract contract PluginManager2 { } } - _validationData.isGlobal = validationConfig.isGlobal(); - _validationData.isSignatureValidation = validationConfig.isSignatureValidation(); - for (uint256 i = 0; i < selectors.length; ++i) { bytes4 selector = selectors[i]; if (!_validationData.selectors.add(toSetValue(selector))) { - revert ValidationAlreadySet(selector, validationConfig.functionReference()); + revert ValidationAlreadySet(selector, validationConfig.pluginEntity()); } } - if (installData.length > 0) { - address plugin = validationConfig.plugin(); - IPlugin(plugin).onInstall(installData); + if (validationConfig.entityId() != _SELF_PERMIT_VALIDATION_FUNCTIONID) { + // Only allow global validations and signature validations if they're not direct-call validations. + + _validationData.isGlobal = validationConfig.isGlobal(); + _validationData.isSignatureValidation = validationConfig.isSignatureValidation(); + if (installData.length > 0) { + IPlugin(validationConfig.plugin()).onInstall(installData); + } } } @@ -120,12 +123,12 @@ abstract contract PluginManager2 { // Clear permission hooks EnumerableSet.Bytes32Set storage permissionHooks = _validationData.permissionHooks; - uint256 i = 0; - while (permissionHooks.length() > 0) { - PluginEntity permissionHook = toPluginEntity(permissionHooks.at(0)); - permissionHooks.remove(toSetValue(permissionHook)); - (address permissionHookPlugin,) = PluginEntityLib.unpack(permissionHook); - IPlugin(permissionHookPlugin).onUninstall(permissionHookUninstallDatas[i++]); + uint256 len = permissionHooks.length(); + for (uint256 i = 0; i < len; ++i) { + bytes32 permissionHook = permissionHooks.at(0); + permissionHooks.remove(permissionHook); + address permissionHookPlugin = address(uint160(bytes20(permissionHook))); + IPlugin(permissionHookPlugin).onUninstall(permissionHookUninstallDatas[i]); } } delete _validationData.preValidationHooks; diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index acbd6de5..6c577ed2 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -78,7 +78,7 @@ contract UpgradeableModularAccount is error SignatureValidationInvalid(address plugin, uint32 entityId); error UnexpectedAggregator(address plugin, uint32 entityId, address aggregator); error UnrecognizedFunction(bytes4 selector); - error UserOpValidationFunctionMissing(bytes4 selector); + error ValidationFunctionMissing(bytes4 selector); error ValidationDoesNotApply(bytes4 selector, address plugin, uint32 entityId, bool isGlobal); error ValidationSignatureSegmentMissing(); error SignatureSegmentOutOfOrder(); @@ -86,14 +86,13 @@ contract UpgradeableModularAccount is // Wraps execution of a native function with runtime validation and hooks // Used for upgradeTo, upgradeToAndCall, execute, executeBatch, installPlugin, uninstallPlugin modifier wrapNativeFunction() { - _checkPermittedCallerIfNotFromEP(); - - PostExecToRun[] memory postExecHooks = - _doPreHooks(getAccountStorage().selectorData[msg.sig].executionHooks, msg.data); + (PostExecToRun[] memory postPermissionHooks, PostExecToRun[] memory postExecHooks) = + _checkPermittedCallerAndAssociatedHooks(); _; _doCachedPostExecHooks(postExecHooks); + _doCachedPostExecHooks(postPermissionHooks); } constructor(IEntryPoint anEntryPoint) { @@ -136,7 +135,7 @@ contract UpgradeableModularAccount is revert UnrecognizedFunction(msg.sig); } - _checkPermittedCallerIfNotFromEP(); + _checkPermittedCallerAndAssociatedHooks(); PostExecToRun[] memory postExecHooks; // Cache post-exec hooks in memory @@ -500,17 +499,7 @@ contract UpgradeableModularAccount is } else { currentAuthData = ""; } - - (address hookPlugin, uint32 hookEntityId) = preRuntimeValidationHooks[i].unpack(); - try IValidationHook(hookPlugin).preRuntimeValidationHook( - hookEntityId, msg.sender, msg.value, callData, currentAuthData - ) - // forgefmt: disable-start - // solhint-disable-next-line no-empty-blocks - {} catch (bytes memory revertReason) { - // forgefmt: disable-end - revert PreRuntimeValidationHookFailed(hookPlugin, hookEntityId, revertReason); - } + _doPreRuntimeValidationHook(preRuntimeValidationHooks[i], callData, currentAuthData); } if (authSegment.getIndex() != _RESERVED_VALIDATION_DATA_INDEX) { @@ -605,9 +594,78 @@ contract UpgradeableModularAccount is } } + function _doPreRuntimeValidationHook( + PluginEntity validationHook, + bytes memory callData, + bytes memory currentAuthData + ) internal { + (address hookPlugin, uint32 hookEntityId) = validationHook.unpack(); + try IValidationHook(hookPlugin).preRuntimeValidationHook( + hookEntityId, msg.sender, msg.value, callData, currentAuthData + ) + // forgefmt: disable-start + // solhint-disable-next-line no-empty-blocks + {} catch (bytes memory revertReason) { + // forgefmt: disable-end + revert PreRuntimeValidationHookFailed(hookPlugin, hookEntityId, revertReason); + } + } + // solhint-disable-next-line no-empty-blocks function _authorizeUpgrade(address newImplementation) internal override {} + /** + * Order of operations: + * 1. Check if the sender is the entry point, the account itself, or the selector called is public. + * - Yes: Return an empty array, there are no post-permissionHooks. + * - No: Continue + * 2. Check if the called selector (msg.sig) is included in the set of selectors the msg.sender can + * directly call. + * - Yes: Continue + * - No: Revert, the caller is not allowed to call this selector + * 3. If there are runtime validation hooks associated with this caller-sig combination, run them. + * 4. Run the pre-permissionHooks associated with this caller-sig combination, and return the + * post-permissionHooks to run later. + */ + function _checkPermittedCallerAndAssociatedHooks() + internal + returns (PostExecToRun[] memory, PostExecToRun[] memory) + { + AccountStorage storage _storage = getAccountStorage(); + + if ( + msg.sender == address(_ENTRY_POINT) || msg.sender == address(this) + || _storage.selectorData[msg.sig].isPublic + ) { + return (new PostExecToRun[](0), new PostExecToRun[](0)); + } + + PluginEntity directCallValidationKey = PluginEntityLib.pack(msg.sender, _SELF_PERMIT_VALIDATION_FUNCTIONID); + + _checkIfValidationAppliesCallData(msg.data, directCallValidationKey, false); + + // Direct call is allowed, run associated permission & validation hooks + + // Validation hooks + PluginEntity[] memory preRuntimeValidationHooks = + _storage.validationData[directCallValidationKey].preValidationHooks; + + uint256 hookLen = preRuntimeValidationHooks.length; + for (uint256 i = 0; i < hookLen; ++i) { + _doPreRuntimeValidationHook(preRuntimeValidationHooks[i], msg.data, ""); + } + + // Permission hooks + PostExecToRun[] memory postPermissionHooks = + _doPreHooks(_storage.validationData[directCallValidationKey].permissionHooks, msg.data); + + // Exec hooks + PostExecToRun[] memory postExecutionHooks = + _doPreHooks(_storage.selectorData[msg.sig].executionHooks, msg.data); + + return (postPermissionHooks, postExecutionHooks); + } + function _checkIfValidationAppliesCallData( bytes calldata callData, PluginEntity validationFunction, @@ -661,25 +719,6 @@ contract UpgradeableModularAccount is } } - function _checkIfValidationAppliesSelector(bytes4 selector, PluginEntity validationFunction, bool isGlobal) - internal - view - { - AccountStorage storage _storage = getAccountStorage(); - - // Check that the provided validation function is applicable to the selector - if (isGlobal) { - if (!_globalValidationAllowed(selector) || !_storage.validationData[validationFunction].isGlobal) { - revert UserOpValidationFunctionMissing(selector); - } - } else { - // Not global validation, but per-selector - if (!getAccountStorage().validationData[validationFunction].selectors.contains(toSetValue(selector))) { - revert UserOpValidationFunctionMissing(selector); - } - } - } - function _globalValidationAllowed(bytes4 selector) internal view returns (bool) { if ( selector == this.execute.selector || selector == this.executeBatch.selector @@ -693,14 +732,22 @@ contract UpgradeableModularAccount is return getAccountStorage().selectorData[selector].allowGlobalValidation; } - function _checkPermittedCallerIfNotFromEP() internal view { + function _checkIfValidationAppliesSelector(bytes4 selector, PluginEntity validationFunction, bool isGlobal) + internal + view + { AccountStorage storage _storage = getAccountStorage(); - if ( - msg.sender != address(_ENTRY_POINT) && msg.sender != address(this) - && !_storage.selectorData[msg.sig].isPublic - ) { - revert ExecFromPluginNotPermitted(msg.sender, msg.sig); + // Check that the provided validation function is applicable to the selector + if (isGlobal) { + if (!_globalValidationAllowed(selector) || !_storage.validationData[validationFunction].isGlobal) { + revert ValidationFunctionMissing(selector); + } + } else { + // Not global validation, but per-selector + if (!getAccountStorage().validationData[validationFunction].selectors.contains(toSetValue(selector))) { + revert ValidationFunctionMissing(selector); + } } } } diff --git a/src/helpers/ValidationConfigLib.sol b/src/helpers/ValidationConfigLib.sol index 95e8ea90..6d27b907 100644 --- a/src/helpers/ValidationConfigLib.sol +++ b/src/helpers/ValidationConfigLib.sol @@ -78,7 +78,7 @@ library ValidationConfigLib { return uint32(bytes4(ValidationConfig.unwrap(config) << 160)); } - function functionReference(ValidationConfig config) internal pure returns (PluginEntity) { + function pluginEntity(ValidationConfig config) internal pure returns (PluginEntity) { return PluginEntity.wrap(bytes24(ValidationConfig.unwrap(config))); } diff --git a/test/account/DirectCallsFromPlugin.t.sol b/test/account/DirectCallsFromPlugin.t.sol new file mode 100644 index 00000000..1c0fcca8 --- /dev/null +++ b/test/account/DirectCallsFromPlugin.t.sol @@ -0,0 +1,133 @@ +pragma solidity ^0.8.19; + +import {DirectCallPlugin} from "../mocks/plugins/DirectCallPlugin.sol"; +import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; +import {IStandardExecutor, Call} from "../../src/interfaces/IStandardExecutor.sol"; +import {PluginEntityLib, PluginEntity} from "../../src/helpers/PluginEntityLib.sol"; +import {ValidationConfig, ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; +import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; + +import {AccountTestBase} from "../utils/AccountTestBase.sol"; + +contract DirectCallsFromPluginTest is AccountTestBase { + using ValidationConfigLib for ValidationConfig; + + DirectCallPlugin internal _plugin; + PluginEntity internal _pluginEntity; + + function setUp() public { + _plugin = new DirectCallPlugin(); + assertFalse(_plugin.preHookRan()); + assertFalse(_plugin.postHookRan()); + _pluginEntity = PluginEntityLib.pack(address(_plugin), type(uint32).max); + } + + /* -------------------------------------------------------------------------- */ + /* Negatives */ + /* -------------------------------------------------------------------------- */ + + function test_Fail_DirectCallPluginNotInstalled() external { + vm.prank(address(_plugin)); + vm.expectRevert(_buildDirectCallDisallowedError(IStandardExecutor.execute.selector)); + account1.execute(address(0), 0, ""); + } + + function test_Fail_DirectCallPluginUninstalled() external { + _installPlugin(); + + _uninstallPlugin(); + + vm.prank(address(_plugin)); + vm.expectRevert(_buildDirectCallDisallowedError(IStandardExecutor.execute.selector)); + account1.execute(address(0), 0, ""); + } + + function test_Fail_DirectCallPluginCallOtherSelector() external { + _installPlugin(); + + Call[] memory calls = new Call[](0); + + vm.prank(address(_plugin)); + vm.expectRevert(_buildDirectCallDisallowedError(IStandardExecutor.executeBatch.selector)); + account1.executeBatch(calls); + } + + /* -------------------------------------------------------------------------- */ + /* Positives */ + /* -------------------------------------------------------------------------- */ + + function test_Pass_DirectCallFromPluginPrank() external { + _installPlugin(); + + vm.prank(address(_plugin)); + account1.execute(address(0), 0, ""); + + assertTrue(_plugin.preHookRan()); + assertTrue(_plugin.postHookRan()); + } + + function test_Pass_DirectCallFromPluginCallback() external { + _installPlugin(); + + bytes memory encodedCall = abi.encodeCall(DirectCallPlugin.directCall, ()); + + vm.prank(address(entryPoint)); + bytes memory result = account1.execute(address(_plugin), 0, encodedCall); + + assertTrue(_plugin.preHookRan()); + assertTrue(_plugin.postHookRan()); + + // the directCall() function in the _plugin calls back into `execute()` with an encoded call back into the + // _plugin's getData() function. + assertEq(abi.decode(result, (bytes)), abi.encode(_plugin.getData())); + } + + function test_Flow_DirectCallFromPluginSequence() external { + // Install => Succeesfully call => uninstall => fail to call + + _installPlugin(); + + vm.prank(address(_plugin)); + account1.execute(address(0), 0, ""); + + assertTrue(_plugin.preHookRan()); + assertTrue(_plugin.postHookRan()); + + _uninstallPlugin(); + + vm.prank(address(_plugin)); + vm.expectRevert(_buildDirectCallDisallowedError(IStandardExecutor.execute.selector)); + account1.execute(address(0), 0, ""); + } + + /* -------------------------------------------------------------------------- */ + /* Internals */ + /* -------------------------------------------------------------------------- */ + + function _installPlugin() internal { + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = IStandardExecutor.execute.selector; + + ExecutionHook[] memory permissionHooks = new ExecutionHook[](1); + bytes[] memory permissionHookInitDatas = new bytes[](1); + + permissionHooks[0] = ExecutionHook({hookFunction: _pluginEntity, isPreHook: true, isPostHook: true}); + + bytes memory encodedPermissionHooks = abi.encode(permissionHooks, permissionHookInitDatas); + + vm.prank(address(entryPoint)); + + ValidationConfig validationConfig = ValidationConfigLib.pack(_pluginEntity, false, false); + + account1.installValidation(validationConfig, selectors, "", "", encodedPermissionHooks); + } + + function _uninstallPlugin() internal { + vm.prank(address(entryPoint)); + account1.uninstallValidation(_pluginEntity, "", abi.encode(new bytes[](0)), abi.encode(new bytes[](1))); + } + + function _buildDirectCallDisallowedError(bytes4 selector) internal pure returns (bytes memory) { + return abi.encodeWithSelector(UpgradeableModularAccount.ValidationFunctionMissing.selector, selector); + } +} diff --git a/test/account/PermittedCallPermissions.t.sol b/test/account/PermittedCallPermissions.t.sol index 18257955..885015b0 100644 --- a/test/account/PermittedCallPermissions.t.sol +++ b/test/account/PermittedCallPermissions.t.sol @@ -48,9 +48,7 @@ contract PermittedCallPermissionsTest is AccountTestBase { function test_permittedCall_NotAllowed() public { vm.expectRevert( abi.encodeWithSelector( - UpgradeableModularAccount.ExecFromPluginNotPermitted.selector, - address(permittedCallerPlugin), - ResultCreatorPlugin.bar.selector + UpgradeableModularAccount.ValidationFunctionMissing.selector, ResultCreatorPlugin.bar.selector ) ); PermittedCallerPlugin(address(account1)).usePermittedCallNotAllowed(); diff --git a/test/account/SelfCallAuthorization.t.sol b/test/account/SelfCallAuthorization.t.sol index c490eea4..4b6d04c7 100644 --- a/test/account/SelfCallAuthorization.t.sol +++ b/test/account/SelfCallAuthorization.t.sol @@ -40,8 +40,7 @@ contract SelfCallAuthorizationTest is AccountTestBase { 0, "AA23 reverted", abi.encodeWithSelector( - UpgradeableModularAccount.UserOpValidationFunctionMissing.selector, - ComprehensivePlugin.foo.selector + UpgradeableModularAccount.ValidationFunctionMissing.selector, ComprehensivePlugin.foo.selector ) ) ); @@ -56,8 +55,7 @@ contract SelfCallAuthorizationTest is AccountTestBase { 0, "AA23 reverted", abi.encodeWithSelector( - UpgradeableModularAccount.UserOpValidationFunctionMissing.selector, - ComprehensivePlugin.foo.selector + UpgradeableModularAccount.ValidationFunctionMissing.selector, ComprehensivePlugin.foo.selector ) ) ); @@ -68,8 +66,7 @@ contract SelfCallAuthorizationTest is AccountTestBase { _runtimeCall( abi.encodeCall(ComprehensivePlugin.foo, ()), abi.encodeWithSelector( - UpgradeableModularAccount.UserOpValidationFunctionMissing.selector, - ComprehensivePlugin.foo.selector + UpgradeableModularAccount.ValidationFunctionMissing.selector, ComprehensivePlugin.foo.selector ) ); } @@ -99,8 +96,7 @@ contract SelfCallAuthorizationTest is AccountTestBase { 0, "AA23 reverted", abi.encodeWithSelector( - UpgradeableModularAccount.UserOpValidationFunctionMissing.selector, - ComprehensivePlugin.foo.selector + UpgradeableModularAccount.ValidationFunctionMissing.selector, ComprehensivePlugin.foo.selector ) ) ); @@ -136,8 +132,7 @@ contract SelfCallAuthorizationTest is AccountTestBase { 0, "AA23 reverted", abi.encodeWithSelector( - UpgradeableModularAccount.UserOpValidationFunctionMissing.selector, - ComprehensivePlugin.foo.selector + UpgradeableModularAccount.ValidationFunctionMissing.selector, ComprehensivePlugin.foo.selector ) ) ); @@ -159,8 +154,7 @@ contract SelfCallAuthorizationTest is AccountTestBase { _runtimeExecBatchExpFail( calls, abi.encodeWithSelector( - UpgradeableModularAccount.UserOpValidationFunctionMissing.selector, - ComprehensivePlugin.foo.selector + UpgradeableModularAccount.ValidationFunctionMissing.selector, ComprehensivePlugin.foo.selector ) ); } diff --git a/test/mocks/plugins/DirectCallPlugin.sol b/test/mocks/plugins/DirectCallPlugin.sol new file mode 100644 index 00000000..8ab5dd42 --- /dev/null +++ b/test/mocks/plugins/DirectCallPlugin.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {PluginManifest, PluginMetadata} from "../../../src/interfaces/IPlugin.sol"; +import {IStandardExecutor} from "../../../src/interfaces/IStandardExecutor.sol"; +import {IExecutionHook} from "../../../src/interfaces/IExecutionHook.sol"; + +import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; + +contract DirectCallPlugin is BasePlugin, IExecutionHook { + bool public preHookRan = false; + bool public postHookRan = false; + + function onInstall(bytes calldata) external override {} + + function onUninstall(bytes calldata) external override {} + + function pluginManifest() external pure override returns (PluginManifest memory) {} + + function directCall() external returns (bytes memory) { + return IStandardExecutor(msg.sender).execute(address(this), 0, abi.encodeCall(this.getData, ())); + } + + function getData() external pure returns (bytes memory) { + return hex"04546b"; + } + + function pluginMetadata() external pure override returns (PluginMetadata memory) {} + + function preExecutionHook(uint32, address sender, uint256, bytes calldata) + external + override + returns (bytes memory) + { + require(sender == address(this), "mock direct call pre permission hook failed"); + preHookRan = true; + return abi.encode(keccak256(hex"04546b")); + } + + function postExecutionHook(uint32, bytes calldata preExecHookData) external override { + require( + abi.decode(preExecHookData, (bytes32)) == keccak256(hex"04546b"), + "mock direct call post permission hook failed" + ); + postHookRan = true; + } +} From 1f43a23d243f6d8f9956d05c11a91f4a95c595ad Mon Sep 17 00:00:00 2001 From: Adam Egyed <5456061+adamegyed@users.noreply.github.com> Date: Wed, 17 Jul 2024 13:01:21 -0400 Subject: [PATCH 082/111] style: [v0.8-develop] linter and fmt update (#98) --- foundry.toml | 3 + package.json | 4 +- src/account/AccountLoupe.sol | 7 ++- src/account/AccountStorage.sol | 2 +- src/account/PluginManager2.sol | 7 ++- src/account/PluginManagerInternals.sol | 11 ++-- src/account/UpgradeableModularAccount.sol | 21 ++++--- src/helpers/KnownSelectors.sol | 4 +- src/plugins/BasePlugin.sol | 2 +- src/plugins/ERC20TokenLimitPlugin.sol | 16 +++--- src/plugins/NativeTokenLimitPlugin.sol | 7 ++- src/plugins/TokenReceiverPlugin.sol | 14 ++--- .../validation/SingleSignerValidation.sol | 56 +++++++++---------- .../permissionhooks/AllowlistPlugin.sol | 5 +- test/account/AccountExecHooks.t.sol | 4 +- test/account/AccountReturnData.t.sol | 4 +- test/account/DirectCallsFromPlugin.t.sol | 10 ++-- test/account/MultiValidation.t.sol | 7 ++- test/account/PerHookData.t.sol | 4 +- test/account/PermittedCallPermissions.t.sol | 2 +- test/account/SelfCallAuthorization.t.sol | 5 +- test/account/UpgradeableModularAccount.t.sol | 14 +++-- test/comparison/CompareSimpleAccount.t.sol | 8 +-- test/libraries/AccountStorage.t.sol | 2 +- test/libraries/KnowSelectors.t.sol | 2 +- test/mocks/Counter.t.sol | 2 +- test/mocks/MockPlugin.sol | 16 +++--- test/mocks/SingleSignerFactoryFixture.sol | 6 +- test/mocks/plugins/ComprehensivePlugin.sol | 16 +++--- test/mocks/plugins/DirectCallPlugin.sol | 2 +- .../plugins/MockAccessControlHookPlugin.sol | 5 +- test/mocks/plugins/ReturnDataPluginMocks.sol | 3 +- test/mocks/plugins/ValidationPluginMocks.sol | 4 +- test/plugin/ERC20TokenLimitPlugin.t.sol | 32 ++++++----- test/plugin/NativeTokenLimitPlugin.t.sol | 45 ++++++++------- test/plugin/TokenReceiverPlugin.t.sol | 8 ++- test/samples/AllowlistPlugin.t.sol | 7 ++- test/utils/AccountTestBase.sol | 10 ++-- test/utils/CustomValidationTestBase.sol | 2 +- test/utils/OptimizedTest.sol | 3 +- test/validation/SingleSignerValidation.t.sol | 2 +- 41 files changed, 208 insertions(+), 176 deletions(-) diff --git a/foundry.toml b/foundry.toml index 67d9eb7c..2aa76ba2 100644 --- a/foundry.toml +++ b/foundry.toml @@ -47,6 +47,9 @@ depth = 32 [fmt] line_length = 115 wrap_comments = true +sort_imports = true +number_underscore = "thousands" +int_types = "long" [rpc_endpoints] mainnet = "${RPC_URL_MAINNET}" diff --git a/package.json b/package.json index a9cc7c23..1c1540e7 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ }, "scripts": { "lint": "pnpm lint:src && pnpm lint:test", - "lint:src": "solhint -c .solhint-src.json './src/**/*.sol'", - "lint:test": "solhint -c .solhint-test.json './test/**/*.sol'" + "lint:src": "solhint --max-warnings 0 -c .solhint-src.json './src/**/*.sol'", + "lint:test": "solhint --max-warnings 0 -c .solhint-test.json './test/**/*.sol'" } } diff --git a/src/account/AccountLoupe.sol b/src/account/AccountLoupe.sol index cca17149..ef679e4e 100644 --- a/src/account/AccountLoupe.sol +++ b/src/account/AccountLoupe.sol @@ -2,11 +2,12 @@ pragma solidity ^0.8.25; import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; -import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; + import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; +import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import {IAccountLoupe, ExecutionHook} from "../interfaces/IAccountLoupe.sol"; -import {PluginEntity, IPluginManager} from "../interfaces/IPluginManager.sol"; +import {ExecutionHook, IAccountLoupe} from "../interfaces/IAccountLoupe.sol"; +import {IPluginManager, PluginEntity} from "../interfaces/IPluginManager.sol"; import {IStandardExecutor} from "../interfaces/IStandardExecutor.sol"; import {getAccountStorage, toExecutionHook, toSelector} from "./AccountStorage.sol"; diff --git a/src/account/AccountStorage.sol b/src/account/AccountStorage.sol index aa56f75d..ea2f4551 100644 --- a/src/account/AccountStorage.sol +++ b/src/account/AccountStorage.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.25; -import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; +import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; import {PluginEntity} from "../interfaces/IPluginManager.sol"; diff --git a/src/account/PluginManager2.sol b/src/account/PluginManager2.sol index 2ff6a9d1..845b80b8 100644 --- a/src/account/PluginManager2.sol +++ b/src/account/PluginManager2.sol @@ -3,12 +3,13 @@ pragma solidity ^0.8.25; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import {IPlugin} from "../interfaces/IPlugin.sol"; -import {PluginEntity, ValidationConfig} from "../interfaces/IPluginManager.sol"; import {PluginEntityLib} from "../helpers/PluginEntityLib.sol"; import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; -import {ValidationData, getAccountStorage, toSetValue} from "./AccountStorage.sol"; + import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; +import {IPlugin} from "../interfaces/IPlugin.sol"; +import {PluginEntity, ValidationConfig} from "../interfaces/IPluginManager.sol"; +import {ValidationData, getAccountStorage, toSetValue} from "./AccountStorage.sol"; // Temporary additional functions for a user-controlled install flow for validation functions. abstract contract PluginManager2 { diff --git a/src/account/PluginManagerInternals.sol b/src/account/PluginManagerInternals.sol index ccc4c68b..e473730e 100644 --- a/src/account/PluginManagerInternals.sol +++ b/src/account/PluginManagerInternals.sol @@ -2,15 +2,16 @@ pragma solidity ^0.8.25; import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; -import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; + import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; +import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import {KnownSelectors} from "../helpers/KnownSelectors.sol"; import {PluginEntityLib} from "../helpers/PluginEntityLib.sol"; -import {IPlugin, ManifestExecutionHook, ManifestValidation, PluginManifest} from "../interfaces/IPlugin.sol"; import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; -import {PluginEntity, IPluginManager} from "../interfaces/IPluginManager.sol"; -import {KnownSelectors} from "../helpers/KnownSelectors.sol"; -import {AccountStorage, getAccountStorage, SelectorData, toSetValue} from "./AccountStorage.sol"; +import {IPlugin, ManifestExecutionHook, ManifestValidation, PluginManifest} from "../interfaces/IPlugin.sol"; +import {IPluginManager, PluginEntity} from "../interfaces/IPluginManager.sol"; +import {AccountStorage, SelectorData, getAccountStorage, toSetValue} from "./AccountStorage.sol"; abstract contract PluginManagerInternals is IPluginManager { using EnumerableSet for EnumerableSet.Bytes32Set; diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 6c577ed2..4f64cdd3 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -2,30 +2,35 @@ pragma solidity ^0.8.25; import {BaseAccount} from "@eth-infinitism/account-abstraction/core/BaseAccount.sol"; + +import {IAccountExecute} from "@eth-infinitism/account-abstraction/interfaces/IAccountExecute.sol"; import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; -import {IAccountExecute} from "@eth-infinitism/account-abstraction/interfaces/IAccountExecute.sol"; + +import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {PluginEntityLib} from "../helpers/PluginEntityLib.sol"; -import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; + import {SparseCalldataSegmentLib} from "../helpers/SparseCalldataSegmentLib.sol"; +import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; import {_coalescePreValidation, _coalesceValidation} from "../helpers/ValidationResHelpers.sol"; + +import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; import {IPlugin, PluginManifest} from "../interfaces/IPlugin.sol"; +import {IPluginManager, PluginEntity, ValidationConfig} from "../interfaces/IPluginManager.sol"; +import {Call, IStandardExecutor} from "../interfaces/IStandardExecutor.sol"; import {IValidation} from "../interfaces/IValidation.sol"; import {IValidationHook} from "../interfaces/IValidationHook.sol"; -import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; -import {PluginEntity, IPluginManager, ValidationConfig} from "../interfaces/IPluginManager.sol"; -import {IStandardExecutor, Call} from "../interfaces/IStandardExecutor.sol"; import {AccountExecutor} from "./AccountExecutor.sol"; import {AccountLoupe} from "./AccountLoupe.sol"; -import {AccountStorage, getAccountStorage, toSetValue, toExecutionHook} from "./AccountStorage.sol"; +import {AccountStorage, getAccountStorage, toExecutionHook, toSetValue} from "./AccountStorage.sol"; import {AccountStorageInitializable} from "./AccountStorageInitializable.sol"; -import {PluginManagerInternals} from "./PluginManagerInternals.sol"; + import {PluginManager2} from "./PluginManager2.sol"; +import {PluginManagerInternals} from "./PluginManagerInternals.sol"; contract UpgradeableModularAccount is AccountExecutor, diff --git a/src/helpers/KnownSelectors.sol b/src/helpers/KnownSelectors.sol index 1d02d2a3..ae91f3b7 100644 --- a/src/helpers/KnownSelectors.sol +++ b/src/helpers/KnownSelectors.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.25; -import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; import {IAccount} from "@eth-infinitism/account-abstraction/interfaces/IAccount.sol"; import {IAggregator} from "@eth-infinitism/account-abstraction/interfaces/IAggregator.sol"; import {IPaymaster} from "@eth-infinitism/account-abstraction/interfaces/IPaymaster.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; +import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import {IAccountLoupe} from "../interfaces/IAccountLoupe.sol"; import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; diff --git a/src/plugins/BasePlugin.sol b/src/plugins/BasePlugin.sol index f5c8523b..40dcd47b 100644 --- a/src/plugins/BasePlugin.sol +++ b/src/plugins/BasePlugin.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.25; -import {ERC165, IERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; import {IAccountExecute} from "@eth-infinitism/account-abstraction/interfaces/IAccountExecute.sol"; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; +import {ERC165, IERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; import {IPlugin} from "../interfaces/IPlugin.sol"; diff --git a/src/plugins/ERC20TokenLimitPlugin.sol b/src/plugins/ERC20TokenLimitPlugin.sol index 4e9c17ac..28ba4e04 100644 --- a/src/plugins/ERC20TokenLimitPlugin.sol +++ b/src/plugins/ERC20TokenLimitPlugin.sol @@ -1,20 +1,22 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.25; -import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {UserOperationLib} from "@eth-infinitism/account-abstraction/core/UserOperationLib.sol"; -import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; + import { - SetValue, AssociatedLinkedListSet, - AssociatedLinkedListSetLib + AssociatedLinkedListSetLib, + SetValue } from "@modular-account-libs/libraries/AssociatedLinkedListSetLib.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; import {PluginManifest, PluginMetadata} from "../interfaces/IPlugin.sol"; -import {IStandardExecutor, Call} from "../interfaces/IStandardExecutor.sol"; import {IPlugin} from "../interfaces/IPlugin.sol"; -import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; +import {Call, IStandardExecutor} from "../interfaces/IStandardExecutor.sol"; + import {BasePlugin, IERC165} from "./BasePlugin.sol"; /// @title ERC20 Token Limit Plugin diff --git a/src/plugins/NativeTokenLimitPlugin.sol b/src/plugins/NativeTokenLimitPlugin.sol index de0d9f3d..37f4399e 100644 --- a/src/plugins/NativeTokenLimitPlugin.sol +++ b/src/plugins/NativeTokenLimitPlugin.sol @@ -1,14 +1,15 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.25; -import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {UserOperationLib} from "@eth-infinitism/account-abstraction/core/UserOperationLib.sol"; +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; import {PluginManifest, PluginMetadata} from "../interfaces/IPlugin.sol"; -import {IStandardExecutor, Call} from "../interfaces/IStandardExecutor.sol"; import {IPlugin} from "../interfaces/IPlugin.sol"; -import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; +import {Call, IStandardExecutor} from "../interfaces/IStandardExecutor.sol"; + import {IValidationHook} from "../interfaces/IValidationHook.sol"; import {BasePlugin, IERC165} from "./BasePlugin.sol"; diff --git a/src/plugins/TokenReceiverPlugin.sol b/src/plugins/TokenReceiverPlugin.sol index d326a9d4..95fbcfe0 100644 --- a/src/plugins/TokenReceiverPlugin.sol +++ b/src/plugins/TokenReceiverPlugin.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.25; -import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; import {IERC1155Receiver} from "@openzeppelin/contracts/interfaces/IERC1155Receiver.sol"; +import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; import {IPlugin, ManifestExecutionFunction, PluginManifest, PluginMetadata} from "../interfaces/IPlugin.sol"; import {BasePlugin} from "./BasePlugin.sol"; @@ -12,9 +12,9 @@ import {BasePlugin} from "./BasePlugin.sol"; /// @notice This plugin allows modular accounts to receive various types of tokens by implementing /// required token receiver interfaces. contract TokenReceiverPlugin is BasePlugin, IERC721Receiver, IERC1155Receiver { - string public constant NAME = "Token Receiver Plugin"; - string public constant VERSION = "1.0.0"; - string public constant AUTHOR = "ERC-6900 Authors"; + string internal constant _NAME = "Token Receiver Plugin"; + string internal constant _VERSION = "1.0.0"; + string internal constant _AUTHOR = "ERC-6900 Authors"; // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ // ┃ Execution functions ┃ @@ -85,9 +85,9 @@ contract TokenReceiverPlugin is BasePlugin, IERC721Receiver, IERC1155Receiver { /// @inheritdoc IPlugin function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { PluginMetadata memory metadata; - metadata.name = NAME; - metadata.version = VERSION; - metadata.author = AUTHOR; + metadata.name = _NAME; + metadata.version = _VERSION; + metadata.author = _AUTHOR; return metadata; } } diff --git a/src/plugins/validation/SingleSignerValidation.sol b/src/plugins/validation/SingleSignerValidation.sol index 5ae4517d..6699cb58 100644 --- a/src/plugins/validation/SingleSignerValidation.sol +++ b/src/plugins/validation/SingleSignerValidation.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.25; +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; -import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; import {IPlugin, PluginManifest, PluginMetadata} from "../../interfaces/IPlugin.sol"; @@ -28,9 +28,9 @@ contract SingleSignerValidation is ISingleSignerValidation, BasePlugin { using ECDSA for bytes32; using MessageHashUtils for bytes32; - string public constant NAME = "SingleSigner Validation"; - string public constant VERSION = "1.0.0"; - string public constant AUTHOR = "ERC-6900 Authors"; + string internal constant _NAME = "SingleSigner Validation"; + string internal constant _VERSION = "1.0.0"; + string internal constant _AUTHOR = "ERC-6900 Authors"; uint256 internal constant _SIG_VALIDATION_PASSED = 0; uint256 internal constant _SIG_VALIDATION_FAILED = 1; @@ -41,35 +41,11 @@ contract SingleSignerValidation is ISingleSignerValidation, BasePlugin { mapping(uint32 entityId => mapping(address account => address)) public signer; - /// @inheritdoc ISingleSignerValidation - function signerOf(uint32 entityId, address account) external view returns (address) { - return signer[entityId][account]; - } - /// @inheritdoc ISingleSignerValidation function transferSigner(uint32 entityId, address newSigner) external { _transferSigner(entityId, newSigner); } - // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Plugin interface functions ┃ - // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - - /// @inheritdoc IPlugin - function pluginManifest() external pure override returns (PluginManifest memory) { - PluginManifest memory manifest; - return manifest; - } - - /// @inheritdoc IPlugin - function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { - PluginMetadata memory metadata; - metadata.name = NAME; - metadata.version = VERSION; - metadata.author = AUTHOR; - return metadata; - } - /// @inheritdoc IPlugin function onInstall(bytes calldata data) external override { (uint32 entityId, address newSigner) = abi.decode(data, (uint32, address)); @@ -83,6 +59,11 @@ contract SingleSignerValidation is ISingleSignerValidation, BasePlugin { _transferSigner(abi.decode(data, (uint32)), address(0)); } + /// @inheritdoc ISingleSignerValidation + function signerOf(uint32 entityId, address account) external view returns (address) { + return signer[entityId][account]; + } + /// @inheritdoc IValidation function validateUserOp(uint32 entityId, PackedUserOperation calldata userOp, bytes32 userOpHash) external @@ -133,6 +114,25 @@ contract SingleSignerValidation is ISingleSignerValidation, BasePlugin { return _1271_INVALID; } + // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ + // ┃ Plugin interface functions ┃ + // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + + /// @inheritdoc IPlugin + function pluginManifest() external pure override returns (PluginManifest memory) { + PluginManifest memory manifest; + return manifest; + } + + /// @inheritdoc IPlugin + function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { + PluginMetadata memory metadata; + metadata.name = _NAME; + metadata.version = _VERSION; + metadata.author = _AUTHOR; + return metadata; + } + // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ // ┃ Internal / Private functions ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/src/samples/permissionhooks/AllowlistPlugin.sol b/src/samples/permissionhooks/AllowlistPlugin.sol index 2d86ca18..bb468ae8 100644 --- a/src/samples/permissionhooks/AllowlistPlugin.sol +++ b/src/samples/permissionhooks/AllowlistPlugin.sol @@ -3,9 +3,10 @@ pragma solidity ^0.8.25; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; -import {PluginMetadata, PluginManifest} from "../../interfaces/IPlugin.sol"; +import {PluginManifest, PluginMetadata} from "../../interfaces/IPlugin.sol"; + +import {Call, IStandardExecutor} from "../../interfaces/IStandardExecutor.sol"; import {IValidationHook} from "../../interfaces/IValidationHook.sol"; -import {IStandardExecutor, Call} from "../../interfaces/IStandardExecutor.sol"; import {BasePlugin} from "../../plugins/BasePlugin.sol"; contract AllowlistPlugin is IValidationHook, BasePlugin { diff --git a/test/account/AccountExecHooks.t.sol b/test/account/AccountExecHooks.t.sol index 98d16dcf..ba3a2098 100644 --- a/test/account/AccountExecHooks.t.sol +++ b/test/account/AccountExecHooks.t.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; +import {IExecutionHook} from "../../src/interfaces/IExecutionHook.sol"; import { IPlugin, - ManifestExecutionHook, ManifestExecutionFunction, + ManifestExecutionHook, PluginManifest } from "../../src/interfaces/IPlugin.sol"; -import {IExecutionHook} from "../../src/interfaces/IExecutionHook.sol"; import {MockPlugin} from "../mocks/MockPlugin.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; diff --git a/test/account/AccountReturnData.t.sol b/test/account/AccountReturnData.t.sol index bb081325..fa8bdf86 100644 --- a/test/account/AccountReturnData.t.sol +++ b/test/account/AccountReturnData.t.sol @@ -6,8 +6,8 @@ import {Call} from "../../src/interfaces/IStandardExecutor.sol"; import { RegularResultContract, - ResultCreatorPlugin, - ResultConsumerPlugin + ResultConsumerPlugin, + ResultCreatorPlugin } from "../mocks/plugins/ReturnDataPluginMocks.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; import {TEST_DEFAULT_VALIDATION_ENTITY_ID} from "../utils/TestConstants.sol"; diff --git a/test/account/DirectCallsFromPlugin.t.sol b/test/account/DirectCallsFromPlugin.t.sol index 1c0fcca8..9e56739a 100644 --- a/test/account/DirectCallsFromPlugin.t.sol +++ b/test/account/DirectCallsFromPlugin.t.sol @@ -1,11 +1,11 @@ pragma solidity ^0.8.19; -import {DirectCallPlugin} from "../mocks/plugins/DirectCallPlugin.sol"; -import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; -import {IStandardExecutor, Call} from "../../src/interfaces/IStandardExecutor.sol"; -import {PluginEntityLib, PluginEntity} from "../../src/helpers/PluginEntityLib.sol"; -import {ValidationConfig, ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; +import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; +import {ValidationConfig, ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; +import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; +import {Call, IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; +import {DirectCallPlugin} from "../mocks/plugins/DirectCallPlugin.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; diff --git a/test/account/MultiValidation.t.sol b/test/account/MultiValidation.t.sol index 641fcefb..afb84f1a 100644 --- a/test/account/MultiValidation.t.sol +++ b/test/account/MultiValidation.t.sol @@ -1,17 +1,18 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.21; -import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {PluginEntity} from "../../src/interfaces/IPluginManager.sol"; -import {IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; + import {PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; +import {PluginEntity} from "../../src/interfaces/IPluginManager.sol"; +import {IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; diff --git a/test/account/PerHookData.t.sol b/test/account/PerHookData.t.sol index bcb53171..c4405d38 100644 --- a/test/account/PerHookData.t.sol +++ b/test/account/PerHookData.t.sol @@ -1,15 +1,15 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.25; -import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; -import {MockAccessControlHookPlugin} from "../mocks/plugins/MockAccessControlHookPlugin.sol"; import {Counter} from "../mocks/Counter.sol"; +import {MockAccessControlHookPlugin} from "../mocks/plugins/MockAccessControlHookPlugin.sol"; import {CustomValidationTestBase} from "../utils/CustomValidationTestBase.sol"; contract PerHookDataTest is CustomValidationTestBase { diff --git a/test/account/PermittedCallPermissions.t.sol b/test/account/PermittedCallPermissions.t.sol index 885015b0..607c50b4 100644 --- a/test/account/PermittedCallPermissions.t.sol +++ b/test/account/PermittedCallPermissions.t.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.19; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {ResultCreatorPlugin} from "../mocks/plugins/ReturnDataPluginMocks.sol"; import {PermittedCallerPlugin} from "../mocks/plugins/PermittedCallMocks.sol"; +import {ResultCreatorPlugin} from "../mocks/plugins/ReturnDataPluginMocks.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; contract PermittedCallPermissionsTest is AccountTestBase { diff --git a/test/account/SelfCallAuthorization.t.sol b/test/account/SelfCallAuthorization.t.sol index 4b6d04c7..743a0241 100644 --- a/test/account/SelfCallAuthorization.t.sol +++ b/test/account/SelfCallAuthorization.t.sol @@ -6,12 +6,13 @@ import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntry import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {IStandardExecutor, Call} from "../../src/interfaces/IStandardExecutor.sol"; + import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; +import {Call, IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; -import {AccountTestBase} from "../utils/AccountTestBase.sol"; import {ComprehensivePlugin} from "../mocks/plugins/ComprehensivePlugin.sol"; +import {AccountTestBase} from "../utils/AccountTestBase.sol"; contract SelfCallAuthorizationTest is AccountTestBase { ComprehensivePlugin public comprehensivePlugin; diff --git a/test/account/UpgradeableModularAccount.t.sol b/test/account/UpgradeableModularAccount.t.sol index 54af25c6..8b77332f 100644 --- a/test/account/UpgradeableModularAccount.t.sol +++ b/test/account/UpgradeableModularAccount.t.sol @@ -3,23 +3,27 @@ pragma solidity ^0.8.19; import {console} from "forge-std/Test.sol"; -import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; -import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; + import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {PluginManagerInternals} from "../../src/account/PluginManagerInternals.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {PluginManifest} from "../../src/interfaces/IPlugin.sol"; + import {IAccountLoupe} from "../../src/interfaces/IAccountLoupe.sol"; +import {PluginManifest} from "../../src/interfaces/IPlugin.sol"; import {IPluginManager} from "../../src/interfaces/IPluginManager.sol"; import {Call} from "../../src/interfaces/IStandardExecutor.sol"; -import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; + import {TokenReceiverPlugin} from "../../src/plugins/TokenReceiverPlugin.sol"; +import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; import {Counter} from "../mocks/Counter.sol"; -import {ComprehensivePlugin} from "../mocks/plugins/ComprehensivePlugin.sol"; + import {MockPlugin} from "../mocks/MockPlugin.sol"; +import {ComprehensivePlugin} from "../mocks/plugins/ComprehensivePlugin.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; import {TEST_DEFAULT_VALIDATION_ENTITY_ID} from "../utils/TestConstants.sol"; diff --git a/test/comparison/CompareSimpleAccount.t.sol b/test/comparison/CompareSimpleAccount.t.sol index 93b75ebe..a7f59b69 100644 --- a/test/comparison/CompareSimpleAccount.t.sol +++ b/test/comparison/CompareSimpleAccount.t.sol @@ -3,10 +3,10 @@ pragma solidity ^0.8.19; import {Test} from "forge-std/Test.sol"; -import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.sol"; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {SimpleAccount} from "@eth-infinitism/account-abstraction/samples/SimpleAccount.sol"; import {SimpleAccountFactory} from "@eth-infinitism/account-abstraction/samples/SimpleAccountFactory.sol"; @@ -34,8 +34,8 @@ contract CompareSimpleAccountTest is Test { Counter public counter; - uint256 public constant CALL_GAS_LIMIT = 500000; - uint256 public constant VERIFICATION_GAS_LIMIT = 500000; + uint256 public constant CALL_GAS_LIMIT = 500_000; + uint256 public constant VERIFICATION_GAS_LIMIT = 500_000; // helper function to compress 2 gas values into a single bytes32 function _encodeGas(uint256 g1, uint256 g2) internal pure returns (bytes32) { diff --git a/test/libraries/AccountStorage.t.sol b/test/libraries/AccountStorage.t.sol index 25594cd2..71c35b6e 100644 --- a/test/libraries/AccountStorage.t.sol +++ b/test/libraries/AccountStorage.t.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {Test} from "forge-std/Test.sol"; import {_ACCOUNT_STORAGE_SLOT} from "../../src/account/AccountStorage.sol"; import {AccountStorageInitializable} from "../../src/account/AccountStorageInitializable.sol"; import {MockDiamondStorageContract} from "../mocks/MockDiamondStorageContract.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {Test} from "forge-std/Test.sol"; // Test implementation of AccountStorageInitializable which is contained in UpgradeableModularAccount contract AccountStorageTest is Test { diff --git a/test/libraries/KnowSelectors.t.sol b/test/libraries/KnowSelectors.t.sol index 893b831b..7ce0910d 100644 --- a/test/libraries/KnowSelectors.t.sol +++ b/test/libraries/KnowSelectors.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.25; -import {Test} from "forge-std/Test.sol"; import {IAccount} from "@eth-infinitism/account-abstraction/interfaces/IAccount.sol"; import {IPaymaster} from "@eth-infinitism/account-abstraction/interfaces/IPaymaster.sol"; +import {Test} from "forge-std/Test.sol"; import {KnownSelectors} from "../../src/helpers/KnownSelectors.sol"; import {IPlugin} from "../../src/interfaces/IPlugin.sol"; diff --git a/test/mocks/Counter.t.sol b/test/mocks/Counter.t.sol index 8b0f5ccf..4749a846 100644 --- a/test/mocks/Counter.t.sol +++ b/test/mocks/Counter.t.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {Test} from "forge-std/Test.sol"; import {Counter} from "./Counter.sol"; +import {Test} from "forge-std/Test.sol"; contract CounterTest is Test { Counter public counter; diff --git a/test/mocks/MockPlugin.sol b/test/mocks/MockPlugin.sol index 3a2bf984..ef79c018 100644 --- a/test/mocks/MockPlugin.sol +++ b/test/mocks/MockPlugin.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.19; import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; -import {PluginManifest, IPlugin, PluginMetadata} from "../../src/interfaces/IPlugin.sol"; -import {IValidation} from "../../src/interfaces/IValidation.sol"; import {IExecutionHook} from "../../src/interfaces/IExecutionHook.sol"; +import {IPlugin, PluginManifest, PluginMetadata} from "../../src/interfaces/IPlugin.sol"; +import {IValidation} from "../../src/interfaces/IValidation.sol"; contract MockPlugin is ERC165 { // It's super inefficient to hold the entire abi-encoded manifest in storage, but this is fine since it's @@ -17,9 +17,9 @@ contract MockPlugin is ERC165 { // struct ManifestAssociatedFunction memory[] memory to storage not yet supported. bytes internal _manifest; - string public constant NAME = "Mock Plugin Modifiable"; - string public constant VERSION = "1.0.0"; - string public constant AUTHOR = "ERC-6900 Authors"; + string internal constant _NAME = "Mock Plugin Modifiable"; + string internal constant _VERSION = "1.0.0"; + string internal constant _AUTHOR = "ERC-6900 Authors"; event ReceivedCall(bytes msgData, uint256 msgValue); @@ -52,9 +52,9 @@ contract MockPlugin is ERC165 { function pluginMetadata() external pure returns (PluginMetadata memory) { PluginMetadata memory metadata; - metadata.name = NAME; - metadata.version = VERSION; - metadata.author = AUTHOR; + metadata.name = _NAME; + metadata.version = _VERSION; + metadata.author = _AUTHOR; return metadata; } diff --git a/test/mocks/SingleSignerFactoryFixture.sol b/test/mocks/SingleSignerFactoryFixture.sol index b3da73ec..b751d440 100644 --- a/test/mocks/SingleSignerFactoryFixture.sol +++ b/test/mocks/SingleSignerFactoryFixture.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; -import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; -import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; +import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; import {OptimizedTest} from "../utils/OptimizedTest.sol"; diff --git a/test/mocks/plugins/ComprehensivePlugin.sol b/test/mocks/plugins/ComprehensivePlugin.sol index 306e96a4..e4233cf2 100644 --- a/test/mocks/plugins/ComprehensivePlugin.sol +++ b/test/mocks/plugins/ComprehensivePlugin.sol @@ -3,9 +3,10 @@ pragma solidity ^0.8.19; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; +import {IExecutionHook} from "../../../src/interfaces/IExecutionHook.sol"; import { - ManifestExecutionHook, ManifestExecutionFunction, + ManifestExecutionHook, ManifestValidation, PluginManifest, PluginMetadata @@ -13,7 +14,6 @@ import { import {PluginManifest} from "../../../src/interfaces/IPlugin.sol"; import {IValidation} from "../../../src/interfaces/IValidation.sol"; import {IValidationHook} from "../../../src/interfaces/IValidationHook.sol"; -import {IExecutionHook} from "../../../src/interfaces/IExecutionHook.sol"; import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; @@ -28,9 +28,9 @@ contract ComprehensivePlugin is IValidation, IValidationHook, IExecutionHook, Ba SIG_VALIDATION } - string public constant NAME = "Comprehensive Plugin"; - string public constant VERSION = "1.0.0"; - string public constant AUTHOR = "ERC-6900 Authors"; + string internal constant _NAME = "Comprehensive Plugin"; + string internal constant _VERSION = "1.0.0"; + string internal constant _AUTHOR = "ERC-6900 Authors"; // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ // ┃ Execution functions ┃ @@ -176,9 +176,9 @@ contract ComprehensivePlugin is IValidation, IValidationHook, IExecutionHook, Ba function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { PluginMetadata memory metadata; - metadata.name = NAME; - metadata.version = VERSION; - metadata.author = AUTHOR; + metadata.name = _NAME; + metadata.version = _VERSION; + metadata.author = _AUTHOR; return metadata; } } diff --git a/test/mocks/plugins/DirectCallPlugin.sol b/test/mocks/plugins/DirectCallPlugin.sol index 8ab5dd42..7ccda4f0 100644 --- a/test/mocks/plugins/DirectCallPlugin.sol +++ b/test/mocks/plugins/DirectCallPlugin.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; +import {IExecutionHook} from "../../../src/interfaces/IExecutionHook.sol"; import {PluginManifest, PluginMetadata} from "../../../src/interfaces/IPlugin.sol"; import {IStandardExecutor} from "../../../src/interfaces/IStandardExecutor.sol"; -import {IExecutionHook} from "../../../src/interfaces/IExecutionHook.sol"; import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; diff --git a/test/mocks/plugins/MockAccessControlHookPlugin.sol b/test/mocks/plugins/MockAccessControlHookPlugin.sol index 6bd593f2..8fa49f8e 100644 --- a/test/mocks/plugins/MockAccessControlHookPlugin.sol +++ b/test/mocks/plugins/MockAccessControlHookPlugin.sol @@ -3,9 +3,10 @@ pragma solidity ^0.8.25; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; -import {PluginMetadata, PluginManifest} from "../../../src/interfaces/IPlugin.sol"; -import {IValidationHook} from "../../../src/interfaces/IValidationHook.sol"; +import {PluginManifest, PluginMetadata} from "../../../src/interfaces/IPlugin.sol"; + import {IStandardExecutor} from "../../../src/interfaces/IStandardExecutor.sol"; +import {IValidationHook} from "../../../src/interfaces/IValidationHook.sol"; import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; // A pre validaiton hook plugin that uses per-hook data. diff --git a/test/mocks/plugins/ReturnDataPluginMocks.sol b/test/mocks/plugins/ReturnDataPluginMocks.sol index 3adbb6e4..96a77ffe 100644 --- a/test/mocks/plugins/ReturnDataPluginMocks.sol +++ b/test/mocks/plugins/ReturnDataPluginMocks.sol @@ -9,8 +9,9 @@ import { PluginManifest, PluginMetadata } from "../../../src/interfaces/IPlugin.sol"; -import {IValidation} from "../../../src/interfaces/IValidation.sol"; + import {IStandardExecutor} from "../../../src/interfaces/IStandardExecutor.sol"; +import {IValidation} from "../../../src/interfaces/IValidation.sol"; import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; diff --git a/test/mocks/plugins/ValidationPluginMocks.sol b/test/mocks/plugins/ValidationPluginMocks.sol index 8d9b6ce2..3cfe1d09 100644 --- a/test/mocks/plugins/ValidationPluginMocks.sol +++ b/test/mocks/plugins/ValidationPluginMocks.sol @@ -6,8 +6,8 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import { ManifestExecutionFunction, ManifestValidation, - PluginMetadata, - PluginManifest + PluginManifest, + PluginMetadata } from "../../../src/interfaces/IPlugin.sol"; import {IValidation} from "../../../src/interfaces/IValidation.sol"; import {IValidationHook} from "../../../src/interfaces/IValidationHook.sol"; diff --git a/test/plugin/ERC20TokenLimitPlugin.t.sol b/test/plugin/ERC20TokenLimitPlugin.t.sol index 9693f98f..cf7b422a 100644 --- a/test/plugin/ERC20TokenLimitPlugin.t.sol +++ b/test/plugin/ERC20TokenLimitPlugin.t.sol @@ -1,19 +1,21 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {MockERC20} from "../mocks/MockERC20.sol"; +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {PluginEntity} from "../../src/helpers/PluginEntityLib.sol"; -import {ERC20TokenLimitPlugin} from "../../src/plugins/ERC20TokenLimitPlugin.sol"; -import {MockPlugin} from "../mocks/MockPlugin.sol"; -import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; + import {PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; -import {IStandardExecutor, Call} from "../../src/interfaces/IStandardExecutor.sol"; -import {PluginManifest} from "../../src/interfaces/IPlugin.sol"; + import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; +import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; +import {PluginManifest} from "../../src/interfaces/IPlugin.sol"; +import {Call, IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; +import {ERC20TokenLimitPlugin} from "../../src/plugins/ERC20TokenLimitPlugin.sol"; +import {MockPlugin} from "../mocks/MockPlugin.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; @@ -71,8 +73,8 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { nonce: 0, initCode: "", callData: abi.encodePacked(UpgradeableModularAccount.executeUserOp.selector, callData), - accountGasLimits: bytes32(bytes16(uint128(200000))) | bytes32(uint256(200000)), - preVerificationGas: 200000, + accountGasLimits: bytes32(bytes16(uint128(200_000))) | bytes32(uint256(200_000)), + preVerificationGas: 200_000, gasFees: bytes32(uint256(uint128(0))), paymasterAndData: "", signature: _encodeSignature(PluginEntityLib.pack(address(validationPlugin), 0), 1, "") @@ -102,13 +104,13 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { calls[2] = Call({ target: address(erc20), value: 0, - data: abi.encodeCall(IERC20.transfer, (recipient, 5 ether + 100000)) + data: abi.encodeCall(IERC20.transfer, (recipient, 5 ether + 100_000)) }); vm.startPrank(address(entryPoint)); assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether); acct.executeUserOp(_getPackedUO(abi.encodeCall(IStandardExecutor.executeBatch, (calls))), bytes32(0)); - assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether - 6 ether - 100001); + assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether - 6 ether - 100_001); } function test_userOp_executeBatch_approveAndTransferLimit() public { @@ -120,13 +122,13 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { calls[2] = Call({ target: address(erc20), value: 0, - data: abi.encodeCall(IERC20.approve, (recipient, 5 ether + 100000)) + data: abi.encodeCall(IERC20.approve, (recipient, 5 ether + 100_000)) }); vm.startPrank(address(entryPoint)); assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether); acct.executeUserOp(_getPackedUO(abi.encodeCall(IStandardExecutor.executeBatch, (calls))), bytes32(0)); - assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether - 6 ether - 100001); + assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether - 6 ether - 100_001); } function test_userOp_executeBatch_approveAndTransferLimit_fail() public { @@ -138,7 +140,7 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { calls[2] = Call({ target: address(erc20), value: 0, - data: abi.encodeCall(IERC20.approve, (recipient, 9 ether + 100000)) + data: abi.encodeCall(IERC20.approve, (recipient, 9 ether + 100_000)) }); vm.startPrank(address(entryPoint)); @@ -168,7 +170,7 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { calls[2] = Call({ target: address(erc20), value: 0, - data: abi.encodeCall(IERC20.approve, (recipient, 5 ether + 100000)) + data: abi.encodeCall(IERC20.approve, (recipient, 5 ether + 100_000)) }); assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether); @@ -176,6 +178,6 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { abi.encodeCall(IStandardExecutor.executeBatch, (calls)), _encodeSignature(PluginEntityLib.pack(address(validationPlugin), 0), 1, "") ); - assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether - 6 ether - 100001); + assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether - 6 ether - 100_001); } } diff --git a/test/plugin/NativeTokenLimitPlugin.t.sol b/test/plugin/NativeTokenLimitPlugin.t.sol index 33635216..0ae9ae40 100644 --- a/test/plugin/NativeTokenLimitPlugin.t.sol +++ b/test/plugin/NativeTokenLimitPlugin.t.sol @@ -5,13 +5,15 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {PluginEntity} from "../../src/helpers/PluginEntityLib.sol"; -import {NativeTokenLimitPlugin} from "../../src/plugins/NativeTokenLimitPlugin.sol"; -import {MockPlugin} from "../mocks/MockPlugin.sol"; -import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; + import {PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; -import {IStandardExecutor, Call} from "../../src/interfaces/IStandardExecutor.sol"; -import {PluginManifest} from "../../src/interfaces/IPlugin.sol"; + import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; +import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; +import {PluginManifest} from "../../src/interfaces/IPlugin.sol"; +import {Call, IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; +import {NativeTokenLimitPlugin} from "../../src/plugins/NativeTokenLimitPlugin.sol"; +import {MockPlugin} from "../mocks/MockPlugin.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; @@ -92,16 +94,16 @@ contract NativeTokenLimitPluginTest is AccountTestBase { // uses 10e - 200000 of gas assertEq(plugin.limits(0, address(acct)), 10 ether); uint256 result = acct.validateUserOp( - _getPackedUO(100000, 100000, 10 ether - 400000, 1, _getExecuteWithValue(0)), bytes32(0), 0 + _getPackedUO(100_000, 100_000, 10 ether - 400_000, 1, _getExecuteWithValue(0)), bytes32(0), 0 ); - assertEq(plugin.limits(0, address(acct)), 200000); + assertEq(plugin.limits(0, address(acct)), 200_000); uint256 expected = uint256(type(uint48).max) << 160; assertEq(result, expected); // uses 200k + 1 wei of gas vm.expectRevert(NativeTokenLimitPlugin.ExceededNativeTokenLimit.selector); - result = acct.validateUserOp(_getPackedUO(100000, 100000, 1, 1, _getExecuteWithValue(0)), bytes32(0), 0); + result = acct.validateUserOp(_getPackedUO(100_000, 100_000, 1, 1, _getExecuteWithValue(0)), bytes32(0), 0); } function test_userOp_executeLimit() public { @@ -130,24 +132,24 @@ contract NativeTokenLimitPluginTest is AccountTestBase { Call[] memory calls = new Call[](3); calls[0] = Call({target: recipient, value: 1, data: ""}); calls[1] = Call({target: recipient, value: 1 ether, data: ""}); - calls[2] = Call({target: recipient, value: 5 ether + 100000, data: ""}); + calls[2] = Call({target: recipient, value: 5 ether + 100_000, data: ""}); vm.startPrank(address(entryPoint)); assertEq(plugin.limits(0, address(acct)), 10 ether); acct.executeUserOp( _getPackedUO(0, 0, 0, 0, abi.encodeCall(IStandardExecutor.executeBatch, (calls))), bytes32(0) ); - assertEq(plugin.limits(0, address(acct)), 10 ether - 6 ether - 100001); - assertEq(recipient.balance, 6 ether + 100001); + assertEq(plugin.limits(0, address(acct)), 10 ether - 6 ether - 100_001); + assertEq(recipient.balance, 6 ether + 100_001); } function test_userOp_combinedExecLimit_success() public { assertEq(plugin.limits(0, address(acct)), 10 ether); PackedUserOperation[] memory uos = new PackedUserOperation[](1); - uos[0] = _getPackedUO(200000, 200000, 200000, 1, _getExecuteWithValue(5 ether)); + uos[0] = _getPackedUO(200_000, 200_000, 200_000, 1, _getExecuteWithValue(5 ether)); entryPoint.handleOps(uos, bundler); - assertEq(plugin.limits(0, address(acct)), 5 ether - 600000); + assertEq(plugin.limits(0, address(acct)), 5 ether - 600_000); assertEq(recipient.balance, 5 ether); } @@ -155,25 +157,26 @@ contract NativeTokenLimitPluginTest is AccountTestBase { Call[] memory calls = new Call[](3); calls[0] = Call({target: recipient, value: 1, data: ""}); calls[1] = Call({target: recipient, value: 1 ether, data: ""}); - calls[2] = Call({target: recipient, value: 5 ether + 100000, data: ""}); + calls[2] = Call({target: recipient, value: 5 ether + 100_000, data: ""}); vm.startPrank(address(entryPoint)); assertEq(plugin.limits(0, address(acct)), 10 ether); PackedUserOperation[] memory uos = new PackedUserOperation[](1); - uos[0] = _getPackedUO(200000, 200000, 200000, 1, abi.encodeCall(IStandardExecutor.executeBatch, (calls))); + uos[0] = + _getPackedUO(200_000, 200_000, 200_000, 1, abi.encodeCall(IStandardExecutor.executeBatch, (calls))); entryPoint.handleOps(uos, bundler); - assertEq(plugin.limits(0, address(acct)), 10 ether - 6 ether - 700001); - assertEq(recipient.balance, 6 ether + 100001); + assertEq(plugin.limits(0, address(acct)), 10 ether - 6 ether - 700_001); + assertEq(recipient.balance, 6 ether + 100_001); } function test_userOp_combinedExecLimit_failExec() public { assertEq(plugin.limits(0, address(acct)), 10 ether); PackedUserOperation[] memory uos = new PackedUserOperation[](1); - uos[0] = _getPackedUO(200000, 200000, 200000, 1, _getExecuteWithValue(10 ether)); + uos[0] = _getPackedUO(200_000, 200_000, 200_000, 1, _getExecuteWithValue(10 ether)); entryPoint.handleOps(uos, bundler); - assertEq(plugin.limits(0, address(acct)), 10 ether - 600000); + assertEq(plugin.limits(0, address(acct)), 10 ether - 600_000); assertEq(recipient.balance, 0); } @@ -187,12 +190,12 @@ contract NativeTokenLimitPluginTest is AccountTestBase { Call[] memory calls = new Call[](3); calls[0] = Call({target: recipient, value: 1, data: ""}); calls[1] = Call({target: recipient, value: 1 ether, data: ""}); - calls[2] = Call({target: recipient, value: 5 ether + 100000, data: ""}); + calls[2] = Call({target: recipient, value: 5 ether + 100_000, data: ""}); assertEq(plugin.limits(0, address(acct)), 10 ether); acct.executeWithAuthorization( abi.encodeCall(IStandardExecutor.executeBatch, (calls)), _encodeSignature(validationFunction, 1, "") ); - assertEq(plugin.limits(0, address(acct)), 4 ether - 100001); + assertEq(plugin.limits(0, address(acct)), 4 ether - 100_001); } } diff --git a/test/plugin/TokenReceiverPlugin.t.sol b/test/plugin/TokenReceiverPlugin.t.sol index 1e198f0b..32fa2a9a 100644 --- a/test/plugin/TokenReceiverPlugin.t.sol +++ b/test/plugin/TokenReceiverPlugin.t.sol @@ -2,15 +2,17 @@ pragma solidity ^0.8.19; import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.sol"; -import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; + import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; +import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {TokenReceiverPlugin} from "../../src/plugins/TokenReceiverPlugin.sol"; -import {SingleSignerFactoryFixture} from "../mocks/SingleSignerFactoryFixture.sol"; -import {MockERC721} from "../mocks/MockERC721.sol"; import {MockERC1155} from "../mocks/MockERC1155.sol"; +import {MockERC721} from "../mocks/MockERC721.sol"; +import {SingleSignerFactoryFixture} from "../mocks/SingleSignerFactoryFixture.sol"; + import {OptimizedTest} from "../utils/OptimizedTest.sol"; contract TokenReceiverPluginTest is OptimizedTest, IERC1155Receiver { diff --git a/test/samples/AllowlistPlugin.t.sol b/test/samples/AllowlistPlugin.t.sol index 441fbdcb..d2a28f5e 100644 --- a/test/samples/AllowlistPlugin.t.sol +++ b/test/samples/AllowlistPlugin.t.sol @@ -3,13 +3,14 @@ pragma solidity ^0.8.25; import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; -import {Call} from "../../src/interfaces/IStandardExecutor.sol"; -import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; +import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; +import {Call} from "../../src/interfaces/IStandardExecutor.sol"; + import {AllowlistPlugin} from "../../src/samples/permissionhooks/AllowlistPlugin.sol"; -import {CustomValidationTestBase} from "../utils/CustomValidationTestBase.sol"; import {Counter} from "../mocks/Counter.sol"; +import {CustomValidationTestBase} from "../utils/CustomValidationTestBase.sol"; contract AllowlistPluginTest is CustomValidationTestBase { AllowlistPlugin public allowlistPlugin; diff --git a/test/utils/AccountTestBase.sol b/test/utils/AccountTestBase.sol index 526365d0..94caaabb 100644 --- a/test/utils/AccountTestBase.sol +++ b/test/utils/AccountTestBase.sol @@ -5,10 +5,10 @@ import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.so import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; -import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; -import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; -import {IStandardExecutor, Call} from "../../src/interfaces/IStandardExecutor.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; +import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; +import {Call, IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; +import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; import {OptimizedTest} from "./OptimizedTest.sol"; import {TEST_DEFAULT_VALIDATION_ENTITY_ID as EXT_CONST_TEST_DEFAULT_VALIDATION_ENTITY_ID} from @@ -40,8 +40,8 @@ abstract contract AccountTestBase is OptimizedTest { // Re-declare the constant to prevent derived test contracts from having to import it uint32 public constant TEST_DEFAULT_VALIDATION_ENTITY_ID = EXT_CONST_TEST_DEFAULT_VALIDATION_ENTITY_ID; - uint256 public constant CALL_GAS_LIMIT = 100000; - uint256 public constant VERIFICATION_GAS_LIMIT = 1200000; + uint256 public constant CALL_GAS_LIMIT = 100_000; + uint256 public constant VERIFICATION_GAS_LIMIT = 1_200_000; struct PreValidationHookData { uint8 index; diff --git a/test/utils/CustomValidationTestBase.sol b/test/utils/CustomValidationTestBase.sol index bd119fa1..f9e37c27 100644 --- a/test/utils/CustomValidationTestBase.sol +++ b/test/utils/CustomValidationTestBase.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.25; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {PluginEntity} from "../../src/helpers/PluginEntityLib.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; -import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {AccountTestBase} from "./AccountTestBase.sol"; diff --git a/test/utils/OptimizedTest.sol b/test/utils/OptimizedTest.sol index d884193f..dc94380b 100644 --- a/test/utils/OptimizedTest.sol +++ b/test/utils/OptimizedTest.sol @@ -6,8 +6,9 @@ import {Test} from "forge-std/Test.sol"; import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; + import {TokenReceiverPlugin} from "../../src/plugins/TokenReceiverPlugin.sol"; +import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; /// @dev This contract provides functions to deploy optimized (via IR) precompiled contracts. By compiling just /// the source contracts (excluding the test suite) via IR, and using the resulting bytecode within the tests diff --git a/test/validation/SingleSignerValidation.t.sol b/test/validation/SingleSignerValidation.t.sol index a983ea93..e81706fe 100644 --- a/test/validation/SingleSignerValidation.t.sol +++ b/test/validation/SingleSignerValidation.t.sol @@ -8,9 +8,9 @@ import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAcc import {PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; +import {ContractOwner} from "../mocks/ContractOwner.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; import {TEST_DEFAULT_VALIDATION_ENTITY_ID} from "../utils/TestConstants.sol"; -import {ContractOwner} from "../mocks/ContractOwner.sol"; contract SingleSignerValidationTest is AccountTestBase { using MessageHashUtils for bytes32; From acfb938dc86f460e8f7009920c628064f2957744 Mon Sep 17 00:00:00 2001 From: Zer0dot Date: Thu, 18 Jul 2024 02:26:01 +0800 Subject: [PATCH 083/111] Zer0dot/remove redundancy (#99) --- src/account/PluginManager2.sol | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/account/PluginManager2.sol b/src/account/PluginManager2.sol index 845b80b8..b77ae1bf 100644 --- a/src/account/PluginManager2.sol +++ b/src/account/PluginManager2.sol @@ -124,20 +124,20 @@ abstract contract PluginManager2 { // Clear permission hooks EnumerableSet.Bytes32Set storage permissionHooks = _validationData.permissionHooks; - uint256 len = permissionHooks.length(); - for (uint256 i = 0; i < len; ++i) { + uint256 permissionHookLen = permissionHooks.length(); + for (uint256 i = 0; i < permissionHookLen; ++i) { bytes32 permissionHook = permissionHooks.at(0); permissionHooks.remove(permissionHook); address permissionHookPlugin = address(uint160(bytes20(permissionHook))); IPlugin(permissionHookPlugin).onUninstall(permissionHookUninstallDatas[i]); } } - delete _validationData.preValidationHooks; // Clear selectors - while (_validationData.selectors.length() > 0) { - bytes32 selector = _validationData.selectors.at(0); - _validationData.selectors.remove(selector); + uint256 selectorLen = _validationData.selectors.length(); + for (uint256 i = 0; i < selectorLen; ++i) { + bytes32 selectorSetValue = _validationData.selectors.at(0); + _validationData.selectors.remove(selectorSetValue); } if (uninstallData.length > 0) { From 151b0563db09826be8bd2a308971d10b0b547504 Mon Sep 17 00:00:00 2001 From: fangting-alchemy <119372438+fangting-alchemy@users.noreply.github.com> Date: Wed, 17 Jul 2024 11:46:34 -0700 Subject: [PATCH 084/111] fix: Add back pluginEntityLib test and update old references and docs (#100) --- src/account/AccountStorage.sol | 7 ++--- test/libraries/PluginEntityLib.t.sol | 40 ++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 test/libraries/PluginEntityLib.t.sol diff --git a/src/account/AccountStorage.sol b/src/account/AccountStorage.sol index ea2f4551..dbf0d956 100644 --- a/src/account/AccountStorage.sol +++ b/src/account/AccountStorage.sol @@ -15,7 +15,8 @@ struct SelectorData { // The plugin that implements this execution function. // If this is a native function, the address must remain address(0). address plugin; - // Whether or not the function needs runtime validation, or can be called by anyone. + // 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 isPublic; @@ -59,8 +60,8 @@ function getAccountStorage() pure returns (AccountStorage storage _storage) { using EnumerableSet for EnumerableSet.Bytes32Set; -function toSetValue(PluginEntity functionReference) pure returns (bytes32) { - return bytes32(PluginEntity.unwrap(functionReference)); +function toSetValue(PluginEntity pluginEntity) pure returns (bytes32) { + return bytes32(PluginEntity.unwrap(pluginEntity)); } function toPluginEntity(bytes32 setValue) pure returns (PluginEntity) { diff --git a/test/libraries/PluginEntityLib.t.sol b/test/libraries/PluginEntityLib.t.sol new file mode 100644 index 00000000..c93e1b61 --- /dev/null +++ b/test/libraries/PluginEntityLib.t.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {Test} from "forge-std/Test.sol"; + +import {PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; +import {PluginEntity} from "../../src/interfaces/IPluginManager.sol"; + +contract PluginEntityLibTest is Test { + using PluginEntityLib for PluginEntity; + + function testFuzz_pluginEntity_packing(address addr, uint32 entityId) public { + // console.log("addr: ", addr); + // console.log("entityId: ", vm.toString(entityId)); + PluginEntity fr = PluginEntityLib.pack(addr, entityId); + // console.log("packed: ", vm.toString(PluginEntity.unwrap(fr))); + (address addr2, uint32 entityId2) = PluginEntityLib.unpack(fr); + // console.log("addr2: ", addr2); + // console.log("entityId2: ", vm.toString(entityId2)); + assertEq(addr, addr2); + assertEq(entityId, entityId2); + } + + function testFuzz_pluginEntity_operators(PluginEntity a, PluginEntity b) public { + assertTrue(a.eq(a)); + assertTrue(b.eq(b)); + + if (PluginEntity.unwrap(a) == PluginEntity.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)); + } + } +} From 90540d632c7764dfa246edbe31984d2978e76d21 Mon Sep 17 00:00:00 2001 From: fangting-alchemy <119372438+fangting-alchemy@users.noreply.github.com> Date: Fri, 19 Jul 2024 10:17:20 -0700 Subject: [PATCH 085/111] fix: Rename plugin to module and update readme and docs (#106) --- README.md | 4 +- src/account/AccountLoupe.sol | 22 +- src/account/AccountStorage.sol | 35 ++- ...{PluginManager2.sol => ModuleManager2.sol} | 56 ++-- ...ternals.sol => ModuleManagerInternals.sol} | 128 ++++---- src/account/UpgradeableModularAccount.sol | 170 +++++------ src/helpers/KnownSelectors.sol | 16 +- src/helpers/ModuleEntityLib.sol | 35 +++ src/helpers/PluginEntityLib.sol | 35 --- src/helpers/ValidationConfigLib.sol | 26 +- src/interfaces/IAccountLoupe.sol | 26 +- src/interfaces/IExecutionHook.sol | 4 +- src/interfaces/{IPlugin.sol => IModule.sol} | 48 +-- ...{IPluginManager.sol => IModuleManager.sol} | 36 +-- src/interfaces/IStandardExecutor.sol | 3 +- src/interfaces/IValidation.sol | 4 +- src/interfaces/IValidationHook.sol | 4 +- .../BasePlugin.sol => modules/BaseModule.sol} | 16 +- .../ERC20TokenLimitModule.sol} | 36 +-- .../NativeTokenLimitModule.sol} | 30 +- .../TokenReceiverModule.sol} | 30 +- .../validation/ISingleSignerValidation.sol | 0 .../validation/SingleSignerValidation.sol | 24 +- ...llowlistPlugin.sol => AllowlistModule.sol} | 14 +- standard/ERCs/ERC6900Diagrams.excalidraw | 104 +++---- standard/ERCs/erc-6900.md | 284 +++++++++--------- .../MSCA_Shared_Components_Diagram.svg | 2 +- .../eip-6900/Modular_Account_Call_Flow.svg | 2 +- .../assets/eip-6900/Plugin_Execution_Flow.svg | 2 +- test/account/AccountExecHooks.t.sol | 68 ++--- test/account/AccountLoupe.t.sol | 104 +++---- test/account/AccountReturnData.t.sol | 52 ++-- ...ugin.t.sol => DirectCallsFromModule.t.sol} | 88 +++--- test/account/GlobalValidationTest.t.sol | 4 +- test/account/MultiValidation.t.sol | 20 +- test/account/PerHookData.t.sol | 28 +- test/account/PermittedCallPermissions.t.sol | 40 +-- test/account/SelfCallAuthorization.t.sol | 102 +++---- test/account/UpgradeableModularAccount.t.sol | 180 +++++------ test/account/ValidationIntersection.t.sol | 138 ++++----- test/libraries/KnowSelectors.t.sol | 6 +- ...nEntityLib.t.sol => ModuleEntityLib.t.sol} | 20 +- test/mocks/{MockPlugin.sol => MockModule.sol} | 32 +- test/mocks/SingleSignerFactoryFixture.sol | 8 +- .../ComprehensiveModule.sol} | 24 +- .../DirectCallModule.sol} | 10 +- .../MockAccessControlHookModule.sol} | 12 +- .../PermittedCallMocks.sol | 22 +- .../ReturnDataModuleMocks.sol} | 30 +- .../ValidationModuleMocks.sol} | 38 +-- .../ERC20TokenLimitModule.t.sol} | 62 ++-- .../NativeTokenLimitModule.t.sol} | 72 ++--- .../TokenReceiverModule.t.sol} | 20 +- ...listPlugin.t.sol => AllowlistModule.t.sol} | 62 ++-- test/utils/AccountTestBase.sol | 22 +- test/utils/CustomValidationTestBase.sol | 6 +- test/utils/OptimizedTest.sol | 10 +- test/validation/SingleSignerValidation.t.sol | 8 +- 58 files changed, 1241 insertions(+), 1243 deletions(-) rename src/account/{PluginManager2.sol => ModuleManager2.sol} (75%) rename src/account/{PluginManagerInternals.sol => ModuleManagerInternals.sol} (67%) create mode 100644 src/helpers/ModuleEntityLib.sol delete mode 100644 src/helpers/PluginEntityLib.sol rename src/interfaces/{IPlugin.sol => IModule.sol} (66%) rename src/interfaces/{IPluginManager.sol => IModuleManager.sol} (63%) rename src/{plugins/BasePlugin.sol => modules/BaseModule.sol} (83%) rename src/{plugins/ERC20TokenLimitPlugin.sol => modules/ERC20TokenLimitModule.sol} (85%) rename src/{plugins/NativeTokenLimitPlugin.sol => modules/NativeTokenLimitModule.sol} (89%) rename src/{plugins/TokenReceiverPlugin.sol => modules/TokenReceiverModule.sol} (78%) rename src/{plugins => modules}/validation/ISingleSignerValidation.sol (100%) rename src/{plugins => modules}/validation/SingleSignerValidation.sol (91%) rename src/samples/permissionhooks/{AllowlistPlugin.sol => AllowlistModule.sol} (91%) rename test/account/{DirectCallsFromPlugin.t.sol => DirectCallsFromModule.t.sol} (59%) rename test/libraries/{PluginEntityLib.t.sol => ModuleEntityLib.t.sol} (56%) rename test/mocks/{MockPlugin.sol => MockModule.sol} (75%) rename test/mocks/{plugins/ComprehensivePlugin.sol => modules/ComprehensiveModule.sol} (89%) rename test/mocks/{plugins/DirectCallPlugin.sol => modules/DirectCallModule.sol} (77%) rename test/mocks/{plugins/MockAccessControlHookPlugin.sol => modules/MockAccessControlHookModule.sol} (85%) rename test/mocks/{plugins => modules}/PermittedCallMocks.sol (53%) rename test/mocks/{plugins/ReturnDataPluginMocks.sol => modules/ReturnDataModuleMocks.sol} (84%) rename test/mocks/{plugins/ValidationPluginMocks.sol => modules/ValidationModuleMocks.sol} (86%) rename test/{plugin/ERC20TokenLimitPlugin.t.sol => module/ERC20TokenLimitModule.t.sol} (75%) rename test/{plugin/NativeTokenLimitPlugin.t.sol => module/NativeTokenLimitModule.t.sol} (74%) rename test/{plugin/TokenReceiverPlugin.t.sol => module/TokenReceiverModule.t.sol} (92%) rename test/samples/{AllowlistPlugin.t.sol => AllowlistModule.t.sol} (83%) diff --git a/README.md b/README.md index 1c90c710..b3b625b4 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Reference implementation for [ERC-6900](https://eips.ethereum.org/EIPS/eip-6900). It is an early draft implementation. -The implementation includes an upgradable modular account with two plugins (`SingleSignerValidation` and `TokenReceiverPlugin`). It is compliant with ERC-6900 with the latest updates. +The implementation includes an upgradable modular account with three modules (`SingleSignerValidation`, `TokenReceiverModule`, and `AllowlistModule`). It is compliant with ERC-6900 with the latest updates. ## Important Callouts @@ -11,7 +11,7 @@ The implementation includes an upgradable modular account with two plugins (`Sin ## Development -Anyone is welcome to submit feedback and/or PRs to improve code or add Plugins. +Anyone is welcome to submit feedback and/or PRs to improve code. ### Testing diff --git a/src/account/AccountLoupe.sol b/src/account/AccountLoupe.sol index ef679e4e..851c9cfa 100644 --- a/src/account/AccountLoupe.sol +++ b/src/account/AccountLoupe.sol @@ -7,7 +7,7 @@ import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {ExecutionHook, IAccountLoupe} from "../interfaces/IAccountLoupe.sol"; -import {IPluginManager, PluginEntity} from "../interfaces/IPluginManager.sol"; +import {IModuleManager, ModuleEntity} from "../interfaces/IModuleManager.sol"; import {IStandardExecutor} from "../interfaces/IStandardExecutor.sol"; import {getAccountStorage, toExecutionHook, toSelector} from "./AccountStorage.sol"; @@ -16,21 +16,21 @@ abstract contract AccountLoupe is IAccountLoupe { using EnumerableMap for EnumerableMap.AddressToUintMap; /// @inheritdoc IAccountLoupe - function getExecutionFunctionHandler(bytes4 selector) external view override returns (address plugin) { + function getExecutionFunctionHandler(bytes4 selector) external view override returns (address module) { if ( selector == IStandardExecutor.execute.selector || selector == IStandardExecutor.executeBatch.selector || selector == UUPSUpgradeable.upgradeToAndCall.selector - || selector == IPluginManager.installPlugin.selector - || selector == IPluginManager.uninstallPlugin.selector + || selector == IModuleManager.installModule.selector + || selector == IModuleManager.uninstallModule.selector ) { return address(this); } - return getAccountStorage().selectorData[selector].plugin; + return getAccountStorage().selectorData[selector].module; } /// @inheritdoc IAccountLoupe - function getSelectors(PluginEntity validationFunction) external view returns (bytes4[] memory) { + function getSelectors(ModuleEntity validationFunction) external view returns (bytes4[] memory) { uint256 length = getAccountStorage().validationData[validationFunction].selectors.length(); bytes4[] memory selectors = new bytes4[](length); @@ -62,7 +62,7 @@ abstract contract AccountLoupe is IAccountLoupe { } /// @inheritdoc IAccountLoupe - function getPermissionHooks(PluginEntity validationFunction) + function getPermissionHooks(ModuleEntity validationFunction) external view override @@ -80,17 +80,17 @@ abstract contract AccountLoupe is IAccountLoupe { } /// @inheritdoc IAccountLoupe - function getPreValidationHooks(PluginEntity validationFunction) + function getPreValidationHooks(ModuleEntity validationFunction) external view override - returns (PluginEntity[] memory preValidationHooks) + returns (ModuleEntity[] memory preValidationHooks) { preValidationHooks = getAccountStorage().validationData[validationFunction].preValidationHooks; } /// @inheritdoc IAccountLoupe - function getInstalledPlugins() external view override returns (address[] memory pluginAddresses) { - pluginAddresses = getAccountStorage().pluginManifestHashes.keys(); + function getInstalledModules() external view override returns (address[] memory moduleAddresses) { + moduleAddresses = getAccountStorage().moduleManifestHashes.keys(); } } diff --git a/src/account/AccountStorage.sol b/src/account/AccountStorage.sol index dbf0d956..2c480e32 100644 --- a/src/account/AccountStorage.sol +++ b/src/account/AccountStorage.sol @@ -5,16 +5,16 @@ import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; -import {PluginEntity} from "../interfaces/IPluginManager.sol"; +import {ModuleEntity} from "../interfaces/IModuleManager.sol"; // bytes = keccak256("ERC6900.UpgradeableModularAccount.Storage") bytes32 constant _ACCOUNT_STORAGE_SLOT = 0x9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e40; // Represents data associated with a specifc function selector. struct SelectorData { - // The plugin that implements this execution function. + // The module that implements this execution function. // If this is a native function, the address must remain address(0). - address plugin; + 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 @@ -32,7 +32,7 @@ struct ValidationData { // Whether or not this validation is a signature validator. bool isSignatureValidation; // The pre validation hooks for this validation function. - PluginEntity[] preValidationHooks; + ModuleEntity[] preValidationHooks; // Permission hooks for this validation function. EnumerableSet.Bytes32Set permissionHooks; // The set of selectors that may be validated by this validation function. @@ -43,11 +43,10 @@ struct AccountStorage { // AccountStorageInitializable variables uint8 initialized; bool initializing; - // Plugin metadata storage - EnumerableMap.AddressToUintMap pluginManifestHashes; + EnumerableMap.AddressToUintMap moduleManifestHashes; // Execution functions and their associated functions mapping(bytes4 => SelectorData) selectorData; - mapping(PluginEntity validationFunction => ValidationData) validationData; + mapping(ModuleEntity validationFunction => ValidationData) validationData; // For ERC165 introspection mapping(bytes4 => uint256) supportedIfaces; } @@ -60,30 +59,30 @@ function getAccountStorage() pure returns (AccountStorage storage _storage) { using EnumerableSet for EnumerableSet.Bytes32Set; -function toSetValue(PluginEntity pluginEntity) pure returns (bytes32) { - return bytes32(PluginEntity.unwrap(pluginEntity)); +function toSetValue(ModuleEntity moduleEntity) pure returns (bytes32) { + return bytes32(ModuleEntity.unwrap(moduleEntity)); } -function toPluginEntity(bytes32 setValue) pure returns (PluginEntity) { - return PluginEntity.wrap(bytes24(setValue)); +function toModuleEntity(bytes32 setValue) pure returns (ModuleEntity) { + return ModuleEntity.wrap(bytes24(setValue)); } // ExecutionHook layout: -// 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF______________________ Hook Plugin Entity +// 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF______________________ Hook Module Entity // 0x________________________________________________AA____________________ is pre hook // 0x__________________________________________________BB__________________ is post hook function toSetValue(ExecutionHook memory executionHook) pure returns (bytes32) { - return bytes32(PluginEntity.unwrap(executionHook.hookFunction)) + return bytes32(ModuleEntity.unwrap(executionHook.hookFunction)) | bytes32(executionHook.isPreHook ? uint256(1) << 56 : 0) | bytes32(executionHook.isPostHook ? uint256(1) << 48 : 0); } function toExecutionHook(bytes32 setValue) pure - returns (PluginEntity hookFunction, bool isPreHook, bool isPostHook) + returns (ModuleEntity hookFunction, bool isPreHook, bool isPostHook) { - hookFunction = PluginEntity.wrap(bytes24(setValue)); + hookFunction = ModuleEntity.wrap(bytes24(setValue)); isPreHook = (uint256(setValue) >> 56) & 0xFF == 1; isPostHook = (uint256(setValue) >> 48) & 0xFF == 1; } @@ -97,12 +96,12 @@ function toSelector(bytes32 setValue) pure returns (bytes4) { } /// @dev Helper function to get all elements of a set into memory. -function toPluginEntityArray(EnumerableSet.Bytes32Set storage set) view returns (PluginEntity[] memory) { +function toModuleEntityArray(EnumerableSet.Bytes32Set storage set) view returns (ModuleEntity[] memory) { uint256 length = set.length(); - PluginEntity[] memory result = new PluginEntity[](length); + ModuleEntity[] memory result = new ModuleEntity[](length); for (uint256 i = 0; i < length; ++i) { bytes32 key = set.at(i); - result[i] = PluginEntity.wrap(bytes24(key)); + result[i] = ModuleEntity.wrap(bytes24(key)); } return result; } diff --git a/src/account/PluginManager2.sol b/src/account/ModuleManager2.sol similarity index 75% rename from src/account/PluginManager2.sol rename to src/account/ModuleManager2.sol index b77ae1bf..e1868403 100644 --- a/src/account/PluginManager2.sol +++ b/src/account/ModuleManager2.sol @@ -3,16 +3,16 @@ pragma solidity ^0.8.25; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import {PluginEntityLib} from "../helpers/PluginEntityLib.sol"; +import {ModuleEntityLib} from "../helpers/ModuleEntityLib.sol"; import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; -import {IPlugin} from "../interfaces/IPlugin.sol"; -import {PluginEntity, ValidationConfig} from "../interfaces/IPluginManager.sol"; +import {IModule} from "../interfaces/IModule.sol"; +import {ModuleEntity, ValidationConfig} from "../interfaces/IModuleManager.sol"; import {ValidationData, getAccountStorage, toSetValue} from "./AccountStorage.sol"; // Temporary additional functions for a user-controlled install flow for validation functions. -abstract contract PluginManager2 { +abstract contract ModuleManager2 { using EnumerableSet for EnumerableSet.Bytes32Set; using ValidationConfigLib for ValidationConfig; @@ -20,10 +20,10 @@ abstract contract PluginManager2 { uint8 internal constant _RESERVED_VALIDATION_DATA_INDEX = 255; uint32 internal constant _SELF_PERMIT_VALIDATION_FUNCTIONID = type(uint32).max; - error PreValidationAlreadySet(PluginEntity validationFunction, PluginEntity preValidationFunction); - error ValidationAlreadySet(bytes4 selector, PluginEntity validationFunction); - error ValidationNotSet(bytes4 selector, PluginEntity validationFunction); - error PermissionAlreadySet(PluginEntity validationFunction, ExecutionHook hook); + error PreValidationAlreadySet(ModuleEntity validationFunction, ModuleEntity preValidationFunction); + error ValidationAlreadySet(bytes4 selector, ModuleEntity validationFunction); + error ValidationNotSet(bytes4 selector, ModuleEntity validationFunction); + error PermissionAlreadySet(ModuleEntity validationFunction, ExecutionHook hook); error PreValidationHookLimitExceeded(); function _installValidation( @@ -34,20 +34,20 @@ abstract contract PluginManager2 { bytes memory permissionHooks ) internal { ValidationData storage _validationData = - getAccountStorage().validationData[validationConfig.pluginEntity()]; + getAccountStorage().validationData[validationConfig.moduleEntity()]; if (preValidationHooks.length > 0) { - (PluginEntity[] memory preValidationFunctions, bytes[] memory initDatas) = - abi.decode(preValidationHooks, (PluginEntity[], bytes[])); + (ModuleEntity[] memory preValidationFunctions, bytes[] memory initDatas) = + abi.decode(preValidationHooks, (ModuleEntity[], bytes[])); for (uint256 i = 0; i < preValidationFunctions.length; ++i) { - PluginEntity preValidationFunction = preValidationFunctions[i]; + ModuleEntity preValidationFunction = preValidationFunctions[i]; _validationData.preValidationHooks.push(preValidationFunction); if (initDatas[i].length > 0) { - (address preValidationPlugin,) = PluginEntityLib.unpack(preValidationFunction); - IPlugin(preValidationPlugin).onInstall(initDatas[i]); + (address preValidationModule,) = ModuleEntityLib.unpack(preValidationFunction); + IModule(preValidationModule).onInstall(initDatas[i]); } } @@ -65,12 +65,12 @@ abstract contract PluginManager2 { ExecutionHook memory permissionFunction = permissionFunctions[i]; if (!_validationData.permissionHooks.add(toSetValue(permissionFunction))) { - revert PermissionAlreadySet(validationConfig.pluginEntity(), permissionFunction); + revert PermissionAlreadySet(validationConfig.moduleEntity(), permissionFunction); } if (initDatas[i].length > 0) { - (address executionPlugin,) = PluginEntityLib.unpack(permissionFunction.hookFunction); - IPlugin(executionPlugin).onInstall(initDatas[i]); + (address executionModule,) = ModuleEntityLib.unpack(permissionFunction.hookFunction); + IModule(executionModule).onInstall(initDatas[i]); } } } @@ -78,7 +78,7 @@ abstract contract PluginManager2 { for (uint256 i = 0; i < selectors.length; ++i) { bytes4 selector = selectors[i]; if (!_validationData.selectors.add(toSetValue(selector))) { - revert ValidationAlreadySet(selector, validationConfig.pluginEntity()); + revert ValidationAlreadySet(selector, validationConfig.moduleEntity()); } } @@ -88,13 +88,13 @@ abstract contract PluginManager2 { _validationData.isGlobal = validationConfig.isGlobal(); _validationData.isSignatureValidation = validationConfig.isSignatureValidation(); if (installData.length > 0) { - IPlugin(validationConfig.plugin()).onInstall(installData); + IModule(validationConfig.module()).onInstall(installData); } } } function _uninstallValidation( - PluginEntity validationFunction, + ModuleEntity validationFunction, bytes calldata uninstallData, bytes calldata preValidationHookUninstallData, bytes calldata permissionHookUninstallData @@ -108,12 +108,12 @@ abstract contract PluginManager2 { bytes[] memory preValidationHookUninstallDatas = abi.decode(preValidationHookUninstallData, (bytes[])); // Clear pre validation hooks - PluginEntity[] storage preValidationHooks = _validationData.preValidationHooks; + ModuleEntity[] storage preValidationHooks = _validationData.preValidationHooks; for (uint256 i = 0; i < preValidationHooks.length; ++i) { - PluginEntity preValidationFunction = preValidationHooks[i]; + ModuleEntity preValidationFunction = preValidationHooks[i]; if (preValidationHookUninstallDatas[0].length > 0) { - (address preValidationPlugin,) = PluginEntityLib.unpack(preValidationFunction); - IPlugin(preValidationPlugin).onUninstall(preValidationHookUninstallDatas[0]); + (address preValidationModule,) = ModuleEntityLib.unpack(preValidationFunction); + IModule(preValidationModule).onUninstall(preValidationHookUninstallDatas[0]); } } delete _validationData.preValidationHooks; @@ -128,8 +128,8 @@ abstract contract PluginManager2 { for (uint256 i = 0; i < permissionHookLen; ++i) { bytes32 permissionHook = permissionHooks.at(0); permissionHooks.remove(permissionHook); - address permissionHookPlugin = address(uint160(bytes20(permissionHook))); - IPlugin(permissionHookPlugin).onUninstall(permissionHookUninstallDatas[i]); + address permissionHookModule = address(uint160(bytes20(permissionHook))); + IModule(permissionHookModule).onUninstall(permissionHookUninstallDatas[i]); } } @@ -141,8 +141,8 @@ abstract contract PluginManager2 { } if (uninstallData.length > 0) { - (address plugin,) = PluginEntityLib.unpack(validationFunction); - IPlugin(plugin).onUninstall(uninstallData); + (address module,) = ModuleEntityLib.unpack(validationFunction); + IModule(module).onUninstall(uninstallData); } } } diff --git a/src/account/PluginManagerInternals.sol b/src/account/ModuleManagerInternals.sol similarity index 67% rename from src/account/PluginManagerInternals.sol rename to src/account/ModuleManagerInternals.sol index e473730e..68d6ea1d 100644 --- a/src/account/PluginManagerInternals.sol +++ b/src/account/ModuleManagerInternals.sol @@ -7,39 +7,39 @@ import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {KnownSelectors} from "../helpers/KnownSelectors.sol"; -import {PluginEntityLib} from "../helpers/PluginEntityLib.sol"; +import {ModuleEntityLib} from "../helpers/ModuleEntityLib.sol"; import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; -import {IPlugin, ManifestExecutionHook, ManifestValidation, PluginManifest} from "../interfaces/IPlugin.sol"; -import {IPluginManager, PluginEntity} from "../interfaces/IPluginManager.sol"; +import {IModule, ManifestExecutionHook, ManifestValidation, ModuleManifest} from "../interfaces/IModule.sol"; +import {IModuleManager, ModuleEntity} from "../interfaces/IModuleManager.sol"; import {AccountStorage, SelectorData, getAccountStorage, toSetValue} from "./AccountStorage.sol"; -abstract contract PluginManagerInternals is IPluginManager { +abstract contract ModuleManagerInternals is IModuleManager { using EnumerableSet for EnumerableSet.Bytes32Set; using EnumerableMap for EnumerableMap.AddressToUintMap; - using PluginEntityLib for PluginEntity; + using ModuleEntityLib for ModuleEntity; error ArrayLengthMismatch(); error Erc4337FunctionNotAllowed(bytes4 selector); error ExecutionFunctionAlreadySet(bytes4 selector); - error InvalidPluginManifest(); - error IPluginFunctionNotAllowed(bytes4 selector); + error InvalidModuleManifest(); + error IModuleFunctionNotAllowed(bytes4 selector); error NativeFunctionNotAllowed(bytes4 selector); - error NullPluginEntity(); - error NullPlugin(); - error PluginAlreadyInstalled(address plugin); - error PluginInstallCallbackFailed(address plugin, bytes revertReason); - error PluginInterfaceNotSupported(address plugin); - error PluginNotInstalled(address plugin); - error ValidationFunctionAlreadySet(bytes4 selector, PluginEntity validationFunction); + error NullModuleEntity(); + error NullModule(); + error ModuleAlreadyInstalled(address module); + error ModuleInstallCallbackFailed(address module, bytes revertReason); + error ModuleInterfaceNotSupported(address module); + error ModuleNotInstalled(address module); + error ValidationFunctionAlreadySet(bytes4 selector, ModuleEntity validationFunction); // Storage update operations - function _setExecutionFunction(bytes4 selector, bool isPublic, bool allowGlobalValidation, address plugin) + function _setExecutionFunction(bytes4 selector, bool isPublic, bool allowGlobalValidation, address module) internal { SelectorData storage _selectorData = getAccountStorage().selectorData[selector]; - if (_selectorData.plugin != address(0)) { + if (_selectorData.module != address(0)) { revert ExecutionFunctionAlreadySet(selector); } @@ -49,13 +49,13 @@ abstract contract PluginManagerInternals is IPluginManager { revert NativeFunctionNotAllowed(selector); } - // Make sure incoming execution function is not a function in IPlugin - if (KnownSelectors.isIPluginFunction(selector)) { - revert IPluginFunctionNotAllowed(selector); + // Make sure incoming execution function is not a function in IModule + if (KnownSelectors.isIModuleFunction(selector)) { + revert IModuleFunctionNotAllowed(selector); } // Also make sure it doesn't collide with functions defined by ERC-4337 - // and called by the entry point. This prevents a malicious plugin from + // and called by the entry point. This prevents a malicious module from // sneaking in a function with the same selector as e.g. // `validatePaymasterUserOp` and turning the account into their own // personal paymaster. @@ -63,7 +63,7 @@ abstract contract PluginManagerInternals is IPluginManager { revert Erc4337FunctionNotAllowed(selector); } - _selectorData.plugin = plugin; + _selectorData.module = module; _selectorData.isPublic = isPublic; _selectorData.allowGlobalValidation = allowGlobalValidation; } @@ -71,15 +71,15 @@ abstract contract PluginManagerInternals is IPluginManager { function _removeExecutionFunction(bytes4 selector) internal { SelectorData storage _selectorData = getAccountStorage().selectorData[selector]; - _selectorData.plugin = address(0); + _selectorData.module = address(0); _selectorData.isPublic = false; _selectorData.allowGlobalValidation = false; } - function _addValidationFunction(address plugin, ManifestValidation memory mv) internal { + function _addValidationFunction(address module, ManifestValidation memory mv) internal { AccountStorage storage _storage = getAccountStorage(); - PluginEntity validationFunction = PluginEntityLib.pack(plugin, mv.entityId); + ModuleEntity validationFunction = ModuleEntityLib.pack(module, mv.entityId); if (mv.isDefault) { _storage.validationData[validationFunction].isGlobal = true; @@ -97,10 +97,10 @@ abstract contract PluginManagerInternals is IPluginManager { } } - function _removeValidationFunction(address plugin, ManifestValidation memory mv) internal { + function _removeValidationFunction(address module, ManifestValidation memory mv) internal { AccountStorage storage _storage = getAccountStorage(); - PluginEntity validationFunction = PluginEntityLib.pack(plugin, mv.entityId); + ModuleEntity validationFunction = ModuleEntityLib.pack(module, mv.entityId); _storage.validationData[validationFunction].isGlobal = false; _storage.validationData[validationFunction].isSignatureValidation = false; @@ -114,7 +114,7 @@ abstract contract PluginManagerInternals is IPluginManager { function _addExecHooks( EnumerableSet.Bytes32Set storage hooks, - PluginEntity hookFunction, + ModuleEntity hookFunction, bool isPreExecHook, bool isPostExecHook ) internal { @@ -127,7 +127,7 @@ abstract contract PluginManagerInternals is IPluginManager { function _removeExecHooks( EnumerableSet.Bytes32Set storage hooks, - PluginEntity hookFunction, + ModuleEntity hookFunction, bool isPreExecHook, bool isPostExecHook ) internal { @@ -138,31 +138,31 @@ abstract contract PluginManagerInternals is IPluginManager { ); } - function _installPlugin(address plugin, bytes32 manifestHash, bytes memory pluginInstallData) internal { + function _installModule(address module, bytes32 manifestHash, bytes memory moduleInstallData) internal { AccountStorage storage _storage = getAccountStorage(); - if (plugin == address(0)) { - revert NullPlugin(); + if (module == address(0)) { + revert NullModule(); } - // Check if the plugin exists. - if (_storage.pluginManifestHashes.contains(plugin)) { - revert PluginAlreadyInstalled(plugin); + // Check if the module exists. + if (_storage.moduleManifestHashes.contains(module)) { + revert ModuleAlreadyInstalled(module); } - // Check that the plugin supports the IPlugin interface. - if (!ERC165Checker.supportsInterface(plugin, type(IPlugin).interfaceId)) { - revert PluginInterfaceNotSupported(plugin); + // Check that the module supports the IModule interface. + if (!ERC165Checker.supportsInterface(module, type(IModule).interfaceId)) { + revert ModuleInterfaceNotSupported(module); } // Check manifest hash. - PluginManifest memory manifest = IPlugin(plugin).pluginManifest(); - if (!_isValidPluginManifest(manifest, manifestHash)) { - revert InvalidPluginManifest(); + ModuleManifest memory manifest = IModule(module).moduleManifest(); + if (!_isValidModuleManifest(manifest, manifestHash)) { + revert InvalidModuleManifest(); } - // Add the plugin metadata to the account - _storage.pluginManifestHashes.set(plugin, uint256(manifestHash)); + // Add the module metadata to the account + _storage.moduleManifestHashes.set(module, uint256(manifestHash)); // Update components according to the manifest. uint256 length = manifest.executionFunctions.length; @@ -170,21 +170,21 @@ abstract contract PluginManagerInternals is IPluginManager { bytes4 selector = manifest.executionFunctions[i].executionSelector; bool isPublic = manifest.executionFunctions[i].isPublic; bool allowGlobalValidation = manifest.executionFunctions[i].allowGlobalValidation; - _setExecutionFunction(selector, isPublic, allowGlobalValidation, plugin); + _setExecutionFunction(selector, isPublic, allowGlobalValidation, module); } length = manifest.validationFunctions.length; for (uint256 i = 0; i < length; ++i) { // Todo: limit this to only "direct runtime call" validation path (old EFP), // and add a way for the user to specify permission/pre-val hooks here. - _addValidationFunction(plugin, manifest.validationFunctions[i]); + _addValidationFunction(module, manifest.validationFunctions[i]); } length = manifest.executionHooks.length; for (uint256 i = 0; i < length; ++i) { ManifestExecutionHook memory mh = manifest.executionHooks[i]; EnumerableSet.Bytes32Set storage execHooks = _storage.selectorData[mh.executionSelector].executionHooks; - PluginEntity hookFunction = PluginEntityLib.pack(plugin, mh.entityId); + ModuleEntity hookFunction = ModuleEntityLib.pack(module, mh.entityId); _addExecHooks(execHooks, hookFunction, mh.isPreHook, mh.isPostHook); } @@ -193,30 +193,30 @@ abstract contract PluginManagerInternals is IPluginManager { _storage.supportedIfaces[manifest.interfaceIds[i]] += 1; } - // Initialize the plugin storage for the account. + // Initialize the module storage for the account. // solhint-disable-next-line no-empty-blocks - try IPlugin(plugin).onInstall(pluginInstallData) {} + try IModule(module).onInstall(moduleInstallData) {} catch (bytes memory revertReason) { - revert PluginInstallCallbackFailed(plugin, revertReason); + revert ModuleInstallCallbackFailed(module, revertReason); } - emit PluginInstalled(plugin, manifestHash); + emit ModuleInstalled(module, manifestHash); } - function _uninstallPlugin(address plugin, PluginManifest memory manifest, bytes memory uninstallData) + function _uninstallModule(address module, ModuleManifest memory manifest, bytes memory uninstallData) internal { AccountStorage storage _storage = getAccountStorage(); - // Check if the plugin exists. - if (!_storage.pluginManifestHashes.contains(plugin)) { - revert PluginNotInstalled(plugin); + // Check if the module exists. + if (!_storage.moduleManifestHashes.contains(module)) { + revert ModuleNotInstalled(module); } // Check manifest hash. - bytes32 manifestHash = bytes32(_storage.pluginManifestHashes.get(plugin)); - if (!_isValidPluginManifest(manifest, manifestHash)) { - revert InvalidPluginManifest(); + bytes32 manifestHash = bytes32(_storage.moduleManifestHashes.get(module)); + if (!_isValidModuleManifest(manifest, manifestHash)) { + revert InvalidModuleManifest(); } // Remove components according to the manifest, in reverse order (by component type) of their installation. @@ -224,14 +224,14 @@ abstract contract PluginManagerInternals is IPluginManager { uint256 length = manifest.executionHooks.length; for (uint256 i = 0; i < length; ++i) { ManifestExecutionHook memory mh = manifest.executionHooks[i]; - PluginEntity hookFunction = PluginEntityLib.pack(plugin, mh.entityId); + ModuleEntity hookFunction = ModuleEntityLib.pack(module, mh.entityId); EnumerableSet.Bytes32Set storage execHooks = _storage.selectorData[mh.executionSelector].executionHooks; _removeExecHooks(execHooks, hookFunction, mh.isPreHook, mh.isPostHook); } length = manifest.validationFunctions.length; for (uint256 i = 0; i < length; ++i) { - _removeValidationFunction(plugin, manifest.validationFunctions[i]); + _removeValidationFunction(module, manifest.validationFunctions[i]); } length = manifest.executionFunctions.length; @@ -245,21 +245,21 @@ abstract contract PluginManagerInternals is IPluginManager { _storage.supportedIfaces[manifest.interfaceIds[i]] -= 1; } - // Remove the plugin metadata from the account. - _storage.pluginManifestHashes.remove(plugin); + // Remove the module metadata from the account. + _storage.moduleManifestHashes.remove(module); - // Clear the plugin storage for the account. + // Clear the module storage for the account. bool onUninstallSuccess = true; // solhint-disable-next-line no-empty-blocks - try IPlugin(plugin).onUninstall(uninstallData) {} + try IModule(module).onUninstall(uninstallData) {} catch { onUninstallSuccess = false; } - emit PluginUninstalled(plugin, onUninstallSuccess); + emit ModuleUninstalled(module, onUninstallSuccess); } - function _isValidPluginManifest(PluginManifest memory manifest, bytes32 manifestHash) + function _isValidModuleManifest(ModuleManifest memory manifest, bytes32 manifestHash) internal pure returns (bool) diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 4f64cdd3..9f102313 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -12,15 +12,15 @@ import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeab import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import {PluginEntityLib} from "../helpers/PluginEntityLib.sol"; +import {ModuleEntityLib} from "../helpers/ModuleEntityLib.sol"; import {SparseCalldataSegmentLib} from "../helpers/SparseCalldataSegmentLib.sol"; import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; import {_coalescePreValidation, _coalesceValidation} from "../helpers/ValidationResHelpers.sol"; import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; -import {IPlugin, PluginManifest} from "../interfaces/IPlugin.sol"; -import {IPluginManager, PluginEntity, ValidationConfig} from "../interfaces/IPluginManager.sol"; +import {IModule, ModuleManifest} from "../interfaces/IModule.sol"; +import {IModuleManager, ModuleEntity, ValidationConfig} from "../interfaces/IModuleManager.sol"; import {Call, IStandardExecutor} from "../interfaces/IStandardExecutor.sol"; import {IValidation} from "../interfaces/IValidation.sol"; import {IValidationHook} from "../interfaces/IValidationHook.sol"; @@ -29,8 +29,8 @@ import {AccountLoupe} from "./AccountLoupe.sol"; import {AccountStorage, getAccountStorage, toExecutionHook, toSetValue} from "./AccountStorage.sol"; import {AccountStorageInitializable} from "./AccountStorageInitializable.sol"; -import {PluginManager2} from "./PluginManager2.sol"; -import {PluginManagerInternals} from "./PluginManagerInternals.sol"; +import {ModuleManager2} from "./ModuleManager2.sol"; +import {ModuleManagerInternals} from "./ModuleManagerInternals.sol"; contract UpgradeableModularAccount is AccountExecutor, @@ -41,18 +41,18 @@ contract UpgradeableModularAccount is IERC1271, IStandardExecutor, IAccountExecute, - PluginManagerInternals, - PluginManager2, + ModuleManagerInternals, + ModuleManager2, UUPSUpgradeable { using EnumerableSet for EnumerableSet.Bytes32Set; - using PluginEntityLib for PluginEntity; + using ModuleEntityLib for ModuleEntity; using ValidationConfigLib for ValidationConfig; using SparseCalldataSegmentLib for bytes; struct PostExecToRun { bytes preExecHookReturnData; - PluginEntity postExecHook; + ModuleEntity postExecHook; } IEntryPoint private immutable _ENTRY_POINT; @@ -68,28 +68,28 @@ contract UpgradeableModularAccount is event ModularAccountInitialized(IEntryPoint indexed entryPoint); error AuthorizeUpgradeReverted(bytes revertReason); - error ExecFromPluginNotPermitted(address plugin, bytes4 selector); - error ExecFromPluginExternalNotPermitted(address plugin, address target, uint256 value, bytes data); - error NativeTokenSpendingNotPermitted(address plugin); + error ExecFromModuleNotPermitted(address module, bytes4 selector); + error ExecFromModuleExternalNotPermitted(address module, address target, uint256 value, bytes data); + error NativeTokenSpendingNotPermitted(address module); error NonCanonicalEncoding(); error NotEntryPoint(); - error PostExecHookReverted(address plugin, uint32 entityId, bytes revertReason); - error PreExecHookReverted(address plugin, uint32 entityId, bytes revertReason); - error PreRuntimeValidationHookFailed(address plugin, uint32 entityId, bytes revertReason); + error PostExecHookReverted(address module, uint32 entityId, bytes revertReason); + error PreExecHookReverted(address module, uint32 entityId, bytes revertReason); + error PreRuntimeValidationHookFailed(address module, uint32 entityId, bytes revertReason); error RequireUserOperationContext(); error RuntimeValidationFunctionMissing(bytes4 selector); - error RuntimeValidationFunctionReverted(address plugin, uint32 entityId, bytes revertReason); + error RuntimeValidationFunctionReverted(address module, uint32 entityId, bytes revertReason); error SelfCallRecursionDepthExceeded(); - error SignatureValidationInvalid(address plugin, uint32 entityId); - error UnexpectedAggregator(address plugin, uint32 entityId, address aggregator); + error SignatureValidationInvalid(address module, uint32 entityId); + error UnexpectedAggregator(address module, uint32 entityId, address aggregator); error UnrecognizedFunction(bytes4 selector); error ValidationFunctionMissing(bytes4 selector); - error ValidationDoesNotApply(bytes4 selector, address plugin, uint32 entityId, bool isGlobal); + error ValidationDoesNotApply(bytes4 selector, address module, uint32 entityId, bool isGlobal); error ValidationSignatureSegmentMissing(); error SignatureSegmentOutOfOrder(); // Wraps execution of a native function with runtime validation and hooks - // Used for upgradeTo, upgradeToAndCall, execute, executeBatch, installPlugin, uninstallPlugin + // Used for upgradeTo, upgradeToAndCall, execute, executeBatch, installModule, uninstallModule modifier wrapNativeFunction() { (PostExecToRun[] memory postPermissionHooks, PostExecToRun[] memory postExecHooks) = _checkPermittedCallerAndAssociatedHooks(); @@ -107,23 +107,23 @@ contract UpgradeableModularAccount is // EXTERNAL FUNCTIONS - /// @notice Initializes the account with a set of plugins - /// @param plugins The plugins to install - /// @param manifestHashes The manifest hashes of the plugins to install - /// @param pluginInstallDatas The plugin install datas of the plugins to install + /// @notice Initializes the account with a set of modules + /// @param modules The modules to install + /// @param manifestHashes The manifest hashes of the modules to install + /// @param moduleInstallDatas The module install datas of the modules to install function initialize( - address[] memory plugins, + address[] memory modules, bytes32[] memory manifestHashes, - bytes[] memory pluginInstallDatas + bytes[] memory moduleInstallDatas ) external initializer { - uint256 length = plugins.length; + uint256 length = modules.length; - if (length != manifestHashes.length || length != pluginInstallDatas.length) { + if (length != manifestHashes.length || length != moduleInstallDatas.length) { revert ArrayLengthMismatch(); } for (uint256 i = 0; i < length; ++i) { - _installPlugin(plugins[i], manifestHashes[i], pluginInstallDatas[i]); + _installModule(modules[i], manifestHashes[i], moduleInstallDatas[i]); } emit ModularAccountInitialized(_ENTRY_POINT); @@ -133,10 +133,10 @@ contract UpgradeableModularAccount is /// @notice Fallback function /// @dev We route calls to execution functions based on incoming msg.sig - /// @dev If there's no plugin associated with this function selector, revert + /// @dev If there's no module associated with this function selector, revert fallback(bytes calldata) external payable returns (bytes memory) { - address execPlugin = getAccountStorage().selectorData[msg.sig].plugin; - if (execPlugin == address(0)) { + address execModule = getAccountStorage().selectorData[msg.sig].module; + if (execModule == address(0)) { revert UnrecognizedFunction(msg.sig); } @@ -147,10 +147,10 @@ contract UpgradeableModularAccount is postExecHooks = _doPreHooks(getAccountStorage().selectorData[msg.sig].executionHooks, msg.data); // execute the function, bubbling up any reverts - (bool execSuccess, bytes memory execReturnData) = execPlugin.call(msg.data); + (bool execSuccess, bytes memory execReturnData) = execModule.call(msg.data); if (!execSuccess) { - // Bubble up revert reasons from plugins + // Bubble up revert reasons from modules assembly ("memory-safe") { revert(add(execReturnData, 32), mload(execReturnData)) } @@ -168,7 +168,7 @@ contract UpgradeableModularAccount is revert NotEntryPoint(); } - PluginEntity userOpValidationFunction = PluginEntity.wrap(bytes24(userOp.signature[:24])); + ModuleEntity userOpValidationFunction = ModuleEntity.wrap(bytes24(userOp.signature[:24])); PostExecToRun[] memory postPermissionHooks = _doPreHooks(getAccountStorage().validationData[userOpValidationFunction].permissionHooks, msg.data); @@ -221,7 +221,7 @@ contract UpgradeableModularAccount is returns (bytes memory) { // Revert if the provided `authorization` less than 21 bytes long, rather than right-padding. - PluginEntity runtimeValidationFunction = PluginEntity.wrap(bytes24(authorization[:24])); + ModuleEntity runtimeValidationFunction = ModuleEntity.wrap(bytes24(authorization[:24])); // Check if the runtime validation function is allowed to be called bool isGlobalValidation = uint8(authorization[24]) == 1; @@ -247,32 +247,32 @@ contract UpgradeableModularAccount is return returnData; } - /// @inheritdoc IPluginManager + /// @inheritdoc IModuleManager /// @notice May be validated by a global validation. - function installPlugin(address plugin, bytes32 manifestHash, bytes calldata pluginInstallData) + function installModule(address module, bytes32 manifestHash, bytes calldata moduleInstallData) external override wrapNativeFunction { - _installPlugin(plugin, manifestHash, pluginInstallData); + _installModule(module, manifestHash, moduleInstallData); } - /// @inheritdoc IPluginManager + /// @inheritdoc IModuleManager /// @notice May be validated by a global validation. - function uninstallPlugin(address plugin, bytes calldata config, bytes calldata pluginUninstallData) + function uninstallModule(address module, bytes calldata config, bytes calldata moduleUninstallData) external override wrapNativeFunction { - PluginManifest memory manifest; + ModuleManifest memory manifest; if (config.length > 0) { - manifest = abi.decode(config, (PluginManifest)); + manifest = abi.decode(config, (ModuleManifest)); } else { - manifest = IPlugin(plugin).pluginManifest(); + manifest = IModule(module).moduleManifest(); } - _uninstallPlugin(plugin, manifest, pluginUninstallData); + _uninstallModule(module, manifest, moduleUninstallData); } /// @notice Initializes the account with a validation function added to the global pool. @@ -290,7 +290,7 @@ contract UpgradeableModularAccount is emit ModularAccountInitialized(_ENTRY_POINT); } - /// @inheritdoc IPluginManager + /// @inheritdoc IModuleManager /// @notice May be validated by a global validation. function installValidation( ValidationConfig validationConfig, @@ -302,10 +302,10 @@ contract UpgradeableModularAccount is _installValidation(validationConfig, selectors, installData, preValidationHooks, permissionHooks); } - /// @inheritdoc IPluginManager + /// @inheritdoc IModuleManager /// @notice May be validated by a global validation. function uninstallValidation( - PluginEntity validationFunction, + ModuleEntity validationFunction, bytes calldata uninstallData, bytes calldata preValidationHookUninstallData, bytes calldata permissionHookUninstallData @@ -345,15 +345,15 @@ contract UpgradeableModularAccount is function isValidSignature(bytes32 hash, bytes calldata signature) public view override returns (bytes4) { AccountStorage storage _storage = getAccountStorage(); - PluginEntity sigValidation = PluginEntity.wrap(bytes24(signature)); + ModuleEntity sigValidation = ModuleEntity.wrap(bytes24(signature)); - (address plugin, uint32 entityId) = sigValidation.unpack(); + (address module, uint32 entityId) = sigValidation.unpack(); if (!_storage.validationData[sigValidation].isSignatureValidation) { - revert SignatureValidationInvalid(plugin, entityId); + revert SignatureValidationInvalid(module, entityId); } if ( - IValidation(plugin).validateSignature(address(this), entityId, msg.sender, hash, signature[24:]) + IValidation(module).validateSignature(address(this), entityId, msg.sender, hash, signature[24:]) == _1271_MAGIC_VALUE ) { return _1271_MAGIC_VALUE; @@ -381,7 +381,7 @@ contract UpgradeableModularAccount is } // Revert if the provided `authorization` less than 21 bytes long, rather than right-padding. - PluginEntity userOpValidationFunction = PluginEntity.wrap(bytes24(userOp.signature[:24])); + ModuleEntity userOpValidationFunction = ModuleEntity.wrap(bytes24(userOp.signature[:24])); bool isGlobalValidation = uint8(userOp.signature[24]) == 1; _checkIfValidationAppliesCallData(userOp.callData, userOpValidationFunction, isGlobalValidation); @@ -402,7 +402,7 @@ contract UpgradeableModularAccount is // To support gas estimation, we don't fail early when the failure is caused by a signature failure function _doUserOpValidation( - PluginEntity userOpValidationFunction, + ModuleEntity userOpValidationFunction, PackedUserOperation memory userOp, bytes calldata signature, bytes32 userOpHash @@ -414,7 +414,7 @@ contract UpgradeableModularAccount is uint256 validationRes; // Do preUserOpValidation hooks - PluginEntity[] memory preUserOpValidationHooks = + ModuleEntity[] memory preUserOpValidationHooks = getAccountStorage().validationData[userOpValidationFunction].preValidationHooks; for (uint256 i = 0; i < preUserOpValidationHooks.length; ++i) { @@ -438,13 +438,13 @@ contract UpgradeableModularAccount is userOp.signature = ""; } - (address plugin, uint32 entityId) = preUserOpValidationHooks[i].unpack(); + (address module, uint32 entityId) = preUserOpValidationHooks[i].unpack(); uint256 currentValidationRes = - IValidationHook(plugin).preUserOpValidationHook(entityId, userOp, userOpHash); + IValidationHook(module).preUserOpValidationHook(entityId, userOp, userOpHash); if (uint160(currentValidationRes) > 1) { // If the aggregator is not 0 or 1, it is an unexpected value - revert UnexpectedAggregator(plugin, entityId, address(uint160(currentValidationRes))); + revert UnexpectedAggregator(module, entityId, address(uint160(currentValidationRes))); } validationRes = _coalescePreValidation(validationRes, currentValidationRes); } @@ -457,8 +457,8 @@ contract UpgradeableModularAccount is userOp.signature = signatureSegment.getBody(); - (address plugin, uint32 entityId) = userOpValidationFunction.unpack(); - uint256 currentValidationRes = IValidation(plugin).validateUserOp(entityId, userOp, userOpHash); + (address module, uint32 entityId) = userOpValidationFunction.unpack(); + uint256 currentValidationRes = IValidation(module).validateUserOp(entityId, userOp, userOpHash); if (preUserOpValidationHooks.length != 0) { // If we have other validation data we need to coalesce with @@ -472,7 +472,7 @@ contract UpgradeableModularAccount is } function _doRuntimeValidation( - PluginEntity runtimeValidationFunction, + ModuleEntity runtimeValidationFunction, bytes calldata callData, bytes calldata authorizationData ) internal { @@ -481,7 +481,7 @@ contract UpgradeableModularAccount is (authSegment, authorizationData) = authorizationData.getNextSegment(); // run all preRuntimeValidation hooks - PluginEntity[] memory preRuntimeValidationHooks = + ModuleEntity[] memory preRuntimeValidationHooks = getAccountStorage().validationData[runtimeValidationFunction].preValidationHooks; for (uint256 i = 0; i < preRuntimeValidationHooks.length; ++i) { @@ -511,16 +511,16 @@ contract UpgradeableModularAccount is revert ValidationSignatureSegmentMissing(); } - (address plugin, uint32 entityId) = runtimeValidationFunction.unpack(); + (address module, uint32 entityId) = runtimeValidationFunction.unpack(); - try IValidation(plugin).validateRuntime( + try IValidation(module).validateRuntime( address(this), entityId, msg.sender, msg.value, callData, authSegment.getBody() ) // forgefmt: disable-start // solhint-disable-next-line no-empty-blocks {} catch (bytes memory revertReason) { // forgefmt: disable-end - revert RuntimeValidationFunctionReverted(plugin, entityId, revertReason); + revert RuntimeValidationFunctionReverted(module, entityId, revertReason); } } @@ -536,7 +536,7 @@ contract UpgradeableModularAccount is // be sure that the set of hooks to run will not be affected by state changes mid-execution. for (uint256 i = 0; i < hooksLength; ++i) { bytes32 key = executionHooks.at(i); - (PluginEntity hookFunction,, bool isPostHook) = toExecutionHook(key); + (ModuleEntity hookFunction,, bool isPostHook) = toExecutionHook(key); if (isPostHook) { postHooksToRun[i].postExecHook = hookFunction; } @@ -546,7 +546,7 @@ contract UpgradeableModularAccount is // exists. for (uint256 i = 0; i < hooksLength; ++i) { bytes32 key = executionHooks.at(i); - (PluginEntity hookFunction, bool isPreHook, bool isPostHook) = toExecutionHook(key); + (ModuleEntity hookFunction, bool isPreHook, bool isPostHook) = toExecutionHook(key); if (isPreHook) { bytes memory preExecHookReturnData; @@ -561,18 +561,18 @@ contract UpgradeableModularAccount is } } - function _runPreExecHook(PluginEntity preExecHook, bytes memory data) + function _runPreExecHook(ModuleEntity preExecHook, bytes memory data) internal returns (bytes memory preExecHookReturnData) { - (address plugin, uint32 entityId) = preExecHook.unpack(); - try IExecutionHook(plugin).preExecutionHook(entityId, msg.sender, msg.value, data) returns ( + (address module, uint32 entityId) = preExecHook.unpack(); + try IExecutionHook(module).preExecutionHook(entityId, msg.sender, msg.value, data) returns ( bytes memory returnData ) { preExecHookReturnData = returnData; } catch (bytes memory revertReason) { - // TODO: same issue with EP0.6 - we can't do bytes4 error codes in plugins - revert PreExecHookReverted(plugin, entityId, revertReason); + // TODO: same issue with EP0.6 - we can't do bytes4 error codes in modules + revert PreExecHookReverted(module, entityId, revertReason); } } @@ -590,29 +590,29 @@ contract UpgradeableModularAccount is continue; } - (address plugin, uint32 entityId) = postHookToRun.postExecHook.unpack(); + (address module, uint32 entityId) = postHookToRun.postExecHook.unpack(); // solhint-disable-next-line no-empty-blocks - try IExecutionHook(plugin).postExecutionHook(entityId, postHookToRun.preExecHookReturnData) {} + try IExecutionHook(module).postExecutionHook(entityId, postHookToRun.preExecHookReturnData) {} catch (bytes memory revertReason) { - revert PostExecHookReverted(plugin, entityId, revertReason); + revert PostExecHookReverted(module, entityId, revertReason); } } } function _doPreRuntimeValidationHook( - PluginEntity validationHook, + ModuleEntity validationHook, bytes memory callData, bytes memory currentAuthData ) internal { - (address hookPlugin, uint32 hookEntityId) = validationHook.unpack(); - try IValidationHook(hookPlugin).preRuntimeValidationHook( + (address hookModule, uint32 hookEntityId) = validationHook.unpack(); + try IValidationHook(hookModule).preRuntimeValidationHook( hookEntityId, msg.sender, msg.value, callData, currentAuthData ) // forgefmt: disable-start // solhint-disable-next-line no-empty-blocks {} catch (bytes memory revertReason) { // forgefmt: disable-end - revert PreRuntimeValidationHookFailed(hookPlugin, hookEntityId, revertReason); + revert PreRuntimeValidationHookFailed(hookModule, hookEntityId, revertReason); } } @@ -645,14 +645,14 @@ contract UpgradeableModularAccount is return (new PostExecToRun[](0), new PostExecToRun[](0)); } - PluginEntity directCallValidationKey = PluginEntityLib.pack(msg.sender, _SELF_PERMIT_VALIDATION_FUNCTIONID); + ModuleEntity directCallValidationKey = ModuleEntityLib.pack(msg.sender, _SELF_PERMIT_VALIDATION_FUNCTIONID); _checkIfValidationAppliesCallData(msg.data, directCallValidationKey, false); // Direct call is allowed, run associated permission & validation hooks // Validation hooks - PluginEntity[] memory preRuntimeValidationHooks = + ModuleEntity[] memory preRuntimeValidationHooks = _storage.validationData[directCallValidationKey].preValidationHooks; uint256 hookLen = preRuntimeValidationHooks.length; @@ -673,7 +673,7 @@ contract UpgradeableModularAccount is function _checkIfValidationAppliesCallData( bytes calldata callData, - PluginEntity validationFunction, + ModuleEntity validationFunction, bool isGlobal ) internal view { bytes4 outerSelector = bytes4(callData[:4]); @@ -712,7 +712,7 @@ contract UpgradeableModularAccount is // To prevent arbitrarily-deep recursive checking, we limit the depth of self-calls to one // for the purposes of batching. // This means that all self-calls must occur at the top level of the batch. - // Note that plugins of other contracts using `executeWithAuthorization` may still + // Note that modules of other contracts using `executeWithAuthorization` may still // independently call into this account with a different validation function, allowing // composition of multiple batches. revert SelfCallRecursionDepthExceeded(); @@ -727,7 +727,7 @@ contract UpgradeableModularAccount is function _globalValidationAllowed(bytes4 selector) internal view returns (bool) { if ( selector == this.execute.selector || selector == this.executeBatch.selector - || selector == this.installPlugin.selector || selector == this.uninstallPlugin.selector + || selector == this.installModule.selector || selector == this.uninstallModule.selector || selector == this.installValidation.selector || selector == this.uninstallValidation.selector || selector == this.upgradeToAndCall.selector ) { @@ -737,7 +737,7 @@ contract UpgradeableModularAccount is return getAccountStorage().selectorData[selector].allowGlobalValidation; } - function _checkIfValidationAppliesSelector(bytes4 selector, PluginEntity validationFunction, bool isGlobal) + function _checkIfValidationAppliesSelector(bytes4 selector, ModuleEntity validationFunction, bool isGlobal) internal view { diff --git a/src/helpers/KnownSelectors.sol b/src/helpers/KnownSelectors.sol index ae91f3b7..811f2f5d 100644 --- a/src/helpers/KnownSelectors.sol +++ b/src/helpers/KnownSelectors.sol @@ -9,8 +9,8 @@ import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import {IAccountLoupe} from "../interfaces/IAccountLoupe.sol"; import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; -import {IPlugin} from "../interfaces/IPlugin.sol"; -import {IPluginManager} from "../interfaces/IPluginManager.sol"; +import {IModule} from "../interfaces/IModule.sol"; +import {IModuleManager} from "../interfaces/IModuleManager.sol"; import {IStandardExecutor} from "../interfaces/IStandardExecutor.sol"; import {IValidation} from "../interfaces/IValidation.sol"; import {IValidationHook} from "../interfaces/IValidationHook.sol"; @@ -22,8 +22,8 @@ library KnownSelectors { return // check against IAccount methods selector == IAccount.validateUserOp.selector - // check against IPluginManager methods - || selector == IPluginManager.installPlugin.selector || selector == IPluginManager.uninstallPlugin.selector + // check against IModuleManager methods + || selector == IModuleManager.installModule.selector || selector == IModuleManager.uninstallModule.selector // check against IERC165 methods || selector == IERC165.supportsInterface.selector // check against UUPSUpgradeable methods @@ -36,7 +36,7 @@ library KnownSelectors { || selector == IAccountLoupe.getExecutionFunctionHandler.selector || selector == IAccountLoupe.getSelectors.selector || selector == IAccountLoupe.getExecutionHooks.selector || selector == IAccountLoupe.getPreValidationHooks.selector - || selector == IAccountLoupe.getInstalledPlugins.selector; + || selector == IAccountLoupe.getInstalledModules.selector; } function isErc4337Function(bytes4 selector) internal pure returns (bool) { @@ -46,9 +46,9 @@ library KnownSelectors { || selector == IPaymaster.validatePaymasterUserOp.selector || selector == IPaymaster.postOp.selector; } - function isIPluginFunction(bytes4 selector) internal pure returns (bool) { - return selector == IPlugin.onInstall.selector || selector == IPlugin.onUninstall.selector - || selector == IPlugin.pluginManifest.selector || selector == IPlugin.pluginMetadata.selector + function isIModuleFunction(bytes4 selector) internal pure returns (bool) { + return selector == IModule.onInstall.selector || selector == IModule.onUninstall.selector + || selector == IModule.moduleManifest.selector || selector == IModule.moduleMetadata.selector || selector == IExecutionHook.preExecutionHook.selector || selector == IExecutionHook.postExecutionHook.selector || selector == IValidation.validateUserOp.selector || selector == IValidation.validateRuntime.selector || selector == IValidation.validateSignature.selector diff --git a/src/helpers/ModuleEntityLib.sol b/src/helpers/ModuleEntityLib.sol new file mode 100644 index 00000000..d8473a5b --- /dev/null +++ b/src/helpers/ModuleEntityLib.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.25; + +import {ModuleEntity} from "../interfaces/IModuleManager.sol"; + +library ModuleEntityLib { + // Magic value for hooks that should always revert. + ModuleEntity internal constant _PRE_HOOK_ALWAYS_DENY = ModuleEntity.wrap(bytes24(uint192(2))); + + function pack(address addr, uint32 entityId) internal pure returns (ModuleEntity) { + return ModuleEntity.wrap(bytes24(bytes20(addr)) | bytes24(uint192(entityId))); + } + + function unpack(ModuleEntity fr) internal pure returns (address addr, uint32 entityId) { + bytes24 underlying = ModuleEntity.unwrap(fr); + addr = address(bytes20(underlying)); + entityId = uint32(bytes4(underlying << 160)); + } + + function isEmpty(ModuleEntity fr) internal pure returns (bool) { + return ModuleEntity.unwrap(fr) == bytes24(0); + } + + function notEmpty(ModuleEntity fr) internal pure returns (bool) { + return ModuleEntity.unwrap(fr) != 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); + } +} diff --git a/src/helpers/PluginEntityLib.sol b/src/helpers/PluginEntityLib.sol deleted file mode 100644 index 423b7f70..00000000 --- a/src/helpers/PluginEntityLib.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.25; - -import {PluginEntity} from "../interfaces/IPluginManager.sol"; - -library PluginEntityLib { - // Magic value for hooks that should always revert. - PluginEntity internal constant _PRE_HOOK_ALWAYS_DENY = PluginEntity.wrap(bytes24(uint192(2))); - - function pack(address addr, uint32 entityId) internal pure returns (PluginEntity) { - return PluginEntity.wrap(bytes24(bytes20(addr)) | bytes24(uint192(entityId))); - } - - function unpack(PluginEntity fr) internal pure returns (address addr, uint32 entityId) { - bytes24 underlying = PluginEntity.unwrap(fr); - addr = address(bytes20(underlying)); - entityId = uint32(bytes4(underlying << 160)); - } - - function isEmpty(PluginEntity fr) internal pure returns (bool) { - return PluginEntity.unwrap(fr) == bytes24(0); - } - - function notEmpty(PluginEntity fr) internal pure returns (bool) { - return PluginEntity.unwrap(fr) != bytes24(0); - } - - function eq(PluginEntity a, PluginEntity b) internal pure returns (bool) { - return PluginEntity.unwrap(a) == PluginEntity.unwrap(b); - } - - function notEq(PluginEntity a, PluginEntity b) internal pure returns (bool) { - return PluginEntity.unwrap(a) != PluginEntity.unwrap(b); - } -} diff --git a/src/helpers/ValidationConfigLib.sol b/src/helpers/ValidationConfigLib.sol index 6d27b907..1c127c3b 100644 --- a/src/helpers/ValidationConfigLib.sol +++ b/src/helpers/ValidationConfigLib.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.25; -import {PluginEntity, ValidationConfig} from "../interfaces/IPluginManager.sol"; +import {ModuleEntity, ValidationConfig} from "../interfaces/IModuleManager.sol"; // Validation config is a packed representation of a validation function and flags for its configuration. // Layout: @@ -12,14 +12,14 @@ import {PluginEntity, ValidationConfig} from "../interfaces/IPluginManager.sol"; // 0x____________________________________________________000000000000 // unused library ValidationConfigLib { - function pack(PluginEntity _validationFunction, bool _isGlobal, bool _isSignatureValidation) + function pack(ModuleEntity _validationFunction, bool _isGlobal, bool _isSignatureValidation) internal pure returns (ValidationConfig) { return ValidationConfig.wrap( bytes26( - bytes26(PluginEntity.unwrap(_validationFunction)) + bytes26(ModuleEntity.unwrap(_validationFunction)) // isGlobal flag stored in the 25th byte | bytes26(bytes32(_isGlobal ? uint256(1) << 56 : 0)) // isSignatureValidation flag stored in the 26th byte @@ -28,15 +28,15 @@ library ValidationConfigLib { ); } - function pack(address _plugin, uint32 _entityId, bool _isGlobal, bool _isSignatureValidation) + function pack(address _module, uint32 _entityId, bool _isGlobal, bool _isSignatureValidation) internal pure returns (ValidationConfig) { return ValidationConfig.wrap( bytes26( - // plugin address stored in the first 20 bytes - bytes26(bytes20(_plugin)) + // module address stored in the first 20 bytes + bytes26(bytes20(_module)) // entityId stored in the 21st - 24th byte | bytes26(bytes24(uint192(_entityId))) // isGlobal flag stored in the 25th byte @@ -50,10 +50,10 @@ library ValidationConfigLib { function unpackUnderlying(ValidationConfig config) internal pure - returns (address _plugin, uint32 _entityId, bool _isGlobal, bool _isSignatureValidation) + returns (address _module, uint32 _entityId, bool _isGlobal, bool _isSignatureValidation) { bytes26 configBytes = ValidationConfig.unwrap(config); - _plugin = address(bytes20(configBytes)); + _module = address(bytes20(configBytes)); _entityId = uint32(bytes4(configBytes << 160)); _isGlobal = uint8(configBytes[24]) == 1; _isSignatureValidation = uint8(configBytes[25]) == 1; @@ -62,15 +62,15 @@ library ValidationConfigLib { function unpack(ValidationConfig config) internal pure - returns (PluginEntity _validationFunction, bool _isGlobal, bool _isSignatureValidation) + returns (ModuleEntity _validationFunction, bool _isGlobal, bool _isSignatureValidation) { bytes26 configBytes = ValidationConfig.unwrap(config); - _validationFunction = PluginEntity.wrap(bytes24(configBytes)); + _validationFunction = ModuleEntity.wrap(bytes24(configBytes)); _isGlobal = uint8(configBytes[24]) == 1; _isSignatureValidation = uint8(configBytes[25]) == 1; } - function plugin(ValidationConfig config) internal pure returns (address) { + function module(ValidationConfig config) internal pure returns (address) { return address(bytes20(ValidationConfig.unwrap(config))); } @@ -78,8 +78,8 @@ library ValidationConfigLib { return uint32(bytes4(ValidationConfig.unwrap(config) << 160)); } - function pluginEntity(ValidationConfig config) internal pure returns (PluginEntity) { - return PluginEntity.wrap(bytes24(ValidationConfig.unwrap(config))); + function moduleEntity(ValidationConfig config) internal pure returns (ModuleEntity) { + return ModuleEntity.wrap(bytes24(ValidationConfig.unwrap(config))); } function isGlobal(ValidationConfig config) internal pure returns (bool) { diff --git a/src/interfaces/IAccountLoupe.sol b/src/interfaces/IAccountLoupe.sol index 3e7d9f11..01cca512 100644 --- a/src/interfaces/IAccountLoupe.sol +++ b/src/interfaces/IAccountLoupe.sol @@ -1,27 +1,27 @@ // SPDX-License-Identifier: CC0-1.0 pragma solidity ^0.8.25; -import {PluginEntity} from "../interfaces/IPluginManager.sol"; +import {ModuleEntity} from "../interfaces/IModuleManager.sol"; /// @notice Pre and post hooks for a given selector. /// @dev It's possible for one of either `preExecHook` or `postExecHook` to be empty. struct ExecutionHook { - PluginEntity hookFunction; + ModuleEntity hookFunction; bool isPreHook; bool isPostHook; } interface IAccountLoupe { - /// @notice Get the plugin address for a selector. - /// @dev If the selector is a native function, the plugin address will be the address of the account. + /// @notice Get the module address 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 configuration for. - /// @return plugin The plugin address for this selector. - function getExecutionFunctionHandler(bytes4 selector) external view returns (address plugin); + /// @return module The module address for this selector. + function getExecutionFunctionHandler(bytes4 selector) external view returns (address module); /// @notice Get the selectors for a validation function. /// @param validationFunction The validation function to get the selectors for. /// @return The allowed selectors for this validation function. - function getSelectors(PluginEntity validationFunction) external view returns (bytes4[] memory); + function getSelectors(ModuleEntity validationFunction) external view returns (bytes4[] memory); /// @notice Get the pre and post execution hooks for a selector. /// @param selector The selector to get the hooks for. @@ -31,17 +31,17 @@ interface IAccountLoupe { /// @notice Get the pre and post execution hooks for a validation function. /// @param validationFunction The validation function to get the hooks for. /// @return The pre and post execution hooks for this validation function. - function getPermissionHooks(PluginEntity validationFunction) external view returns (ExecutionHook[] memory); + function getPermissionHooks(ModuleEntity validationFunction) external view returns (ExecutionHook[] memory); /// @notice Get the pre user op and runtime validation hooks associated with a selector. /// @param validationFunction The validation function to get the hooks for. /// @return preValidationHooks The pre validation hooks for this selector. - function getPreValidationHooks(PluginEntity validationFunction) + function getPreValidationHooks(ModuleEntity validationFunction) external view - returns (PluginEntity[] memory preValidationHooks); + returns (ModuleEntity[] memory preValidationHooks); - /// @notice Get an array of all installed plugins. - /// @return The addresses of all installed plugins. - function getInstalledPlugins() external view returns (address[] memory); + /// @notice Get an array of all installed modules. + /// @return The addresses of all installed modules. + function getInstalledModules() external view returns (address[] memory); } diff --git a/src/interfaces/IExecutionHook.sol b/src/interfaces/IExecutionHook.sol index 9cb16482..ad9e52b6 100644 --- a/src/interfaces/IExecutionHook.sol +++ b/src/interfaces/IExecutionHook.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: CC0-1.0 pragma solidity ^0.8.25; -import {IPlugin} from "./IPlugin.sol"; +import {IModule} from "./IModule.sol"; -interface IExecutionHook is IPlugin { +interface IExecutionHook 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 diff --git a/src/interfaces/IPlugin.sol b/src/interfaces/IModule.sol similarity index 66% rename from src/interfaces/IPlugin.sol rename to src/interfaces/IModule.sol index 824a1ddf..8851acaf 100644 --- a/src/interfaces/IPlugin.sol +++ b/src/interfaces/IModule.sol @@ -34,53 +34,53 @@ struct SelectorPermission { string permissionDescription; } -/// @dev A struct holding fields to describe the plugin in a purely view context. Intended for front end clients. -struct PluginMetadata { - // A human-readable name of the plugin. +/// @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 plugin, following the semantic versioning scheme. + // 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 plugin. + // that created this module. string author; // String desciptions of the relative sensitivity of specific functions. The selectors MUST be selectors for - // functions implemented by this plugin. + // functions implemented by this module. SelectorPermission[] permissionDescriptors; - // A list of all ERC-7715 permission strings that the plugin could possibly use + // A list of all ERC-7715 permission strings that the module could possibly use string[] permissionRequest; } -/// @dev A struct describing how the plugin should be installed on a modular account. -struct PluginManifest { - // Execution functions defined in this plugin to be installed on the MSCA. +/// @dev A struct describing how the module should be installed on a modular account. +struct ModuleManifest { + // Execution functions defined in this module to be installed on the MSCA. ManifestExecutionFunction[] executionFunctions; ManifestValidation[] validationFunctions; ManifestExecutionHook[] executionHooks; // List of ERC-165 interface IDs to add to account to support introspection checks. This MUST NOT include - // IPlugin's interface ID. + // IModule's interface ID. bytes4[] interfaceIds; } -interface IPlugin is IERC165 { - /// @notice Initialize plugin data for the modular account. - /// @dev Called by the modular account during `installPlugin`. - /// @param data Optional bytes array to be decoded and used by the plugin to setup initial plugin data for the +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 plugin data for the modular account. - /// @dev Called by the modular account during `uninstallPlugin`. - /// @param data Optional bytes array to be decoded and used by the plugin to clear plugin data for the modular + /// @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 Describe the contents and intended configuration of the plugin. + /// @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 plugin. - function pluginManifest() external pure returns (PluginManifest memory); + /// @return A manifest describing the contents and intended configuration of the module. + function moduleManifest() external pure returns (ModuleManifest memory); - /// @notice Describe the metadata of the plugin. + /// @notice Describe the metadata of the module. /// @dev This metadata MUST stay constant over time. - /// @return A metadata struct describing the plugin. - function pluginMetadata() external pure returns (PluginMetadata memory); + /// @return A metadata struct describing the module. + function moduleMetadata() external pure returns (ModuleMetadata memory); } diff --git a/src/interfaces/IPluginManager.sol b/src/interfaces/IModuleManager.sol similarity index 63% rename from src/interfaces/IPluginManager.sol rename to src/interfaces/IModuleManager.sol index d2df72b5..eb2db1ea 100644 --- a/src/interfaces/IPluginManager.sol +++ b/src/interfaces/IModuleManager.sol @@ -1,21 +1,21 @@ // SPDX-License-Identifier: CC0-1.0 pragma solidity ^0.8.25; -type PluginEntity is bytes24; +type ModuleEntity is bytes24; type ValidationConfig is bytes26; -interface IPluginManager { - event PluginInstalled(address indexed plugin, bytes32 manifestHash); +interface IModuleManager { + event ModuleInstalled(address indexed module, bytes32 manifestHash); - event PluginUninstalled(address indexed plugin, bool indexed onUninstallSucceeded); + event ModuleUninstalled(address indexed module, bool indexed onUninstallSucceeded); - /// @notice Install a plugin to the modular account. - /// @param plugin The plugin to install. - /// @param manifestHash The hash of the plugin manifest. - /// @param pluginInstallData Optional data to be decoded and used by the plugin to setup initial plugin data + /// @notice Install a module to the modular account. + /// @param module The module to install. + /// @param manifestHash The hash of the module manifest. + /// @param moduleInstallData Optional data to be decoded and used by the module to setup initial module data /// for the modular account. - function installPlugin(address plugin, bytes32 manifestHash, bytes calldata pluginInstallData) external; + function installModule(address module, bytes32 manifestHash, bytes calldata moduleInstallData) external; /// @notice Temporary install function - pending a different user-supplied install config & manifest validation /// path. @@ -25,7 +25,7 @@ interface IPluginManager { /// @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 decoded and used by the plugin to setup initial plugin state. + /// @param installData Optional data to be decoded and used by the module to setup initial module state. /// @param preValidationHooks Optional pre-validation hooks to install for the validation function. /// @param permissionHooks Optional permission hooks to install for the validation function. function installValidation( @@ -39,23 +39,23 @@ interface IPluginManager { /// @notice Uninstall a validation function from a set of execution selectors. /// TODO: remove or update. /// @param validationFunction The validation function to uninstall. - /// @param uninstallData Optional data to be decoded and used by the plugin to clear plugin data for the + /// @param uninstallData Optional data to be decoded and used by the module to clear module data for the /// account. - /// @param preValidationHookUninstallData Optional data to be decoded and used by the plugin to clear account + /// @param preValidationHookUninstallData Optional data to be decoded and used by the module to clear account /// data - /// @param permissionHookUninstallData Optional data to be decoded and used by the plugin to clear account data + /// @param permissionHookUninstallData Optional data to be decoded and used by the module to clear account data function uninstallValidation( - PluginEntity validationFunction, + ModuleEntity validationFunction, bytes calldata uninstallData, bytes calldata preValidationHookUninstallData, bytes calldata permissionHookUninstallData ) external; - /// @notice Uninstall a plugin from the modular account. - /// @param plugin The plugin to uninstall. + /// @notice Uninstall a module from the modular account. + /// @param module The module to uninstall. /// @param config An optional, implementation-specific field that accounts may use to ensure consistency /// guarantees. - /// @param pluginUninstallData Optional data to be decoded and used by the plugin to clear plugin data for the + /// @param moduleUninstallData Optional data to be decoded and used by the module to clear module data for the /// modular account. - function uninstallPlugin(address plugin, bytes calldata config, bytes calldata pluginUninstallData) external; + function uninstallModule(address module, bytes calldata config, bytes calldata moduleUninstallData) external; } diff --git a/src/interfaces/IStandardExecutor.sol b/src/interfaces/IStandardExecutor.sol index fbeb89c4..db9a7c19 100644 --- a/src/interfaces/IStandardExecutor.sol +++ b/src/interfaces/IStandardExecutor.sol @@ -12,7 +12,6 @@ struct Call { interface IStandardExecutor { /// @notice Standard execute method. - /// @dev If the target is a plugin, the call SHOULD revert. /// @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. @@ -20,7 +19,7 @@ interface IStandardExecutor { function execute(address target, uint256 value, bytes calldata data) external payable returns (bytes memory); /// @notice Standard executeBatch method. - /// @dev If the target is a plugin, the call SHOULD revert. If any of the calls revert, the entire batch MUST + /// @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. diff --git a/src/interfaces/IValidation.sol b/src/interfaces/IValidation.sol index 471cd2c1..4f8fbbb8 100644 --- a/src/interfaces/IValidation.sol +++ b/src/interfaces/IValidation.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.25; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; -import {IPlugin} from "./IPlugin.sol"; +import {IModule} from "./IModule.sol"; -interface IValidation is IPlugin { +interface IValidation 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. diff --git a/src/interfaces/IValidationHook.sol b/src/interfaces/IValidationHook.sol index dd7e2500..1a8ee589 100644 --- a/src/interfaces/IValidationHook.sol +++ b/src/interfaces/IValidationHook.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.25; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; -import {IPlugin} from "./IPlugin.sol"; +import {IModule} from "./IModule.sol"; -interface IValidationHook is IPlugin { +interface IValidationHook 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 diff --git a/src/plugins/BasePlugin.sol b/src/modules/BaseModule.sol similarity index 83% rename from src/plugins/BasePlugin.sol rename to src/modules/BaseModule.sol index 40dcd47b..b6d785c5 100644 --- a/src/plugins/BasePlugin.sol +++ b/src/modules/BaseModule.sol @@ -5,13 +5,13 @@ import {IAccountExecute} from "@eth-infinitism/account-abstraction/interfaces/IA import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {ERC165, IERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; -import {IPlugin} from "../interfaces/IPlugin.sol"; +import {IModule} from "../interfaces/IModule.sol"; -/// @title Base contract for plugins -/// @dev Implements ERC-165 to support IPlugin's interface, which is a requirement -/// for plugin installation. This also ensures that plugin interactions cannot +/// @title Base contract for modules +/// @dev Implements ERC-165 to support IModule's interface, which is a requirement +/// for module installation. This also ensures that module interactions cannot /// happen via the standard execution funtions `execute` and `executeBatch`. -abstract contract BasePlugin is ERC165, IPlugin { +abstract contract BaseModule is ERC165, IModule { error NotImplemented(); /// @dev Returns true if this contract implements the interface defined by @@ -21,13 +21,13 @@ abstract contract BasePlugin is ERC165, IPlugin { /// /// This function call must use less than 30 000 gas. /// - /// Supporting the IPlugin interface is a requirement for plugin installation. This is also used + /// Supporting the IModule interface is a requirement for module installation. This is also used /// by the modular account to prevent standard execution functions `execute` and `executeBatch` from - /// making calls to plugins. + /// making calls to modules. /// @param interfaceId The interface ID to check for support. /// @return True if the contract supports `interfaceId`. function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { - return interfaceId == type(IPlugin).interfaceId || super.supportsInterface(interfaceId); + return interfaceId == type(IModule).interfaceId || super.supportsInterface(interfaceId); } function _getSelectorAndCalldata(bytes calldata data) internal pure returns (bytes4, bytes memory) { diff --git a/src/plugins/ERC20TokenLimitPlugin.sol b/src/modules/ERC20TokenLimitModule.sol similarity index 85% rename from src/plugins/ERC20TokenLimitPlugin.sol rename to src/modules/ERC20TokenLimitModule.sol index 28ba4e04..85d90585 100644 --- a/src/plugins/ERC20TokenLimitPlugin.sol +++ b/src/modules/ERC20TokenLimitModule.sol @@ -13,20 +13,20 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; -import {PluginManifest, PluginMetadata} from "../interfaces/IPlugin.sol"; -import {IPlugin} from "../interfaces/IPlugin.sol"; +import {ModuleManifest, ModuleMetadata} from "../interfaces/IModule.sol"; +import {IModule} from "../interfaces/IModule.sol"; import {Call, IStandardExecutor} from "../interfaces/IStandardExecutor.sol"; -import {BasePlugin, IERC165} from "./BasePlugin.sol"; +import {BaseModule, IERC165} from "./BaseModule.sol"; -/// @title ERC20 Token Limit Plugin +/// @title ERC20 Token Limit Module /// @author ERC-6900 Authors -/// @notice This plugin supports an ERC20 token spend limit. This should be combined with a contract whitelist -/// plugin to make sure that token transfers not tracked by the plugin don't happen. -/// Note: this plugin is opinionated on what selectors can be called for token contracts to guard against weird +/// @notice This module supports an ERC20 token spend limit. This should be combined with a contract whitelist +/// module to make sure that token transfers not tracked by the module don't happen. +/// Note: this module is opinionated on what selectors can be called for token contracts to guard against weird /// edge cases like DAI. You wouldn't be able to use uni v2 pairs directly as the pair contract is also the LP /// token contract -contract ERC20TokenLimitPlugin is BasePlugin, IExecutionHook { +contract ERC20TokenLimitModule is BaseModule, IExecutionHook { using UserOperationLib for PackedUserOperation; using EnumerableSet for EnumerableSet.AddressSet; using AssociatedLinkedListSetLib for AssociatedLinkedListSet; @@ -36,7 +36,7 @@ contract ERC20TokenLimitPlugin is BasePlugin, IExecutionHook { uint256[] limits; } - string internal constant _NAME = "ERC20 Token Limit Plugin"; + string internal constant _NAME = "ERC20 Token Limit Module"; string internal constant _VERSION = "1.0.0"; string internal constant _AUTHOR = "ERC-6900 Authors"; @@ -77,7 +77,7 @@ contract ERC20TokenLimitPlugin is BasePlugin, IExecutionHook { return ""; } - /// @inheritdoc IPlugin + /// @inheritdoc IModule function onInstall(bytes calldata data) external override { (uint32 startEntityId, ERC20SpendLimit[] memory spendLimits) = abi.decode(data, (uint32, ERC20SpendLimit[])); @@ -94,7 +94,7 @@ contract ERC20TokenLimitPlugin is BasePlugin, IExecutionHook { } } - /// @inheritdoc IPlugin + /// @inheritdoc IModule function onUninstall(bytes calldata data) external override { (address token, uint32 entityId) = abi.decode(data, (address, uint32)); delete limits[entityId][token][msg.sender]; @@ -114,13 +114,13 @@ contract ERC20TokenLimitPlugin is BasePlugin, IExecutionHook { revert NotImplemented(); } - /// @inheritdoc IPlugin + /// @inheritdoc IModule // solhint-disable-next-line no-empty-blocks - function pluginManifest() external pure override returns (PluginManifest memory) {} + function moduleManifest() external pure override returns (ModuleManifest memory) {} - /// @inheritdoc IPlugin - function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { - PluginMetadata memory metadata; + /// @inheritdoc IModule + function moduleMetadata() external pure virtual override returns (ModuleMetadata memory) { + ModuleMetadata memory metadata; metadata.name = _NAME; metadata.version = _VERSION; metadata.author = _AUTHOR; @@ -130,8 +130,8 @@ contract ERC20TokenLimitPlugin is BasePlugin, IExecutionHook { return metadata; } - /// @inheritdoc BasePlugin - function supportsInterface(bytes4 interfaceId) public view override(BasePlugin, IERC165) returns (bool) { + /// @inheritdoc BaseModule + function supportsInterface(bytes4 interfaceId) public view override(BaseModule, IERC165) returns (bool) { return super.supportsInterface(interfaceId); } diff --git a/src/plugins/NativeTokenLimitPlugin.sol b/src/modules/NativeTokenLimitModule.sol similarity index 89% rename from src/plugins/NativeTokenLimitPlugin.sol rename to src/modules/NativeTokenLimitModule.sol index 37f4399e..f09994be 100644 --- a/src/plugins/NativeTokenLimitPlugin.sol +++ b/src/modules/NativeTokenLimitModule.sol @@ -6,20 +6,20 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; -import {PluginManifest, PluginMetadata} from "../interfaces/IPlugin.sol"; -import {IPlugin} from "../interfaces/IPlugin.sol"; +import {ModuleManifest, ModuleMetadata} from "../interfaces/IModule.sol"; +import {IModule} from "../interfaces/IModule.sol"; import {Call, IStandardExecutor} from "../interfaces/IStandardExecutor.sol"; import {IValidationHook} from "../interfaces/IValidationHook.sol"; -import {BasePlugin, IERC165} from "./BasePlugin.sol"; +import {BaseModule, IERC165} from "./BaseModule.sol"; -/// @title Native Token Limit Plugin +/// @title Native Token Limit Module /// @author ERC-6900 Authors -/// @notice This plugin supports a single total native token spend limit. +/// @notice This module supports a single total native token spend limit. /// It tracks a total spend limit across UserOperation gas limits and native token transfers. /// If a non whitelisted paymaster is used, UO gas would not cause the limit to decrease. /// If a whitelisted paymaster is used, gas is still counted towards the limit -contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { +contract NativeTokenLimitModule is BaseModule, IExecutionHook, IValidationHook { using UserOperationLib for PackedUserOperation; using EnumerableSet for EnumerableSet.Bytes32Set; @@ -82,7 +82,7 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { return _checkAndDecrementLimit(entityId, data); } - /// @inheritdoc IPlugin + /// @inheritdoc IModule function onInstall(bytes calldata data) external override { (uint32 startEntityId, uint256[] memory spendLimits) = abi.decode(data, (uint32, uint256[])); @@ -95,7 +95,7 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { } } - /// @inheritdoc IPlugin + /// @inheritdoc IModule function onUninstall(bytes calldata data) external override { // This is the highest entityId that's being used by the account uint32 entityId = abi.decode(data, (uint32)); @@ -117,13 +117,13 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { override {} // solhint-disable-line no-empty-blocks - /// @inheritdoc IPlugin + /// @inheritdoc IModule // solhint-disable-next-line no-empty-blocks - function pluginManifest() external pure override returns (PluginManifest memory) {} + function moduleManifest() external pure override returns (ModuleManifest memory) {} - /// @inheritdoc IPlugin - function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { - PluginMetadata memory metadata; + /// @inheritdoc IModule + function moduleMetadata() external pure virtual override returns (ModuleMetadata memory) { + ModuleMetadata memory metadata; metadata.name = _NAME; metadata.version = _VERSION; metadata.author = _AUTHOR; @@ -138,8 +138,8 @@ contract NativeTokenLimitPlugin is BasePlugin, IExecutionHook, IValidationHook { // ┃ EIP-165 ┃ // ┗━━━━━━━━━━━━━━━┛ - /// @inheritdoc BasePlugin - function supportsInterface(bytes4 interfaceId) public view override(BasePlugin, IERC165) returns (bool) { + /// @inheritdoc BaseModule + function supportsInterface(bytes4 interfaceId) public view override(BaseModule, IERC165) returns (bool) { return interfaceId == type(IExecutionHook).interfaceId || super.supportsInterface(interfaceId); } diff --git a/src/plugins/TokenReceiverPlugin.sol b/src/modules/TokenReceiverModule.sol similarity index 78% rename from src/plugins/TokenReceiverPlugin.sol rename to src/modules/TokenReceiverModule.sol index 95fbcfe0..b4cf839b 100644 --- a/src/plugins/TokenReceiverPlugin.sol +++ b/src/modules/TokenReceiverModule.sol @@ -4,15 +4,15 @@ pragma solidity ^0.8.25; import {IERC1155Receiver} from "@openzeppelin/contracts/interfaces/IERC1155Receiver.sol"; import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; -import {IPlugin, ManifestExecutionFunction, PluginManifest, PluginMetadata} from "../interfaces/IPlugin.sol"; -import {BasePlugin} from "./BasePlugin.sol"; +import {IModule, ManifestExecutionFunction, ModuleManifest, ModuleMetadata} from "../interfaces/IModule.sol"; +import {BaseModule} from "./BaseModule.sol"; -/// @title Token Receiver Plugin +/// @title Token Receiver Module /// @author ERC-6900 Authors -/// @notice This plugin allows modular accounts to receive various types of tokens by implementing +/// @notice This module allows modular accounts to receive various types of tokens by implementing /// required token receiver interfaces. -contract TokenReceiverPlugin is BasePlugin, IERC721Receiver, IERC1155Receiver { - string internal constant _NAME = "Token Receiver Plugin"; +contract TokenReceiverModule is BaseModule, IERC721Receiver, IERC1155Receiver { + string internal constant _NAME = "Token Receiver Module"; string internal constant _VERSION = "1.0.0"; string internal constant _AUTHOR = "ERC-6900 Authors"; @@ -43,20 +43,20 @@ contract TokenReceiverPlugin is BasePlugin, IERC721Receiver, IERC1155Receiver { } // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Plugin interface functions ┃ + // ┃ Module interface functions ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - /// @inheritdoc IPlugin + /// @inheritdoc IModule // solhint-disable-next-line no-empty-blocks function onInstall(bytes calldata) external pure override {} - /// @inheritdoc IPlugin + /// @inheritdoc IModule // solhint-disable-next-line no-empty-blocks function onUninstall(bytes calldata) external pure override {} - /// @inheritdoc IPlugin - function pluginManifest() external pure override returns (PluginManifest memory) { - PluginManifest memory manifest; + /// @inheritdoc IModule + function moduleManifest() external pure override returns (ModuleManifest memory) { + ModuleManifest memory manifest; manifest.executionFunctions = new ManifestExecutionFunction[](3); manifest.executionFunctions[0] = ManifestExecutionFunction({ @@ -82,9 +82,9 @@ contract TokenReceiverPlugin is BasePlugin, IERC721Receiver, IERC1155Receiver { return manifest; } - /// @inheritdoc IPlugin - function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { - PluginMetadata memory metadata; + /// @inheritdoc IModule + function moduleMetadata() external pure virtual override returns (ModuleMetadata memory) { + ModuleMetadata memory metadata; metadata.name = _NAME; metadata.version = _VERSION; metadata.author = _AUTHOR; diff --git a/src/plugins/validation/ISingleSignerValidation.sol b/src/modules/validation/ISingleSignerValidation.sol similarity index 100% rename from src/plugins/validation/ISingleSignerValidation.sol rename to src/modules/validation/ISingleSignerValidation.sol diff --git a/src/plugins/validation/SingleSignerValidation.sol b/src/modules/validation/SingleSignerValidation.sol similarity index 91% rename from src/plugins/validation/SingleSignerValidation.sol rename to src/modules/validation/SingleSignerValidation.sol index 6699cb58..a384cda7 100644 --- a/src/plugins/validation/SingleSignerValidation.sol +++ b/src/modules/validation/SingleSignerValidation.sol @@ -6,9 +6,9 @@ import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; -import {IPlugin, PluginManifest, PluginMetadata} from "../../interfaces/IPlugin.sol"; +import {IModule, ModuleManifest, ModuleMetadata} from "../../interfaces/IModule.sol"; import {IValidation} from "../../interfaces/IValidation.sol"; -import {BasePlugin} from "../BasePlugin.sol"; +import {BaseModule} from "../BaseModule.sol"; import {ISingleSignerValidation} from "./ISingleSignerValidation.sol"; /// @title ECSDA Validation @@ -24,7 +24,7 @@ import {ISingleSignerValidation} from "./ISingleSignerValidation.sol"; /// /// - This validation supports composition that other validation can relay on entities in this validation /// to validate partially or fully. -contract SingleSignerValidation is ISingleSignerValidation, BasePlugin { +contract SingleSignerValidation is ISingleSignerValidation, BaseModule { using ECDSA for bytes32; using MessageHashUtils for bytes32; @@ -46,13 +46,13 @@ contract SingleSignerValidation is ISingleSignerValidation, BasePlugin { _transferSigner(entityId, newSigner); } - /// @inheritdoc IPlugin + /// @inheritdoc IModule function onInstall(bytes calldata data) external override { (uint32 entityId, address newSigner) = abi.decode(data, (uint32, address)); _transferSigner(entityId, newSigner); } - /// @inheritdoc IPlugin + /// @inheritdoc IModule function onUninstall(bytes calldata data) external override { // ToDo: what does it mean in the world of composable validation world to uninstall one type of validation // We can either get rid of all SingleSigner signers. What about the nested ones? @@ -115,18 +115,18 @@ contract SingleSignerValidation is ISingleSignerValidation, BasePlugin { } // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Plugin interface functions ┃ + // ┃ Module interface functions ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - /// @inheritdoc IPlugin - function pluginManifest() external pure override returns (PluginManifest memory) { - PluginManifest memory manifest; + /// @inheritdoc IModule + function moduleManifest() external pure override returns (ModuleManifest memory) { + ModuleManifest memory manifest; return manifest; } - /// @inheritdoc IPlugin - function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { - PluginMetadata memory metadata; + /// @inheritdoc IModule + function moduleMetadata() external pure virtual override returns (ModuleMetadata memory) { + ModuleMetadata memory metadata; metadata.name = _NAME; metadata.version = _VERSION; metadata.author = _AUTHOR; diff --git a/src/samples/permissionhooks/AllowlistPlugin.sol b/src/samples/permissionhooks/AllowlistModule.sol similarity index 91% rename from src/samples/permissionhooks/AllowlistPlugin.sol rename to src/samples/permissionhooks/AllowlistModule.sol index bb468ae8..b90fd6c2 100644 --- a/src/samples/permissionhooks/AllowlistPlugin.sol +++ b/src/samples/permissionhooks/AllowlistModule.sol @@ -3,13 +3,13 @@ pragma solidity ^0.8.25; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; -import {PluginManifest, PluginMetadata} from "../../interfaces/IPlugin.sol"; +import {ModuleManifest, ModuleMetadata} from "../../interfaces/IModule.sol"; import {Call, IStandardExecutor} from "../../interfaces/IStandardExecutor.sol"; import {IValidationHook} from "../../interfaces/IValidationHook.sol"; -import {BasePlugin} from "../../plugins/BasePlugin.sol"; +import {BaseModule} from "../../modules/BaseModule.sol"; -contract AllowlistPlugin is IValidationHook, BasePlugin { +contract AllowlistModule is IValidationHook, BaseModule { enum EntityId { PRE_VALIDATION_HOOK } @@ -95,9 +95,9 @@ contract AllowlistPlugin is IValidationHook, BasePlugin { revert NotImplemented(); } - function pluginMetadata() external pure override returns (PluginMetadata memory) { - PluginMetadata memory metadata; - metadata.name = "Allowlist Plugin"; + function moduleMetadata() external pure override returns (ModuleMetadata memory) { + ModuleMetadata memory metadata; + metadata.name = "Allowlist Module"; metadata.version = "v0.0.1"; metadata.author = "ERC-6900 Working Group"; @@ -105,7 +105,7 @@ contract AllowlistPlugin is IValidationHook, BasePlugin { } // solhint-disable-next-line no-empty-blocks - function pluginManifest() external pure override returns (PluginManifest memory) {} + function moduleManifest() external pure override returns (ModuleManifest memory) {} function _checkAllowlistCalldata(bytes calldata callData) internal view { if (bytes4(callData[:4]) == IStandardExecutor.execute.selector) { diff --git a/standard/ERCs/ERC6900Diagrams.excalidraw b/standard/ERCs/ERC6900Diagrams.excalidraw index aeef2f91..0b37d0b4 100644 --- a/standard/ERCs/ERC6900Diagrams.excalidraw +++ b/standard/ERCs/ERC6900Diagrams.excalidraw @@ -734,11 +734,11 @@ "locked": false, "fontSize": 25.664343526865803, "fontFamily": 2, - "text": "Native Function / (Plugin) Execution Function", + "text": "Native Function / (Module) Execution Function", "textAlign": "center", "verticalAlign": "middle", "containerId": "FSqrJM9FRySWN10YDIFuH", - "originalText": "Native Function / (Plugin) Execution Function", + "originalText": "Native Function / (Module) Execution Function", "lineHeight": 1.15, "baseline": 24 }, @@ -1274,11 +1274,11 @@ "locked": false, "fontSize": 20, "fontFamily": 3, - "text": "\n executeFromPlugin", + "text": "\n executeFromModule", "textAlign": "left", "verticalAlign": "top", "containerId": "V64RYeLbqd8REancwGi30", - "originalText": "\n executeFromPlugin", + "originalText": "\n executeFromModule", "lineHeight": 1.2, "baseline": 44 }, @@ -1513,11 +1513,11 @@ "locked": false, "fontSize": 25.664343526865803, "fontFamily": 2, - "text": "Permitted Plugin Execution Function", + "text": "Permitted Module Execution Function", "textAlign": "center", "verticalAlign": "middle", "containerId": "3HJCra6w4bVr0zNxhwPIg", - "originalText": "Permitted Plugin Execution Function", + "originalText": "Permitted Module Execution Function", "lineHeight": 1.15, "baseline": 24 }, @@ -1681,11 +1681,11 @@ "locked": false, "fontSize": 25.664343526865803, "fontFamily": 2, - "text": "Plugins", + "text": "Modules", "textAlign": "center", "verticalAlign": "middle", "containerId": "owBW-NHsmLmREuOjDCv06", - "originalText": "Plugins", + "originalText": "Modules", "lineHeight": 1.15, "baseline": 24 }, @@ -1720,11 +1720,11 @@ "locked": false, "fontSize": 25.664343526865803, "fontFamily": 2, - "text": "Plugin Permission Check", + "text": "Module Permission Check", "textAlign": "center", "verticalAlign": "middle", "containerId": "wBFdRyQG4wrtwbKkyNsa-", - "originalText": "Plugin Permission Check", + "originalText": "Module Permission Check", "lineHeight": 1.15, "baseline": 24 }, @@ -1796,11 +1796,11 @@ "locked": false, "fontSize": 20, "fontFamily": 3, - "text": "\n executeFromPluginExternal", + "text": "\n executeFromModuleExternal", "textAlign": "left", "verticalAlign": "top", "containerId": "dgC4KV1DfhkMlJgqA9-10", - "originalText": "\n executeFromPluginExternal", + "originalText": "\n executeFromModuleExternal", "lineHeight": 1.2, "baseline": 44 }, @@ -1876,11 +1876,11 @@ "locked": false, "fontSize": 25.664343526865803, "fontFamily": 2, - "text": "(Plugin) Execution Function", + "text": "(Module) Execution Function", "textAlign": "center", "verticalAlign": "middle", "containerId": "j7kk61t1I6enSnhuaVZHW", - "originalText": "(Plugin) Execution Function", + "originalText": "(Module) Execution Function", "lineHeight": 1.15, "baseline": 24 }, @@ -1977,11 +1977,11 @@ "locked": false, "fontSize": 20, "fontFamily": 2, - "text": "1 calls plugin ", + "text": "1 calls module ", "textAlign": "center", "verticalAlign": "middle", "containerId": "71sd7fDDpQnB-DSlvQqP_", - "originalText": "1 calls plugin ", + "originalText": "1 calls module ", "lineHeight": 1.15, "baseline": 19 }, @@ -2133,11 +2133,11 @@ "locked": false, "fontSize": 20, "fontFamily": 2, - "text": "2.2 calls external contracts \nthrough\n executeFromPluginExternal", + "text": "2.2 calls external contracts \nthrough\n executeFromModuleExternal", "textAlign": "center", "verticalAlign": "middle", "containerId": "W_TcvUOGvsHsXmj31qq0O", - "originalText": "2.2 calls external contracts through\n executeFromPluginExternal", + "originalText": "2.2 calls external contracts through\n executeFromModuleExternal", "lineHeight": 1.15, "baseline": 65 }, @@ -2259,11 +2259,11 @@ "locked": false, "fontSize": 25.664343526865803, "fontFamily": 2, - "text": "Plugin Permission Check", + "text": "Module Permission Check", "textAlign": "center", "verticalAlign": "middle", "containerId": "K34YWjpoqir9H5g1FHSe1", - "originalText": "Plugin Permission Check", + "originalText": "Module Permission Check", "lineHeight": 1.15, "baseline": 24 }, @@ -2517,11 +2517,11 @@ "locked": false, "fontSize": 36, "fontFamily": 2, - "text": "Plugin Execution Flow", + "text": "Module Execution Flow", "textAlign": "left", "verticalAlign": "top", "containerId": null, - "originalText": "Plugin Execution Flow", + "originalText": "Module Execution Flow", "lineHeight": 1.15, "baseline": 33 }, @@ -2555,11 +2555,11 @@ "locked": false, "fontSize": 20, "fontFamily": 2, - "text": "2.1 calls other installed \nplugin through\n executeFromPlugin", + "text": "2.1 calls other installed \nmodule through\n executeFromModule", "textAlign": "center", "verticalAlign": "middle", "containerId": "fA1p68GcpvI-X1krxkJ29", - "originalText": "2.1 calls other installed plugin through\n executeFromPlugin", + "originalText": "2.1 calls other installed module through\n executeFromModule", "lineHeight": 1.15, "baseline": 65 }, @@ -2643,11 +2643,11 @@ "locked": false, "fontSize": 25.68325562404122, "fontFamily": 2, - "text": "Pre executeFromPluginExternal Hook(s)", + "text": "Pre executeFromModuleExternal Hook(s)", "textAlign": "center", "verticalAlign": "middle", "containerId": "emDobd4D91UDt1c8oZ0Iu", - "originalText": "Pre executeFromPluginExternal Hook(s)", + "originalText": "Pre executeFromModuleExternal Hook(s)", "lineHeight": 1.15, "baseline": 24 }, @@ -2727,11 +2727,11 @@ "locked": false, "fontSize": 25.664343526865803, "fontFamily": 2, - "text": "Post executeFromPluginExternal Hook(s)", + "text": "Post executeFromModuleExternal Hook(s)", "textAlign": "center", "verticalAlign": "middle", "containerId": "Hx9p9aaBWogvW3Z9zujgf", - "originalText": "Post executeFromPluginExternal Hook(s)", + "originalText": "Post executeFromModuleExternal Hook(s)", "lineHeight": 1.15, "baseline": 24 }, @@ -3325,11 +3325,11 @@ "locked": false, "fontSize": 20, "fontFamily": 3, - "text": "\n executeFromPlugin/executeFromPluginExternal", + "text": "\n executeFromModule/executeFromModuleExternal", "textAlign": "left", "verticalAlign": "top", "containerId": "2JLeKLisTh-Vc4SuDVk5b", - "originalText": "\n executeFromPlugin/executeFromPluginExternal", + "originalText": "\n executeFromModule/executeFromModuleExternal", "lineHeight": 1.2, "baseline": 44 }, @@ -3715,11 +3715,11 @@ "locked": false, "fontSize": 25.664343526865803, "fontFamily": 2, - "text": "Pre Plugin Execution Hook(s)", + "text": "Pre Module Execution Hook(s)", "textAlign": "center", "verticalAlign": "middle", "containerId": "uJ0egPTKleB3Aut1HFd2K", - "originalText": "Pre Plugin Execution Hook(s)", + "originalText": "Pre Module Execution Hook(s)", "lineHeight": 1.15, "baseline": 24 }, @@ -3793,11 +3793,11 @@ "locked": false, "fontSize": 25.664343526865803, "fontFamily": 2, - "text": "Post Plugin Execution Hook(s)", + "text": "Post Module Execution Hook(s)", "textAlign": "center", "verticalAlign": "middle", "containerId": "7Klv-GWoFcKLlBdaExqzT", - "originalText": "Post Plugin Execution Hook(s)", + "originalText": "Post Module Execution Hook(s)", "lineHeight": 1.15, "baseline": 24 }, @@ -4183,11 +4183,11 @@ "locked": false, "fontSize": 25.664343526865803, "fontFamily": 2, - "text": "Plugins", + "text": "Modules", "textAlign": "center", "verticalAlign": "middle", "containerId": "qLT39tSOFDyxvMCmG5W1h", - "originalText": "Plugins", + "originalText": "Modules", "lineHeight": 1.15, "baseline": 24 }, @@ -4781,11 +4781,11 @@ "locked": false, "fontSize": 20, "fontFamily": 3, - "text": "\n executeFromPlugin", + "text": "\n executeFromModule", "textAlign": "left", "verticalAlign": "top", "containerId": "h4-dGWuBPeBT6vEznfNUa", - "originalText": "\n executeFromPlugin", + "originalText": "\n executeFromModule", "lineHeight": 1.2, "baseline": 44 }, @@ -5171,11 +5171,11 @@ "locked": false, "fontSize": 25.664343526865803, "fontFamily": 2, - "text": "Pre Plugin Execution Hook(s)", + "text": "Pre Module Execution Hook(s)", "textAlign": "center", "verticalAlign": "middle", "containerId": "2tiy_HH9LYxsFnORyo1QN", - "originalText": "Pre Plugin Execution Hook(s)", + "originalText": "Pre Module Execution Hook(s)", "lineHeight": 1.15, "baseline": 24 }, @@ -5249,11 +5249,11 @@ "locked": false, "fontSize": 25.664343526865803, "fontFamily": 2, - "text": "Post Plugin Execution Hook(s)", + "text": "Post Module Execution Hook(s)", "textAlign": "center", "verticalAlign": "middle", "containerId": "T7fYn-6m0AnnYUsZ7Vu-6", - "originalText": "Post Plugin Execution Hook(s)", + "originalText": "Post Module Execution Hook(s)", "lineHeight": 1.15, "baseline": 24 }, @@ -5323,11 +5323,11 @@ "locked": false, "fontSize": 25.664343526865803, "fontFamily": 2, - "text": "Account-Native Function / Plugin Execution Function", + "text": "Account-Native Function / Module Execution Function", "textAlign": "center", "verticalAlign": "middle", "containerId": "Sj8y2LMf6pefmYIQqny_R", - "originalText": "Account-Native Function / Plugin Execution Function", + "originalText": "Account-Native Function / Module Execution Function", "lineHeight": 1.15, "baseline": 24 }, @@ -5639,11 +5639,11 @@ "locked": false, "fontSize": 25.664343526865803, "fontFamily": 2, - "text": "Plugins", + "text": "Modules", "textAlign": "center", "verticalAlign": "middle", "containerId": "UIOBlFmO5FdF3hqajkL-P", - "originalText": "Plugins", + "originalText": "Modules", "lineHeight": 1.15, "baseline": 24 }, @@ -6517,11 +6517,11 @@ "locked": false, "fontSize": 20, "fontFamily": 3, - "text": "\n executeFromPlugin", + "text": "\n executeFromModule", "textAlign": "left", "verticalAlign": "top", "containerId": "ejh1h99xkb4w1wuTImtiK", - "originalText": "\n executeFromPlugin", + "originalText": "\n executeFromModule", "lineHeight": 1.2, "baseline": 44 }, @@ -7207,11 +7207,11 @@ "locked": false, "fontSize": 25.664343526865803, "fontFamily": 2, - "text": "Plugins", + "text": "Modules", "textAlign": "center", "verticalAlign": "middle", "containerId": "9PEiLoAhsOByWsJZ9OKS9", - "originalText": "Plugins", + "originalText": "Modules", "lineHeight": 1.15, "baseline": 24 }, @@ -7339,11 +7339,11 @@ "locked": false, "fontSize": 25.664343526865803, "fontFamily": 2, - "text": "Plugin Permission Check", + "text": "Module Permission Check", "textAlign": "center", "verticalAlign": "middle", "containerId": "S3jaS7lY7njUd4iFC2CvV", - "originalText": "Plugin Permission Check", + "originalText": "Module Permission Check", "lineHeight": 1.15, "baseline": 24 } diff --git a/standard/ERCs/erc-6900.md b/standard/ERCs/erc-6900.md index fed6a625..d5d15744 100644 --- a/standard/ERCs/erc-6900.md +++ b/standard/ERCs/erc-6900.md @@ -1,6 +1,6 @@ --- eip: 6900 -title: Modular Smart Contract Accounts and Plugins +title: Modular Smart Contract Accounts and Modules description: Interfaces for composable contract accounts optionally supporting upgradability and introspection author: Adam Egyed (@adamegyed), Fangting Liu (@trinity-0111), Jay Paik (@jaypaik), Yoav Weiss (@yoavw) discussions-to: https://ethereum-magicians.org/t/eip-modular-smart-contract-accounts-and-plugins/13885 @@ -13,7 +13,7 @@ requires: 165, 4337 ## Abstract -This proposal standardizes smart contract accounts and account plugins, which are smart contract interfaces that allow for composable logic within smart contract accounts. This proposal is compliant with [ERC-4337](./eip-4337.md), and takes inspiration from [ERC-2535](./eip-2535.md) when defining interfaces for updating and querying modular function implementations. +This proposal standardizes smart contract accounts and account modules, which are smart contract interfaces that allow for composable logic within smart contract accounts. This proposal is compliant with [ERC-4337](./eip-4337.md), and takes inspiration from [ERC-2535](./eip-2535.md) when defining interfaces for updating and querying modular function implementations. This modular approach splits account functionality into three categories, implements them in external contracts, and defines an expected execution flow from accounts. @@ -21,24 +21,24 @@ This modular approach splits account functionality into three categories, implem One of the goals that ERC-4337 accomplishes is abstracting the logic for execution and validation to each smart contract account. -Many new features of accounts can be built by customizing the logic that goes into the validation and execution steps. Examples of such features include session keys, subscriptions, spending limits, and role-based access control. Currently, some of these features are implemented natively by specific smart contract accounts, and others are able to be implemented by plugin systems. Examples of proprietary plugin systems include Safe modules and ZeroDev plugins. +Many new features of accounts can be built by customizing the logic that goes into the validation and execution steps. Examples of such features include session keys, subscriptions, spending limits, and role-based access control. Currently, some of these features are implemented natively by specific smart contract accounts, and others are able to be implemented by module systems. Examples of proprietary module systems include Safe modules and ZeroDev modules. -However, managing multiple account instances provides a worse user experience, fragmenting accounts across supported features and security configurations. Additionally, it requires plugin developers to choose which platforms to support, causing either platform lock-in or duplicated development effort. +However, managing multiple account instances provides a worse user experience, fragmenting accounts across supported features and security configurations. Additionally, it requires module developers to choose which platforms to support, causing either platform lock-in or duplicated development effort. -We propose a standard that coordinates the implementation work between plugin developers and wallet developers. This standard defines a modular smart contract account capable of supporting all standard-conformant plugins. This allows users to have greater portability of their data, and for plugin developers to not have to choose specific account implementations to support. +We propose a standard that coordinates the implementation work between module developers and wallet developers. This standard defines a modular smart contract account capable of supporting all standard-conformant modules. This allows users to have greater portability of their data, and for module developers to not have to choose specific account implementations to support. -![diagram showing relationship between accounts and plugins with modular functions](../assets/eip-6900/MSCA_Shared_Components_Diagram.svg) +![diagram showing relationship between accounts and modules with modular functions](../assets/eip-6900/MSCA_Shared_Components_Diagram.svg) We take inspiration from ERC-2535's diamond pattern for routing execution based on function selectors, and create a similarly composable account. However, the standard does not require the multi-facet proxy pattern. -These plugins can contain execution logic, validation schemes, and hooks. Validation schemes define the circumstances under which the smart contract account will approve actions taken on its behalf, while hooks allow for pre- and post-execution controls. +These modules can contain execution logic, validation schemes, and hooks. Validation schemes define the circumstances under which the smart contract account will approve actions taken on its behalf, while hooks allow for pre- and post-execution controls. -Accounts adopting this standard will support modular, upgradable execution and validation logic. Defining this as a standard for smart contract accounts will make plugins easier to develop securely and will allow for greater interoperability. +Accounts adopting this standard will support modular, upgradable execution and validation logic. Defining this as a standard for smart contract accounts will make modules easier to develop securely and will allow for greater interoperability. Goals: - Provide standards for how validation, execution, and hook functions for smart contract accounts should be written. -- Provide standards for how compliant accounts should add, update, remove, and inspect plugins. +- Provide standards for how compliant accounts should add, update, remove, and inspect modules. ## Specification @@ -55,16 +55,16 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S - **User Operation Validation** functions handle calls to `validateUserOp` and check the validity of an ERC-4337 user operation. - **Runtime Validation** functions run before an execution function when not called via a user operation, and enforce checks. Common checks include allowing execution only by an owner. - An **execution function** is a smart contract function that defines the main execution step of a function for a modular account. -- The **standard execute** functions are two specific execute functions that are implemented natively by the modular account, and not on a plugin. These allow for open-ended execution. +- The **standard execute** functions are two specific execute functions that are implemented natively by the modular account, and not on a module. These allow for open-ended execution. - A **hook** is a smart contract function executed before or after another function, with the ability to modify state or cause the entire call to revert. There are four types of hooks: - **Pre User Operation Validation Hook** functions run before user operation validation functions. These can enforce permissions on what actions a validation function may perform via user operations. - **Pre Runtime Validation Hook** functions run before runtime validation functions. These can enforce permissions on what actions a validation function may perform via direct calls. - **Pre Execution Hook** functions run before an execution function. They may optionally return data to be consumed by their related post execution hook functions. - **Post Execution Hook** functions run after an execution function. They may optionally take returned data from their related pre execution hook functions. - An **associated function** refers to either a validation function or a hook. -- A **native function** refers to a function implemented natively by the modular account, as opposed to a function added by a plugin. -- A **plugin** is a deployed smart contract that hosts any amount of the above three kinds of modular functions: execution functions, validation functions, or hooks. -- A plugin **manifest** is responsible for describing the execution functions, validation functions, and hooks that will be configured on the MSCA during installation, as well as the plugin’s metadata, dependency requirements, and permissions. +- A **native function** refers to a function implemented natively by the modular account, as opposed to a function added by a module. +- A **module** is a deployed smart contract that hosts any amount of the above three kinds of modular functions: execution functions, validation functions, or hooks. +- A module **manifest** is responsible for describing the execution functions, validation functions, and hooks that will be configured on the MSCA during installation, as well as the module’s metadata, dependency requirements, and permissions. ### Overview @@ -74,12 +74,12 @@ A call to the smart contract account can be broken down into the steps as shown ![diagram showing call flow within an modular account](../assets/eip-6900/Modular_Account_Call_Flow.svg) -The following diagram shows permitted plugin execution flows. During a plugin's execution step from the above diagram, the plugin may perform a "Plugin Execution Function", using either `executeFromPlugin` or `executeFromPluginExternal`. These can be used by plugins to execute using the account's context. +The following diagram shows permitted module execution flows. During a module's execution step from the above diagram, the module may perform a "Module Execution Function", using either `executeFromModule` or `executeFromModuleExternal`. These can be used by modules to execute using the account's context. -- `executeFromPlugin` handles calls to other installed plugin's execution function on the modular account. -- `executeFromPluginExternal` handles calls to external addresses. +- `executeFromModule` handles calls to other installed module's execution function on the modular account. +- `executeFromModuleExternal` handles calls to external addresses. -![diagram showing a plugin execution flow](../assets/eip-6900/Plugin_Execution_Flow.svg) +![diagram showing a module execution flow](../assets/eip-6900/Module_Execution_Flow.svg) Each step is modular, supporting different implementations for each execution function, and composable, supporting multiple steps through hooks. Combined, these allow for open-ended programmable accounts. @@ -88,52 +88,52 @@ Each step is modular, supporting different implementations for each execution fu **Modular Smart Contract Accounts** **MUST** implement - `IAccount.sol` from [ERC-4337](./eip-4337.md). -- `IPluginManager.sol` to support installing and uninstalling plugins. -- `IStandardExecutor.sol` to support open-ended execution. **Calls to plugins through this SHOULD revert.** -- `IPluginExecutor.sol` to support execution from plugins. **Calls to plugins through `executeFromPluginExternal` SHOULD revert.** +- `IModuleManager.sol` to support installing and uninstalling modules. +- `IStandardExecutor.sol` to support open-ended execution. **Calls to modules through this SHOULD revert.** +- `IModuleExecutor.sol` to support execution from modules. **Calls to modules through `executeFromModuleExternal` SHOULD revert.** **Modular Smart Contract Accounts** **MAY** implement -- `IAccountLoupe.sol` to support visibility in plugin configuration on-chain. +- `IAccountLoupe.sol` to support visibility in module configuration on-chain. -**Plugins** **MUST** implement +**Modules** **MUST** implement -- `IPlugin.sol` described below and implement [ERC-165](./eip-165.md) for `IPlugin`. +- `IModule.sol` described below and implement [ERC-165](./eip-165.md) for `IModule`. -#### `IPluginManager.sol` +#### `IModuleManager.sol` -Plugin manager interface. Modular Smart Contract Accounts **MUST** implement this interface to support installing and uninstalling plugins. +Module manager interface. Modular Smart Contract Accounts **MUST** implement this interface to support installing and uninstalling modules. ```solidity // Treats the first 20 bytes as an address, and the last byte as a function identifier. -type PluginEntity is bytes21; +type ModuleEntity is bytes21; -interface IPluginManager { - event PluginInstalled(address indexed plugin, bytes32 manifestHash, PluginEntity[] dependencies); +interface IModuleManager { + event ModuleInstalled(address indexed module, bytes32 manifestHash, ModuleEntity[] dependencies); - event PluginUninstalled(address indexed plugin, bool indexed onUninstallSucceeded); + event ModuleUninstalled(address indexed module, bool indexed onUninstallSucceeded); - /// @notice Install a plugin to the modular account. - /// @param plugin The plugin to install. - /// @param manifestHash The hash of the plugin manifest. - /// @param pluginInstallData Optional data to be decoded and used by the plugin to setup initial plugin data + /// @notice Install a module to the modular account. + /// @param module The module to install. + /// @param manifestHash The hash of the module manifest. + /// @param moduleInstallData Optional data to be decoded and used by the module to setup initial module data /// for the modular account. - /// @param dependencies The dependencies of the plugin, as described in the manifest. Each PluginEntity - /// MUST be composed of an installed plugin's address and a function ID of its validation function. - function installPlugin( - address plugin, + /// @param dependencies The dependencies of the module, as described in the manifest. Each ModuleEntity + /// MUST be composed of an installed module's address and a function ID of its validation function. + function installModule( + address module, bytes32 manifestHash, - bytes calldata pluginInstallData, - PluginEntity[] calldata dependencies + bytes calldata moduleInstallData, + ModuleEntity[] calldata dependencies ) external; - /// @notice Uninstall a plugin from the modular account. - /// @param plugin The plugin to uninstall. + /// @notice Uninstall a module from the modular account. + /// @param module The module to uninstall. /// @param config An optional, implementation-specific field that accounts may use to ensure consistency /// guarantees. - /// @param pluginUninstallData Optional data to be decoded and used by the plugin to clear plugin data for the + /// @param moduleUninstallData Optional data to be decoded and used by the module to clear module data for the /// modular account. - function uninstallPlugin(address plugin, bytes calldata config, bytes calldata pluginUninstallData) external; + function uninstallModule(address module, bytes calldata config, bytes calldata moduleUninstallData) external; } ``` @@ -142,9 +142,9 @@ interface IPluginManager { Standard execute interface. Modular Smart Contract Accounts **MUST** implement this interface to support open-ended execution. -Standard execute functions SHOULD check whether the call's target implements the `IPlugin` interface via ERC-165. +Standard execute functions SHOULD check whether the call's target implements the `IModule` interface via ERC-165. -**If the target is a plugin, the call SHOULD revert.** This prevents accidental misconfiguration or misuse of plugins (both installed and uninstalled). +**If the target is a module, the call SHOULD revert.** This prevents accidental misconfiguration or misuse of modules (both installed and uninstalled). ```solidity struct Call { @@ -158,7 +158,7 @@ struct Call { interface IStandardExecutor { /// @notice Standard execute method. - /// @dev If the target is a plugin, the call SHOULD revert. + /// @dev If the target is a module, the call SHOULD revert. /// @param target The target address for account to call. /// @param value The value to send with the call. /// @param data The calldata for the call. @@ -166,7 +166,7 @@ interface IStandardExecutor { function execute(address target, uint256 value, bytes calldata data) external payable returns (bytes memory); /// @notice Standard executeBatch method. - /// @dev If the target is a plugin, the call SHOULD revert. If any of the calls revert, the entire batch MUST + /// @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. @@ -174,32 +174,32 @@ interface IStandardExecutor { } ``` -#### `IPluginExecutor.sol` +#### `IModuleExecutor.sol` -Execution interface for calls made from plugins. Modular Smart Contract Accounts **MUST** implement this interface to support execution from plugins. +Execution interface for calls made from modules. Modular Smart Contract Accounts **MUST** implement this interface to support execution from modules. -The `executeFromPluginExternal` function SHOULD check whether the call's target implements the `IPlugin` interface via ERC-165. +The `executeFromModuleExternal` function SHOULD check whether the call's target implements the `IModule` interface via ERC-165. -**If the target of `executeFromPluginExternal` function is a plugin, the call SHOULD revert.** +**If the target of `executeFromModuleExternal` function is a module, the call SHOULD revert.** -This prevents accidental misconfiguration or misuse of plugins (both installed and uninstalled). Installed plugins MAY interact with other installed plugins via the `executeFromPlugin` function. +This prevents accidental misconfiguration or misuse of modules (both installed and uninstalled). Installed modules MAY interact with other installed modules via the `executeFromModule` function. ```solidity -interface IPluginExecutor { - /// @notice Execute a call from a plugin through the account. - /// @dev Permissions must be granted to the calling plugin for the call to go through. +interface IModuleExecutor { + /// @notice Execute a call from a module through the account. + /// @dev Permissions must be granted to the calling module for the call to go through. /// @param data The calldata to send to the account. /// @return The return data from the call. - function executeFromPlugin(bytes calldata data) external payable returns (bytes memory); + function executeFromModule(bytes calldata data) external payable returns (bytes memory); - /// @notice Execute a call from a plugin to a non-plugin address. - /// @dev If the target is a plugin, the call SHOULD revert. Permissions must be granted to the calling plugin + /// @notice Execute a call from a module to a non-module address. + /// @dev If the target is a module, the call SHOULD revert. Permissions must be granted to the calling module /// for the call to go through. /// @param target The address to be called. /// @param value The value to send with the call. /// @param data The calldata to send to the target. /// @return The return data from the call. - function executeFromPluginExternal(address target, uint256 value, bytes calldata data) + function executeFromModuleExternal(address target, uint256 value, bytes calldata data) external payable returns (bytes memory); @@ -208,26 +208,26 @@ interface IPluginExecutor { #### `IAccountLoupe.sol` -Plugin inspection interface. Modular Smart Contract Accounts **MAY** implement this interface to support visibility in plugin configuration on-chain. +Module inspection interface. Modular Smart Contract Accounts **MAY** implement this interface to support visibility in module configuration on-chain. ```solidity interface IAccountLoupe { /// @notice Config for an execution function, given a selector. struct ExecutionFunctionConfig { - address plugin; - PluginEntity validationFunction; + address module; + ModuleEntity validationFunction; } /// @notice Pre and post hooks for a given selector. /// @dev It's possible for one of either `preExecHook` or `postExecHook` to be empty. struct ExecutionHooks { - PluginEntity hookFunction; + ModuleEntity hookFunction; bool isPreHook; bool isPostHook; } - /// @notice Get the validation functions and plugin address for a selector. - /// @dev If the selector is a native function, the plugin address will be the address of the account. + /// @notice Get the validation functions and module address 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 configuration for. /// @return The configuration for this selector. function getExecutionFunctionConfig(bytes4 selector) external view returns (ExecutionFunctionConfig memory); @@ -243,28 +243,28 @@ interface IAccountLoupe { function getPreValidationHooks(bytes4 selector) external view - returns (PluginEntity[] memory preValidationHooks); + returns (ModuleEntity[] memory preValidationHooks); - /// @notice Get an array of all installed plugins. - /// @return The addresses of all installed plugins. - function getInstalledPlugins() external view returns (address[] memory); + /// @notice Get an array of all installed modules. + /// @return The addresses of all installed modules. + function getInstalledModules() external view returns (address[] memory); } ``` -#### `IPlugin.sol` +#### `IModule.sol` -Plugin interface. Plugins **MUST** implement this interface to support plugin management and interactions with MSCAs. +Module interface. Modules **MUST** implement this interface to support module management and interactions with MSCAs. ```solidity -interface IPlugin { - /// @notice Initialize plugin data for the modular account. - /// @dev Called by the modular account during `installPlugin`. - /// @param data Optional bytes array to be decoded and used by the plugin to setup initial plugin data for the modular account. +interface IModule { + /// @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 plugin data for the modular account. - /// @dev Called by the modular account during `uninstallPlugin`. - /// @param data Optional bytes array to be decoded and used by the plugin to clear plugin data for the modular account. + /// @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 Run the pre user operation validation hook specified by the `entityId`. @@ -318,29 +318,29 @@ interface IPlugin { /// @param preExecHookData The context returned by its associated pre execution hook. function postExecutionHook(uint32 entityId, bytes calldata preExecHookData) external; - /// @notice Describe the contents and intended configuration of the plugin. + /// @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 plugin. - function pluginManifest() external pure returns (PluginManifest memory); + /// @return A manifest describing the contents and intended configuration of the module. + function moduleManifest() external pure returns (ModuleManifest memory); - /// @notice Describe the metadata of the plugin. + /// @notice Describe the metadata of the module. /// @dev This metadata MUST stay constant over time. - /// @return A metadata struct describing the plugin. - function pluginMetadata() external pure returns (PluginMetadata memory); + /// @return A metadata struct describing the module. + function moduleMetadata() external pure returns (ModuleMetadata memory); } ``` -### Plugin manifest +### Module manifest -The plugin manifest is responsible for describing the execution functions, validation functions, and hooks that will be configured on the MSCA during installation, as well as the plugin's metadata, dependencies, and permissions. +The module manifest is responsible for describing the execution functions, validation functions, and hooks that will be configured on the MSCA during installation, as well as the module's metadata, dependencies, and permissions. ```solidity enum ManifestAssociatedFunctionType { // Function is not defined. NONE, - // Function belongs to this plugin. + // Function belongs to this module. SELF, - // Function belongs to an external plugin provided as a dependency during plugin installation. Plugins MAY depend + // Function belongs to an external module provided as a dependency during module installation. Modules MAY depend // on external validation functions. It MUST NOT depend on external hooks, or installation will fail. DEPENDENCY, // Resolves to a magic value to always bypass runtime validation for a given function. @@ -355,8 +355,8 @@ enum ManifestAssociatedFunctionType { PRE_HOOK_ALWAYS_DENY } -/// @dev For functions of type `ManifestAssociatedFunctionType.DEPENDENCY`, the MSCA MUST find the plugin address -/// of the function at `dependencies[dependencyIndex]` during the call to `installPlugin(config)`. +/// @dev For functions of type `ManifestAssociatedFunctionType.DEPENDENCY`, the MSCA MUST find the module address +/// of the function at `dependencies[dependencyIndex]` during the call to `installModule(config)`. struct ManifestFunction { ManifestAssociatedFunctionType functionType; uint8 entityId; @@ -387,37 +387,37 @@ struct SelectorPermission { string permissionDescription; } -/// @dev A struct holding fields to describe the plugin in a purely view context. Intended for front end clients. -struct PluginMetadata { - // A human-readable name of the plugin. +/// @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 plugin, following the semantic versioning scheme. + // 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 plugin. + // 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 plugin. + // functions implemented by this module. SelectorPermission[] permissionDescriptors; } -/// @dev A struct describing how the plugin should be installed on a modular account. -struct PluginManifest { +/// @dev A struct describing how the module should be installed on a modular account. +struct ModuleManifest { // List of ERC-165 interface IDs to add to account to support introspection checks. This MUST NOT include - // IPlugin's interface ID. + // IModule's interface ID. bytes4[] interfaceIds; - // If this plugin depends on other plugins' validation functions, the interface IDs of those plugins MUST be + // If this module depends on other modules' validation functions, the interface IDs of those modules MUST be // provided here, with its position in the array matching the `dependencyIndex` members of `ManifestFunction` // structs used in the manifest. bytes4[] dependencyInterfaceIds; - // Execution functions defined in this plugin to be installed on the MSCA. + // Execution functions defined in this module to be installed on the MSCA. bytes4[] executionFunctions; - // Plugin execution functions already installed on the MSCA that this plugin will be able to call. + // Module execution functions already installed on the MSCA that this module will be able to call. bytes4[] permittedExecutionSelectors; - // Boolean to indicate whether the plugin can call any external address. + // Boolean to indicate whether the module can call any external address. bool permitAnyExternalAddress; - // Boolean to indicate whether the plugin needs access to spend native tokens of the account. If false, the - // plugin MUST still be able to spend up to the balance that it sends to the account in the same call. + // Boolean to indicate whether the module needs access to spend native tokens of the account. If false, the + // module MUST still be able to spend up to the balance that it sends to the account in the same call. bool canSpendNativeToken; ManifestExternalCallPermission[] permittedExternalCalls; ManifestAssociatedFunction[] validationFunctions; @@ -429,34 +429,34 @@ struct PluginManifest { ### Expected behavior -#### Responsibilties of `StandardExecutor` and `PluginExecutor` +#### Responsibilties of `StandardExecutor` and `ModuleExecutor` `StandardExecutor` functions are used for open-ended calls to external addresses. -`PluginExecutor` functions are specifically used by plugins to request the account to execute with account's context. Explicit permissions are required for plugins to use `PluginExecutor`. +`ModuleExecutor` functions are specifically used by modules to request the account to execute with account's context. Explicit permissions are required for modules to use `ModuleExecutor`. The following behavior MUST be followed: -- `StandardExecutor` can NOT call plugin execution functions and/or `PluginExecutor`. This is guaranteed by checking whether the call's target implements the `IPlugin` interface via ERC-165 as required. -- `StandardExecutor` can NOT be called by plugin execution functions and/or `PluginExecutor`. -- Plugin execution functions MUST NOT request access to `StandardExecutor`, they MAY request access to `PluginExecutor`. +- `StandardExecutor` can NOT call module execution functions and/or `ModuleExecutor`. This is guaranteed by checking whether the call's target implements the `IModule` interface via ERC-165 as required. +- `StandardExecutor` can NOT be called by module execution functions and/or `ModuleExecutor`. +- Module execution functions MUST NOT request access to `StandardExecutor`, they MAY request access to `ModuleExecutor`. -#### Calls to `installPlugin` +#### Calls to `installModule` -The function `installPlugin` accepts 3 parameters: the address of the plugin to install, the Keccak-256 hash of the plugin's manifest, ABI-encoded data to pass to the plugin's `onInstall` callback. +The function `installModule` accepts 3 parameters: the address of the module to install, the Keccak-256 hash of the module's manifest, ABI-encoded data to pass to the module's `onInstall` callback. -The function MUST retrieve the plugin's manifest by calling `pluginManifest()` using `staticcall`. +The function MUST retrieve the module's manifest by calling `moduleManifest()` using `staticcall`. The function MUST perform the following preliminary checks: -- Revert if the plugin has already been installed on the modular account. -- Revert if the plugin does not implement ERC-165 or does not support the `IPlugin` interface. -- Revert if `manifestHash` does not match the computed Keccak-256 hash of the plugin's returned manifest. This prevents installation of plugins that attempt to install a different plugin configuration than the one that was approved by the client. +- Revert if the module has already been installed on the modular account. +- Revert if the module does not implement ERC-165 or does not support the `IModule` interface. +- Revert if `manifestHash` does not match the computed Keccak-256 hash of the module's returned manifest. This prevents installation of modules that attempt to install a different module configuration than the one that was approved by the client. - Revert if any address in `dependencies` does not support the interface at its matching index in the manifest's `dependencyInterfaceIds`, or if the two array lengths do not match, or if any of the dependencies are not already installed on the modular account. -The function MUST record the manifest hash and dependencies that were used for the plugin's installation. Each dependency's record MUST also be updated to reflect that it has a new dependent. These records MUST be used to ensure calls to `uninstallPlugin` are comprehensive and undo all edited configuration state from installation. The mechanism by which these records are stored and validated is up to the implementation. +The function MUST record the manifest hash and dependencies that were used for the module's installation. Each dependency's record MUST also be updated to reflect that it has a new dependent. These records MUST be used to ensure calls to `uninstallModule` are comprehensive and undo all edited configuration state from installation. The mechanism by which these records are stored and validated is up to the implementation. -The function MUST store the plugin's permitted function selectors, permitted external calls, and whether it can spend the account's native tokens, to be able to validate calls to `executeFromPlugin` and `executeFromPluginExternal`. +The function MUST store the module's permitted function selectors, permitted external calls, and whether it can spend the account's native tokens, to be able to validate calls to `executeFromModule` and `executeFromModuleExternal`. The function MUST parse through the execution functions, validation functions, and hooks in the manifest and add them to the modular account after resolving each `ManifestFunction` type. @@ -465,36 +465,36 @@ The function MUST parse through the execution functions, validation functions, a The function MAY store the interface IDs provided in the manifest's `interfaceIds` and update its `supportsInterface` behavior accordingly. -Next, the function MUST call the plugin's `onInstall` callback with the data provided in the `pluginInstallData` parameter. This serves to initialize the plugin state for the modular account. If `onInstall` reverts, the `installPlugin` function MUST revert. +Next, the function MUST call the module's `onInstall` callback with the data provided in the `moduleInstallData` parameter. This serves to initialize the module state for the modular account. If `onInstall` reverts, the `installModule` function MUST revert. -Finally, the function MUST emit the event `PluginInstalled` with the plugin's address, the hash of its manifest, and the dependencies that were used. +Finally, the function MUST emit the event `ModuleInstalled` with the module's address, the hash of its manifest, and the dependencies that were used. -> **⚠️ The ability to install and uninstall plugins is very powerful. The security of these functions determines the security of the account. It is critical for modular account implementers to make sure the implementation of the functions in `IPluginManager` have the proper security consideration and access control in place.** +> **⚠️ The ability to install and uninstall modules is very powerful. The security of these functions determines the security of the account. It is critical for modular account implementers to make sure the implementation of the functions in `IModuleManager` have the proper security consideration and access control in place.** -#### Calls to `uninstallPlugin` +#### Calls to `uninstallModule` -The function `uninstallPlugin` accepts 3 parameters: the address of the plugin to uninstall, a bytes field that may have custom requirements or uses by the implementing account, and ABI-encoded data to pass to the plugin's `onUninstall` callback. +The function `uninstallModule` accepts 3 parameters: the address of the module to uninstall, a bytes field that may have custom requirements or uses by the implementing account, and ABI-encoded data to pass to the module's `onUninstall` callback. -The function MUST revert if the plugin is not installed on the modular account. +The function MUST revert if the module is not installed on the modular account. The function SHOULD perform the following checks: -- Revert if the hash of the manifest used at install time does not match the computed Keccak-256 hash of the plugin's current manifest. This prevents unclean removal of plugins that attempt to force a removal of a different plugin configuration than the one that was originally approved by the client for installation. To allow for removal of such plugins, the modular account MAY implement the capability for the manifest to be encoded in the config field as a parameter. -- Revert if there is at least 1 other installed plugin that depends on validation functions added by this plugin. Plugins used as dependencies SHOULD NOT be uninstalled while dependent plugins exist. +- Revert if the hash of the manifest used at install time does not match the computed Keccak-256 hash of the module's current manifest. This prevents unclean removal of modules that attempt to force a removal of a different module configuration than the one that was originally approved by the client for installation. To allow for removal of such modules, the modular account MAY implement the capability for the manifest to be encoded in the config field as a parameter. +- Revert if there is at least 1 other installed module that depends on validation functions added by this module. Modules used as dependencies SHOULD NOT be uninstalled while dependent modules exist. -The function SHOULD update account storage to reflect the uninstall via inspection functions, such as those defined by `IAccountLoupe`. Each dependency's record SHOULD also be updated to reflect that it has no longer has this plugin as a dependent. +The function SHOULD update account storage to reflect the uninstall via inspection functions, such as those defined by `IAccountLoupe`. Each dependency's record SHOULD also be updated to reflect that it has no longer has this module as a dependent. -The function MUST remove records for the plugin's manifest hash, dependencies, permitted function selectors, permitted external calls, and whether it can spend the account's native tokens. +The function MUST remove records for the module's manifest hash, dependencies, permitted function selectors, permitted external calls, and whether it can spend the account's native tokens. -The function MUST parse through the execution functions, validation functions, and hooks in the manifest and remove them from the modular account after resolving each `ManifestFunction` type. If multiple plugins added the same hook, it MUST persist until the last plugin is uninstalled. +The function MUST parse through the execution functions, validation functions, and hooks in the manifest and remove them from the modular account after resolving each `ManifestFunction` type. If multiple modules added the same hook, it MUST persist until the last module is uninstalled. -If the account stored the interface IDs provided in the manifest's `interfaceIds` during installation, it MUST remove them and update its `supportsInterface` behavior accordingly. If multiple plugins added the same interface ID, it MUST persist until the last plugin is uninstalled. +If the account stored the interface IDs provided in the manifest's `interfaceIds` during installation, it MUST remove them and update its `supportsInterface` behavior accordingly. If multiple modules added the same interface ID, it MUST persist until the last module is uninstalled. -Next, the function MUST call the plugin's `onUninstall` callback with the data provided in the `pluginUninstallData` parameter. This serves to clear the plugin state for the modular account. If `onUninstall` reverts, execution SHOULD continue to allow the uninstall to complete. +Next, the function MUST call the module's `onUninstall` callback with the data provided in the `moduleUninstallData` parameter. This serves to clear the module state for the modular account. If `onUninstall` reverts, execution SHOULD continue to allow the uninstall to complete. -Finally, the function MUST emit the event `PluginUninstalled` with the plugin's address and whether the `onUninstall` callback succeeded. +Finally, the function MUST emit the event `ModuleUninstalled` with the module's address and whether the `onUninstall` callback succeeded. -> **⚠️ Incorrectly uninstalled plugins can prevent uninstalls of their dependencies. Therefore, some form of validation that the uninstall step completely and correctly removes the plugin and its usage of dependencies is required.** +> **⚠️ Incorrectly uninstalled modules can prevent uninstalls of their dependencies. Therefore, some form of validation that the uninstall step completely and correctly removes the module and its usage of dependencies is required.** #### Calls to `validateUserOp` @@ -506,26 +506,26 @@ Then, the modular account MUST execute the validation function with the user ope #### Calls to execution functions -When a function other than a native function is called on an modular account, it MUST find the plugin configuration for the corresponding selector added via plugin installation. If no corresponding plugin is found, the modular account MUST revert. Otherwise, the following steps MUST be performed. +When a function other than a native function is called on an modular account, it MUST find the module configuration for the corresponding selector added via module installation. If no corresponding module is found, the modular account MUST revert. Otherwise, the following steps MUST be performed. -Additionally, when the modular account natively implements functions in `IPluginManager` and `IStandardExecutor`, the same following steps MUST be performed for those functions. Other native functions MAY perform these steps. +Additionally, when the modular account natively implements functions in `IModuleManager` and `IStandardExecutor`, the same following steps MUST be performed for those functions. Other native functions MAY perform these steps. The steps to perform are: - If the call is not from the `EntryPoint`, then find an associated runtime validation function. If one does not exist, execution MUST revert. The modular account MUST execute all pre runtime validation hooks, then the runtime validation function, with the `call` opcode. All of these functions MUST receive the caller, value, and execution function's calldata as parameters. If any of these functions revert, execution MUST revert. If any pre execution hooks are set to `PRE_HOOK_ALWAYS_DENY`, execution MUST revert. If the validation function is set to `RUNTIME_VALIDATION_ALWAYS_ALLOW`, the runtime validation function MUST be bypassed. -- If there are pre execution hooks defined for the execution function, execute those hooks with the caller, value, and execution function's calldata as parameters. If any of these hooks returns data, it MUST be preserved until the call to the post execution hook. The operation MUST be done with the `call` opcode. If there are duplicate pre execution hooks (i.e., hooks with identical `PluginEntity`s), run the hook only once. If any of these functions revert, execution MUST revert. +- If there are pre execution hooks defined for the execution function, execute those hooks with the caller, value, and execution function's calldata as parameters. If any of these hooks returns data, it MUST be preserved until the call to the post execution hook. The operation MUST be done with the `call` opcode. If there are duplicate pre execution hooks (i.e., hooks with identical `ModuleEntity`s), run the hook only once. If any of these functions revert, execution MUST revert. - Run the execution function. - If any post execution hooks are defined, run the functions. If a pre execution hook returned data to the account, that data MUST be passed as a parameter to the associated post execution hook. The operation MUST be done with the `call` opcode. If there are duplicate post execution hooks, run them once for each unique associated pre execution hook. For post execution hooks without an associated pre execution hook, run the hook only once. If any of these functions revert, execution MUST revert. -The set of hooks run for a given execution function MUST be the hooks specified by account state at the start of the execution phase. This is relevant for functions like `installPlugin` and `uninstallPlugin`, which modify the account state, and possibly other execution or native functions as well. +The set of hooks run for a given execution function MUST be the hooks specified by account state at the start of the execution phase. This is relevant for functions like `installModule` and `uninstallModule`, which modify the account state, and possibly other execution or native functions as well. -#### Calls made from plugins +#### Calls made from modules -Plugins MAY interact with other plugins and external addresses through the modular account using the functions defined in the `IPluginExecutor` interface. These functions MAY be called without a defined validation function, but the modular account MUST enforce these checks and behaviors: +Modules MAY interact with other modules and external addresses through the modular account using the functions defined in the `IModuleExecutor` interface. These functions MAY be called without a defined validation function, but the modular account MUST enforce these checks and behaviors: -The `executeFromPlugin` function MUST allow plugins to call execution functions installed by plugins on the modular account. Hooks matching the function selector provided in `data` MUST be called. If the calling plugin's manifest did not include the provided function selector within `permittedExecutionSelectors` at the time of installation, execution MUST revert. +The `executeFromModule` function MUST allow modules to call execution functions installed by modules on the modular account. Hooks matching the function selector provided in `data` MUST be called. If the calling module's manifest did not include the provided function selector within `permittedExecutionSelectors` at the time of installation, execution MUST revert. -The `executeFromPluginExternal` function MUST allow plugins to call external addresses as specified by its parameters on behalf of the modular account. If the calling plugin's manifest did not explicitly allow the external call within `permittedExternalCalls` at the time of installation, execution MUST revert. +The `executeFromModuleExternal` function MUST allow modules to call external addresses as specified by its parameters on behalf of the modular account. If the calling module's manifest did not explicitly allow the external call within `permittedExternalCalls` at the time of installation, execution MUST revert. ## Rationale @@ -533,7 +533,7 @@ ERC-4337 compatible accounts must implement the `IAccount` interface, which cons The function routing pattern of ERC-2535 is the logical starting point for achieving this extension into multi-functional accounts. It also meets our other primary design rationale of generalizing execution calls across multiple implementing contracts. However, a strict diamond pattern is constrained by its inability to customize validation schemes for specific execution functions in the context of `validateUserOp`, and its requirement of `delegatecall`. -This proposal includes several interfaces that build on ERC-4337 and are inspired by ERC-2535. First, we standardize a set of modular functions that allow smart contract developers greater flexibility in bundling validation, execution, and hook logic. We also propose interfaces that take inspiration from the diamond standard and provide methods for querying execution functions, validation functions, and hooks on a modular account. The rest of the interfaces describe a plugin's methods for exposing its modular functions and desired configuration, and the modular account's methods for installing and removing plugins and allowing execution across plugins and external addresses. +This proposal includes several interfaces that build on ERC-4337 and are inspired by ERC-2535. First, we standardize a set of modular functions that allow smart contract developers greater flexibility in bundling validation, execution, and hook logic. We also propose interfaces that take inspiration from the diamond standard and provide methods for querying execution functions, validation functions, and hooks on a modular account. The rest of the interfaces describe a module's methods for exposing its modular functions and desired configuration, and the modular account's methods for installing and removing modules and allowing execution across modules and external addresses. ## Backwards Compatibility diff --git a/standard/assets/eip-6900/MSCA_Shared_Components_Diagram.svg b/standard/assets/eip-6900/MSCA_Shared_Components_Diagram.svg index 836a8ad9..e443dd56 100644 --- a/standard/assets/eip-6900/MSCA_Shared_Components_Diagram.svg +++ b/standard/assets/eip-6900/MSCA_Shared_Components_Diagram.svg @@ -14,4 +14,4 @@ - Alice's MSCAUO / TxPluginHooksValidationExecutionPluginHooksValidationExecutionUO / TxBob's MSCA \ No newline at end of file + Alice's MSCAUO / TxModuleHooksValidationExecutionModuleHooksValidationExecutionUO / TxBob's MSCA \ No newline at end of file diff --git a/standard/assets/eip-6900/Modular_Account_Call_Flow.svg b/standard/assets/eip-6900/Modular_Account_Call_Flow.svg index c4112c33..8d7f7611 100644 --- a/standard/assets/eip-6900/Modular_Account_Call_Flow.svg +++ b/standard/assets/eip-6900/Modular_Account_Call_Flow.svg @@ -14,4 +14,4 @@ - Direct Call1 validation2. Execution Modular Account validateUserOpPre User Operation Validation Hook(s)User Operation Validation FunctionPre Execution Hook(s)Native Function / (Plugin) Execution FunctionPost Execution Hook(s)EntryPointEOA / SCRuntime Validation FunctionModular Account Call FlowPre Runtime Validation Hook(s) \ No newline at end of file + Direct Call1 validation2. Execution Modular Account validateUserOpPre User Operation Validation Hook(s)User Operation Validation FunctionPre Execution Hook(s)Native Function / (Module) Execution FunctionPost Execution Hook(s)EntryPointEOA / SCRuntime Validation FunctionModular Account Call FlowPre Runtime Validation Hook(s) \ No newline at end of file diff --git a/standard/assets/eip-6900/Plugin_Execution_Flow.svg b/standard/assets/eip-6900/Plugin_Execution_Flow.svg index 3a94b512..ed1ff580 100644 --- a/standard/assets/eip-6900/Plugin_Execution_Flow.svg +++ b/standard/assets/eip-6900/Plugin_Execution_Flow.svg @@ -18,4 +18,4 @@ - Permitted External Contracts & Methods executeFromPluginPre Execution Hook(s)Permitted Plugin Execution FunctionPost Execution Hook(s)PluginsPlugin Permission Check executeFromPluginExternal(Plugin) Execution Function1 calls plugin 2.2 calls external contracts through executeFromPluginExternalPlugin Permission CheckModular AccountPlugin Execution Flow2.1 calls other installed plugin through executeFromPluginPre executeFromPluginExternal Hook(s)Post executeFromPluginExternal Hook(s) \ No newline at end of file + Permitted External Contracts & Methods executeFromModulePre Execution Hook(s)Permitted Module Execution FunctionPost Execution Hook(s)ModulesModule Permission Check executeFromModuleExternal(Module) Execution Function1 calls module 2.2 calls external contracts through executeFromModuleExternalModule Permission CheckModular AccountModule Execution Flow2.1 calls other installed module through executeFromModulePre executeFromModuleExternal Hook(s)Post executeFromModuleExternal Hook(s) \ No newline at end of file diff --git a/test/account/AccountExecHooks.t.sol b/test/account/AccountExecHooks.t.sol index ba3a2098..052cea4e 100644 --- a/test/account/AccountExecHooks.t.sol +++ b/test/account/AccountExecHooks.t.sol @@ -3,17 +3,17 @@ pragma solidity ^0.8.19; import {IExecutionHook} from "../../src/interfaces/IExecutionHook.sol"; import { - IPlugin, + IModule, ManifestExecutionFunction, ManifestExecutionHook, - PluginManifest -} from "../../src/interfaces/IPlugin.sol"; + ModuleManifest +} from "../../src/interfaces/IModule.sol"; -import {MockPlugin} from "../mocks/MockPlugin.sol"; +import {MockModule} from "../mocks/MockModule.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; contract AccountExecHooksTest is AccountTestBase { - MockPlugin public mockPlugin1; + MockModule public mockModule1; bytes32 public manifestHash1; bytes32 public manifestHash2; @@ -22,11 +22,11 @@ contract AccountExecHooksTest is AccountTestBase { uint32 internal constant _POST_HOOK_FUNCTION_ID_2 = 2; uint32 internal constant _BOTH_HOOKS_FUNCTION_ID_3 = 3; - PluginManifest internal _m1; + ModuleManifest internal _m1; - event PluginInstalled(address indexed plugin, bytes32 manifestHash); - event PluginUninstalled(address indexed plugin, bool indexed callbacksSucceeded); - // emitted by MockPlugin + event ModuleInstalled(address indexed module, bytes32 manifestHash); + event ModuleUninstalled(address indexed module, bool indexed callbacksSucceeded); + // emitted by MockModule event ReceivedCall(bytes msgData, uint256 msgValue); function setUp() public { @@ -42,7 +42,7 @@ contract AccountExecHooksTest is AccountTestBase { } function test_preExecHook_install() public { - _installPlugin1WithHooks( + _installModule1WithHooks( ManifestExecutionHook({ executionSelector: _EXEC_SELECTOR, entityId: _PRE_HOOK_FUNCTION_ID_1, @@ -52,7 +52,7 @@ contract AccountExecHooksTest is AccountTestBase { ); } - /// @dev Plugin 1 hook pair: [1, null] + /// @dev Module 1 hook pair: [1, null] /// Expected execution: [1, null] function test_preExecHook_run() public { test_preExecHook_install(); @@ -66,7 +66,7 @@ contract AccountExecHooksTest is AccountTestBase { uint256(0), // msg.value in call to account abi.encodeWithSelector(_EXEC_SELECTOR) ), - 0 // msg value in call to plugin + 0 // msg value in call to module ); (bool success,) = address(account1).call(abi.encodeWithSelector(_EXEC_SELECTOR)); @@ -76,11 +76,11 @@ contract AccountExecHooksTest is AccountTestBase { function test_preExecHook_uninstall() public { test_preExecHook_install(); - _uninstallPlugin(mockPlugin1); + _uninstallModule(mockModule1); } function test_execHookPair_install() public { - _installPlugin1WithHooks( + _installModule1WithHooks( ManifestExecutionHook({ executionSelector: _EXEC_SELECTOR, entityId: _BOTH_HOOKS_FUNCTION_ID_3, @@ -90,7 +90,7 @@ contract AccountExecHooksTest is AccountTestBase { ); } - /// @dev Plugin 1 hook pair: [1, 2] + /// @dev Module 1 hook pair: [1, 2] /// Expected execution: [1, 2] function test_execHookPair_run() public { test_execHookPair_install(); @@ -105,7 +105,7 @@ contract AccountExecHooksTest is AccountTestBase { uint256(0), // msg.value in call to account abi.encodeWithSelector(_EXEC_SELECTOR) ), - 0 // msg value in call to plugin + 0 // msg value in call to module ); vm.expectEmit(true, true, true, true); // exec call @@ -114,7 +114,7 @@ contract AccountExecHooksTest is AccountTestBase { // post hook call emit ReceivedCall( abi.encodeCall(IExecutionHook.postExecutionHook, (_BOTH_HOOKS_FUNCTION_ID_3, "")), - 0 // msg value in call to plugin + 0 // msg value in call to module ); (bool success,) = address(account1).call(abi.encodeWithSelector(_EXEC_SELECTOR)); @@ -124,11 +124,11 @@ contract AccountExecHooksTest is AccountTestBase { function test_execHookPair_uninstall() public { test_execHookPair_install(); - _uninstallPlugin(mockPlugin1); + _uninstallModule(mockModule1); } function test_postOnlyExecHook_install() public { - _installPlugin1WithHooks( + _installModule1WithHooks( ManifestExecutionHook({ executionSelector: _EXEC_SELECTOR, entityId: _POST_HOOK_FUNCTION_ID_2, @@ -138,7 +138,7 @@ contract AccountExecHooksTest is AccountTestBase { ); } - /// @dev Plugin 1 hook pair: [null, 2] + /// @dev Module 1 hook pair: [null, 2] /// Expected execution: [null, 2] function test_postOnlyExecHook_run() public { test_postOnlyExecHook_install(); @@ -146,7 +146,7 @@ contract AccountExecHooksTest is AccountTestBase { vm.expectEmit(true, true, true, true); emit ReceivedCall( abi.encodeCall(IExecutionHook.postExecutionHook, (_POST_HOOK_FUNCTION_ID_2, "")), - 0 // msg value in call to plugin + 0 // msg value in call to module ); (bool success,) = address(account1).call(abi.encodeWithSelector(_EXEC_SELECTOR)); @@ -156,34 +156,34 @@ contract AccountExecHooksTest is AccountTestBase { function test_postOnlyExecHook_uninstall() public { test_postOnlyExecHook_install(); - _uninstallPlugin(mockPlugin1); + _uninstallModule(mockModule1); } - function _installPlugin1WithHooks(ManifestExecutionHook memory execHooks) internal { + function _installModule1WithHooks(ManifestExecutionHook memory execHooks) internal { _m1.executionHooks.push(execHooks); - mockPlugin1 = new MockPlugin(_m1); - manifestHash1 = keccak256(abi.encode(mockPlugin1.pluginManifest())); + mockModule1 = new MockModule(_m1); + manifestHash1 = keccak256(abi.encode(mockModule1.moduleManifest())); vm.expectEmit(true, true, true, true); - emit ReceivedCall(abi.encodeCall(IPlugin.onInstall, (bytes(""))), 0); + emit ReceivedCall(abi.encodeCall(IModule.onInstall, (bytes(""))), 0); vm.expectEmit(true, true, true, true); - emit PluginInstalled(address(mockPlugin1), manifestHash1); + emit ModuleInstalled(address(mockModule1), manifestHash1); vm.prank(address(entryPoint)); - account1.installPlugin({ - plugin: address(mockPlugin1), + account1.installModule({ + module: address(mockModule1), manifestHash: manifestHash1, - pluginInstallData: bytes("") + moduleInstallData: bytes("") }); } - function _uninstallPlugin(MockPlugin plugin) internal { + function _uninstallModule(MockModule module) internal { vm.expectEmit(true, true, true, true); - emit ReceivedCall(abi.encodeCall(IPlugin.onUninstall, (bytes(""))), 0); + emit ReceivedCall(abi.encodeCall(IModule.onUninstall, (bytes(""))), 0); vm.expectEmit(true, true, true, true); - emit PluginUninstalled(address(plugin), true); + emit ModuleUninstalled(address(module), true); vm.prank(address(entryPoint)); - account1.uninstallPlugin(address(plugin), bytes(""), bytes("")); + account1.uninstallModule(address(module), bytes(""), bytes("")); } } diff --git a/test/account/AccountLoupe.t.sol b/test/account/AccountLoupe.t.sol index f0670848..c4f4abc0 100644 --- a/test/account/AccountLoupe.t.sol +++ b/test/account/AccountLoupe.t.sol @@ -3,38 +3,38 @@ pragma solidity ^0.8.19; import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; -import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; +import {ModuleEntity, ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; -import {IPluginManager} from "../../src/interfaces/IPluginManager.sol"; +import {IModuleManager} from "../../src/interfaces/IModuleManager.sol"; import {IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; -import {ComprehensivePlugin} from "../mocks/plugins/ComprehensivePlugin.sol"; +import {ComprehensiveModule} from "../mocks/modules/ComprehensiveModule.sol"; import {CustomValidationTestBase} from "../utils/CustomValidationTestBase.sol"; contract AccountLoupeTest is CustomValidationTestBase { - ComprehensivePlugin public comprehensivePlugin; + ComprehensiveModule public comprehensiveModule; event ReceivedCall(bytes msgData, uint256 msgValue); function setUp() public { - comprehensivePlugin = new ComprehensivePlugin(); + comprehensiveModule = new ComprehensiveModule(); _customValidationSetup(); - bytes32 manifestHash = keccak256(abi.encode(comprehensivePlugin.pluginManifest())); + bytes32 manifestHash = keccak256(abi.encode(comprehensiveModule.moduleManifest())); vm.prank(address(entryPoint)); - account1.installPlugin(address(comprehensivePlugin), manifestHash, ""); + account1.installModule(address(comprehensiveModule), manifestHash, ""); } - function test_pluginLoupe_getInstalledPlugins_initial() public { - address[] memory plugins = account1.getInstalledPlugins(); + function test_moduleLoupe_getInstalledModules_initial() public { + address[] memory modules = account1.getInstalledModules(); - assertEq(plugins.length, 1); + assertEq(modules.length, 1); - assertEq(plugins[0], address(comprehensivePlugin)); + assertEq(modules[0], address(comprehensiveModule)); } - function test_pluginLoupe_getExecutionFunctionHandler_native() public { + function test_moduleLoupe_getExecutionFunctionHandler_native() public { bytes4[] memory selectorsToCheck = new bytes4[](5); selectorsToCheck[0] = IStandardExecutor.execute.selector; @@ -43,61 +43,61 @@ contract AccountLoupeTest is CustomValidationTestBase { selectorsToCheck[2] = UUPSUpgradeable.upgradeToAndCall.selector; - selectorsToCheck[3] = IPluginManager.installPlugin.selector; + selectorsToCheck[3] = IModuleManager.installModule.selector; - selectorsToCheck[4] = IPluginManager.uninstallPlugin.selector; + selectorsToCheck[4] = IModuleManager.uninstallModule.selector; for (uint256 i = 0; i < selectorsToCheck.length; i++) { - address plugin = account1.getExecutionFunctionHandler(selectorsToCheck[i]); + address module = account1.getExecutionFunctionHandler(selectorsToCheck[i]); - assertEq(plugin, address(account1)); + assertEq(module, address(account1)); } } - function test_pluginLoupe_getExecutionFunctionConfig_plugin() public { + function test_moduleLoupe_getExecutionFunctionConfig_module() public { bytes4[] memory selectorsToCheck = new bytes4[](1); - address[] memory expectedPluginAddress = new address[](1); + address[] memory expectedModuleAddress = new address[](1); - selectorsToCheck[0] = comprehensivePlugin.foo.selector; - expectedPluginAddress[0] = address(comprehensivePlugin); + selectorsToCheck[0] = comprehensiveModule.foo.selector; + expectedModuleAddress[0] = address(comprehensiveModule); for (uint256 i = 0; i < selectorsToCheck.length; i++) { - address plugin = account1.getExecutionFunctionHandler(selectorsToCheck[i]); + address module = account1.getExecutionFunctionHandler(selectorsToCheck[i]); - assertEq(plugin, expectedPluginAddress[i]); + assertEq(module, expectedModuleAddress[i]); } } - function test_pluginLoupe_getSelectors() public { - PluginEntity comprehensivePluginValidation = - PluginEntityLib.pack(address(comprehensivePlugin), uint32(ComprehensivePlugin.EntityId.VALIDATION)); + function test_moduleLoupe_getSelectors() public { + ModuleEntity comprehensiveModuleValidation = + ModuleEntityLib.pack(address(comprehensiveModule), uint32(ComprehensiveModule.EntityId.VALIDATION)); - bytes4[] memory selectors = account1.getSelectors(comprehensivePluginValidation); + bytes4[] memory selectors = account1.getSelectors(comprehensiveModuleValidation); assertEq(selectors.length, 1); - assertEq(selectors[0], comprehensivePlugin.foo.selector); + assertEq(selectors[0], comprehensiveModule.foo.selector); } - function test_pluginLoupe_getExecutionHooks() public { - ExecutionHook[] memory hooks = account1.getExecutionHooks(comprehensivePlugin.foo.selector); + function test_moduleLoupe_getExecutionHooks() public { + ExecutionHook[] memory hooks = account1.getExecutionHooks(comprehensiveModule.foo.selector); ExecutionHook[3] memory expectedHooks = [ ExecutionHook({ - hookFunction: PluginEntityLib.pack( - address(comprehensivePlugin), uint32(ComprehensivePlugin.EntityId.BOTH_EXECUTION_HOOKS) + hookFunction: ModuleEntityLib.pack( + address(comprehensiveModule), uint32(ComprehensiveModule.EntityId.BOTH_EXECUTION_HOOKS) ), isPreHook: true, isPostHook: true }), ExecutionHook({ - hookFunction: PluginEntityLib.pack( - address(comprehensivePlugin), uint32(ComprehensivePlugin.EntityId.PRE_EXECUTION_HOOK) + hookFunction: ModuleEntityLib.pack( + address(comprehensiveModule), uint32(ComprehensiveModule.EntityId.PRE_EXECUTION_HOOK) ), isPreHook: true, isPostHook: false }), ExecutionHook({ - hookFunction: PluginEntityLib.pack( - address(comprehensivePlugin), uint32(ComprehensivePlugin.EntityId.POST_EXECUTION_HOOK) + hookFunction: ModuleEntityLib.pack( + address(comprehensiveModule), uint32(ComprehensiveModule.EntityId.POST_EXECUTION_HOOK) ), isPreHook: false, isPostHook: true @@ -107,30 +107,30 @@ contract AccountLoupeTest is CustomValidationTestBase { assertEq(hooks.length, 3); for (uint256 i = 0; i < hooks.length; i++) { assertEq( - PluginEntity.unwrap(hooks[i].hookFunction), PluginEntity.unwrap(expectedHooks[i].hookFunction) + ModuleEntity.unwrap(hooks[i].hookFunction), ModuleEntity.unwrap(expectedHooks[i].hookFunction) ); assertEq(hooks[i].isPreHook, expectedHooks[i].isPreHook); assertEq(hooks[i].isPostHook, expectedHooks[i].isPostHook); } } - function test_pluginLoupe_getValidationHooks() public { - PluginEntity[] memory hooks = account1.getPreValidationHooks(_signerValidation); + function test_moduleLoupe_getValidationHooks() public { + ModuleEntity[] memory hooks = account1.getPreValidationHooks(_signerValidation); assertEq(hooks.length, 2); assertEq( - PluginEntity.unwrap(hooks[0]), - PluginEntity.unwrap( - PluginEntityLib.pack( - address(comprehensivePlugin), uint32(ComprehensivePlugin.EntityId.PRE_VALIDATION_HOOK_1) + ModuleEntity.unwrap(hooks[0]), + ModuleEntity.unwrap( + ModuleEntityLib.pack( + address(comprehensiveModule), uint32(ComprehensiveModule.EntityId.PRE_VALIDATION_HOOK_1) ) ) ); assertEq( - PluginEntity.unwrap(hooks[1]), - PluginEntity.unwrap( - PluginEntityLib.pack( - address(comprehensivePlugin), uint32(ComprehensivePlugin.EntityId.PRE_VALIDATION_HOOK_2) + ModuleEntity.unwrap(hooks[1]), + ModuleEntity.unwrap( + ModuleEntityLib.pack( + address(comprehensiveModule), uint32(ComprehensiveModule.EntityId.PRE_VALIDATION_HOOK_2) ) ) ); @@ -142,14 +142,14 @@ contract AccountLoupeTest is CustomValidationTestBase { internal virtual override - returns (PluginEntity, bool, bool, bytes4[] memory, bytes memory, bytes memory, bytes memory) + returns (ModuleEntity, bool, bool, bytes4[] memory, bytes memory, bytes memory, bytes memory) { - PluginEntity[] memory preValidationHooks = new PluginEntity[](2); - preValidationHooks[0] = PluginEntityLib.pack( - address(comprehensivePlugin), uint32(ComprehensivePlugin.EntityId.PRE_VALIDATION_HOOK_1) + ModuleEntity[] memory preValidationHooks = new ModuleEntity[](2); + preValidationHooks[0] = ModuleEntityLib.pack( + address(comprehensiveModule), uint32(ComprehensiveModule.EntityId.PRE_VALIDATION_HOOK_1) ); - preValidationHooks[1] = PluginEntityLib.pack( - address(comprehensivePlugin), uint32(ComprehensivePlugin.EntityId.PRE_VALIDATION_HOOK_2) + preValidationHooks[1] = ModuleEntityLib.pack( + address(comprehensiveModule), uint32(ComprehensiveModule.EntityId.PRE_VALIDATION_HOOK_2) ); bytes[] memory installDatas = new bytes[](2); diff --git a/test/account/AccountReturnData.t.sol b/test/account/AccountReturnData.t.sol index fa8bdf86..febaa54b 100644 --- a/test/account/AccountReturnData.t.sol +++ b/test/account/AccountReturnData.t.sol @@ -1,51 +1,51 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; +import {ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {Call} from "../../src/interfaces/IStandardExecutor.sol"; import { RegularResultContract, - ResultConsumerPlugin, - ResultCreatorPlugin -} from "../mocks/plugins/ReturnDataPluginMocks.sol"; + ResultConsumerModule, + ResultCreatorModule +} from "../mocks/modules/ReturnDataModuleMocks.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; import {TEST_DEFAULT_VALIDATION_ENTITY_ID} from "../utils/TestConstants.sol"; -// Tests all the different ways that return data can be read from plugins through an account +// Tests all the different ways that return data can be read from modules through an account contract AccountReturnDataTest is AccountTestBase { RegularResultContract public regularResultContract; - ResultCreatorPlugin public resultCreatorPlugin; - ResultConsumerPlugin public resultConsumerPlugin; + ResultCreatorModule public resultCreatorModule; + ResultConsumerModule public resultConsumerModule; function setUp() public { _transferOwnershipToTest(); regularResultContract = new RegularResultContract(); - resultCreatorPlugin = new ResultCreatorPlugin(); - resultConsumerPlugin = new ResultConsumerPlugin(resultCreatorPlugin, regularResultContract); + resultCreatorModule = new ResultCreatorModule(); + resultConsumerModule = new ResultConsumerModule(resultCreatorModule, regularResultContract); - // Add the result creator plugin to the account - bytes32 resultCreatorManifestHash = keccak256(abi.encode(resultCreatorPlugin.pluginManifest())); + // Add the result creator module to the account + bytes32 resultCreatorManifestHash = keccak256(abi.encode(resultCreatorModule.moduleManifest())); vm.prank(address(entryPoint)); - account1.installPlugin({ - plugin: address(resultCreatorPlugin), + account1.installModule({ + module: address(resultCreatorModule), manifestHash: resultCreatorManifestHash, - pluginInstallData: "" + moduleInstallData: "" }); - // Add the result consumer plugin to the account - bytes32 resultConsumerManifestHash = keccak256(abi.encode(resultConsumerPlugin.pluginManifest())); + // Add the result consumer module to the account + bytes32 resultConsumerManifestHash = keccak256(abi.encode(resultConsumerModule.moduleManifest())); vm.prank(address(entryPoint)); - account1.installPlugin({ - plugin: address(resultConsumerPlugin), + account1.installModule({ + module: address(resultConsumerModule), manifestHash: resultConsumerManifestHash, - pluginInstallData: "" + moduleInstallData: "" }); } - // Tests the ability to read the result of plugin execution functions via the account's fallback + // Tests the ability to read the result of module execution functions via the account's fallback function test_returnData_fallback() public { - bytes32 result = ResultCreatorPlugin(address(account1)).foo(); + bytes32 result = ResultCreatorModule(address(account1)).foo(); assertEq(result, keccak256("bar")); } @@ -58,7 +58,7 @@ contract AccountReturnDataTest is AccountTestBase { (address(regularResultContract), 0, abi.encodeCall(RegularResultContract.foo, ())) ), _encodeSignature( - PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), + ModuleEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, "" ) @@ -86,7 +86,7 @@ contract AccountReturnDataTest is AccountTestBase { bytes memory retData = account1.executeWithAuthorization( abi.encodeCall(account1.executeBatch, (calls)), _encodeSignature( - PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), + ModuleEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, "" ) @@ -102,15 +102,15 @@ contract AccountReturnDataTest is AccountTestBase { } // Tests the ability to read data via routing to fallback functions - function test_returnData_execFromPlugin_fallback() public { - bool result = ResultConsumerPlugin(address(account1)).checkResultFallback(keccak256("bar")); + function test_returnData_execFromModule_fallback() public { + bool result = ResultConsumerModule(address(account1)).checkResultFallback(keccak256("bar")); assertTrue(result); } // Tests the ability to read data via executeWithAuthorization function test_returnData_authorized_exec() public { - bool result = ResultConsumerPlugin(address(account1)).checkResultExecuteWithAuthorization( + bool result = ResultConsumerModule(address(account1)).checkResultExecuteWithAuthorization( address(regularResultContract), keccak256("bar") ); diff --git a/test/account/DirectCallsFromPlugin.t.sol b/test/account/DirectCallsFromModule.t.sol similarity index 59% rename from test/account/DirectCallsFromPlugin.t.sol rename to test/account/DirectCallsFromModule.t.sol index 9e56739a..b8d39728 100644 --- a/test/account/DirectCallsFromPlugin.t.sol +++ b/test/account/DirectCallsFromModule.t.sol @@ -1,53 +1,53 @@ pragma solidity ^0.8.19; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; +import {ModuleEntity, ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {ValidationConfig, ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; import {Call, IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; -import {DirectCallPlugin} from "../mocks/plugins/DirectCallPlugin.sol"; +import {DirectCallModule} from "../mocks/modules/DirectCallModule.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; -contract DirectCallsFromPluginTest is AccountTestBase { +contract DirectCallsFromModuleTest is AccountTestBase { using ValidationConfigLib for ValidationConfig; - DirectCallPlugin internal _plugin; - PluginEntity internal _pluginEntity; + DirectCallModule internal _module; + ModuleEntity internal _moduleEntity; function setUp() public { - _plugin = new DirectCallPlugin(); - assertFalse(_plugin.preHookRan()); - assertFalse(_plugin.postHookRan()); - _pluginEntity = PluginEntityLib.pack(address(_plugin), type(uint32).max); + _module = new DirectCallModule(); + assertFalse(_module.preHookRan()); + assertFalse(_module.postHookRan()); + _moduleEntity = ModuleEntityLib.pack(address(_module), type(uint32).max); } /* -------------------------------------------------------------------------- */ /* Negatives */ /* -------------------------------------------------------------------------- */ - function test_Fail_DirectCallPluginNotInstalled() external { - vm.prank(address(_plugin)); + function test_Fail_DirectCallModuleNotInstalled() external { + vm.prank(address(_module)); vm.expectRevert(_buildDirectCallDisallowedError(IStandardExecutor.execute.selector)); account1.execute(address(0), 0, ""); } - function test_Fail_DirectCallPluginUninstalled() external { - _installPlugin(); + function test_Fail_DirectCallModuleUninstalled() external { + _installModule(); - _uninstallPlugin(); + _uninstallModule(); - vm.prank(address(_plugin)); + vm.prank(address(_module)); vm.expectRevert(_buildDirectCallDisallowedError(IStandardExecutor.execute.selector)); account1.execute(address(0), 0, ""); } - function test_Fail_DirectCallPluginCallOtherSelector() external { - _installPlugin(); + function test_Fail_DirectCallModuleCallOtherSelector() external { + _installModule(); Call[] memory calls = new Call[](0); - vm.prank(address(_plugin)); + vm.prank(address(_module)); vm.expectRevert(_buildDirectCallDisallowedError(IStandardExecutor.executeBatch.selector)); account1.executeBatch(calls); } @@ -56,46 +56,46 @@ contract DirectCallsFromPluginTest is AccountTestBase { /* Positives */ /* -------------------------------------------------------------------------- */ - function test_Pass_DirectCallFromPluginPrank() external { - _installPlugin(); + function test_Pass_DirectCallFromModulePrank() external { + _installModule(); - vm.prank(address(_plugin)); + vm.prank(address(_module)); account1.execute(address(0), 0, ""); - assertTrue(_plugin.preHookRan()); - assertTrue(_plugin.postHookRan()); + assertTrue(_module.preHookRan()); + assertTrue(_module.postHookRan()); } - function test_Pass_DirectCallFromPluginCallback() external { - _installPlugin(); + function test_Pass_DirectCallFromModuleCallback() external { + _installModule(); - bytes memory encodedCall = abi.encodeCall(DirectCallPlugin.directCall, ()); + bytes memory encodedCall = abi.encodeCall(DirectCallModule.directCall, ()); vm.prank(address(entryPoint)); - bytes memory result = account1.execute(address(_plugin), 0, encodedCall); + bytes memory result = account1.execute(address(_module), 0, encodedCall); - assertTrue(_plugin.preHookRan()); - assertTrue(_plugin.postHookRan()); + assertTrue(_module.preHookRan()); + assertTrue(_module.postHookRan()); - // the directCall() function in the _plugin calls back into `execute()` with an encoded call back into the - // _plugin's getData() function. - assertEq(abi.decode(result, (bytes)), abi.encode(_plugin.getData())); + // the directCall() function in the _module calls back into `execute()` with an encoded call back into the + // _module's getData() function. + assertEq(abi.decode(result, (bytes)), abi.encode(_module.getData())); } - function test_Flow_DirectCallFromPluginSequence() external { + function test_Flow_DirectCallFromModuleSequence() external { // Install => Succeesfully call => uninstall => fail to call - _installPlugin(); + _installModule(); - vm.prank(address(_plugin)); + vm.prank(address(_module)); account1.execute(address(0), 0, ""); - assertTrue(_plugin.preHookRan()); - assertTrue(_plugin.postHookRan()); + assertTrue(_module.preHookRan()); + assertTrue(_module.postHookRan()); - _uninstallPlugin(); + _uninstallModule(); - vm.prank(address(_plugin)); + vm.prank(address(_module)); vm.expectRevert(_buildDirectCallDisallowedError(IStandardExecutor.execute.selector)); account1.execute(address(0), 0, ""); } @@ -104,27 +104,27 @@ contract DirectCallsFromPluginTest is AccountTestBase { /* Internals */ /* -------------------------------------------------------------------------- */ - function _installPlugin() internal { + function _installModule() internal { bytes4[] memory selectors = new bytes4[](1); selectors[0] = IStandardExecutor.execute.selector; ExecutionHook[] memory permissionHooks = new ExecutionHook[](1); bytes[] memory permissionHookInitDatas = new bytes[](1); - permissionHooks[0] = ExecutionHook({hookFunction: _pluginEntity, isPreHook: true, isPostHook: true}); + permissionHooks[0] = ExecutionHook({hookFunction: _moduleEntity, isPreHook: true, isPostHook: true}); bytes memory encodedPermissionHooks = abi.encode(permissionHooks, permissionHookInitDatas); vm.prank(address(entryPoint)); - ValidationConfig validationConfig = ValidationConfigLib.pack(_pluginEntity, false, false); + ValidationConfig validationConfig = ValidationConfigLib.pack(_moduleEntity, false, false); account1.installValidation(validationConfig, selectors, "", "", encodedPermissionHooks); } - function _uninstallPlugin() internal { + function _uninstallModule() internal { vm.prank(address(entryPoint)); - account1.uninstallValidation(_pluginEntity, "", abi.encode(new bytes[](0)), abi.encode(new bytes[](1))); + account1.uninstallValidation(_moduleEntity, "", abi.encode(new bytes[](0)), abi.encode(new bytes[](1))); } function _buildDirectCallDisallowedError(bytes4 selector) internal pure returns (bytes memory) { diff --git a/test/account/GlobalValidationTest.t.sol b/test/account/GlobalValidationTest.t.sol index 7ef7fdc5..b22d68eb 100644 --- a/test/account/GlobalValidationTest.t.sol +++ b/test/account/GlobalValidationTest.t.sol @@ -5,7 +5,7 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; +import {ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; @@ -27,7 +27,7 @@ contract GlobalValidationTest is AccountTestBase { vm.deal(address(account2), 100 ether); _signerValidation = - PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID); + ModuleEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID); ethRecipient = makeAddr("ethRecipient"); vm.deal(ethRecipient, 1 wei); diff --git a/test/account/MultiValidation.t.sol b/test/account/MultiValidation.t.sol index afb84f1a..772011bf 100644 --- a/test/account/MultiValidation.t.sol +++ b/test/account/MultiValidation.t.sol @@ -9,11 +9,11 @@ import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntry import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; +import {ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; -import {PluginEntity} from "../../src/interfaces/IPluginManager.sol"; +import {ModuleEntity} from "../../src/interfaces/IModuleManager.sol"; import {IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; -import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; +import {SingleSignerValidation} from "../../src/modules/validation/SingleSignerValidation.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; import {TEST_DEFAULT_VALIDATION_ENTITY_ID} from "../utils/TestConstants.sol"; @@ -43,9 +43,9 @@ contract MultiValidationTest is AccountTestBase { "" ); - PluginEntity[] memory validations = new PluginEntity[](2); - validations[0] = PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID); - validations[1] = PluginEntityLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID); + ModuleEntity[] memory validations = new ModuleEntity[](2); + validations[0] = ModuleEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID); + validations[1] = ModuleEntityLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID); bytes4[] memory selectors0 = account1.getSelectors(validations[0]); bytes4[] memory selectors1 = account1.getSelectors(validations[1]); @@ -72,7 +72,7 @@ contract MultiValidationTest is AccountTestBase { account1.executeWithAuthorization( abi.encodeCall(IStandardExecutor.execute, (address(0), 0, "")), _encodeSignature( - PluginEntityLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, "" + ModuleEntityLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, "" ) ); @@ -80,7 +80,7 @@ contract MultiValidationTest is AccountTestBase { account1.executeWithAuthorization( abi.encodeCall(IStandardExecutor.execute, (address(0), 0, "")), _encodeSignature( - PluginEntityLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, "" + ModuleEntityLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, "" ) ); } @@ -106,7 +106,7 @@ contract MultiValidationTest is AccountTestBase { bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); userOp.signature = _encodeSignature( - PluginEntityLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID), + ModuleEntityLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, abi.encodePacked(r, s, v) ); @@ -121,7 +121,7 @@ contract MultiValidationTest is AccountTestBase { userOp.nonce = 1; (v, r, s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); userOp.signature = _encodeSignature( - PluginEntityLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID), + ModuleEntityLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, abi.encodePacked(r, s, v) ); diff --git a/test/account/PerHookData.t.sol b/test/account/PerHookData.t.sol index c4405d38..15cefbd9 100644 --- a/test/account/PerHookData.t.sol +++ b/test/account/PerHookData.t.sol @@ -6,23 +6,23 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; +import {ModuleEntity, ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {Counter} from "../mocks/Counter.sol"; -import {MockAccessControlHookPlugin} from "../mocks/plugins/MockAccessControlHookPlugin.sol"; +import {MockAccessControlHookModule} from "../mocks/modules/MockAccessControlHookModule.sol"; import {CustomValidationTestBase} from "../utils/CustomValidationTestBase.sol"; contract PerHookDataTest is CustomValidationTestBase { using MessageHashUtils for bytes32; - MockAccessControlHookPlugin internal _accessControlHookPlugin; + MockAccessControlHookModule internal _accessControlHookModule; Counter internal _counter; function setUp() public { _counter = new Counter(); - _accessControlHookPlugin = new MockAccessControlHookPlugin(); + _accessControlHookModule = new MockAccessControlHookModule(); _customValidationSetup(); } @@ -217,8 +217,8 @@ contract PerHookDataTest is CustomValidationTestBase { vm.expectRevert( abi.encodeWithSelector( UpgradeableModularAccount.PreRuntimeValidationHookFailed.selector, - _accessControlHookPlugin, - uint32(MockAccessControlHookPlugin.EntityId.PRE_VALIDATION_HOOK), + _accessControlHookModule, + uint32(MockAccessControlHookModule.EntityId.PRE_VALIDATION_HOOK), abi.encodeWithSignature("Error(string)", "Proof doesn't match target") ) ); @@ -236,8 +236,8 @@ contract PerHookDataTest is CustomValidationTestBase { vm.expectRevert( abi.encodeWithSelector( UpgradeableModularAccount.PreRuntimeValidationHookFailed.selector, - _accessControlHookPlugin, - uint32(MockAccessControlHookPlugin.EntityId.PRE_VALIDATION_HOOK), + _accessControlHookModule, + uint32(MockAccessControlHookModule.EntityId.PRE_VALIDATION_HOOK), abi.encodeWithSignature("Error(string)", "Proof doesn't match target") ) ); @@ -278,8 +278,8 @@ contract PerHookDataTest is CustomValidationTestBase { vm.expectRevert( abi.encodeWithSelector( UpgradeableModularAccount.PreRuntimeValidationHookFailed.selector, - _accessControlHookPlugin, - uint32(MockAccessControlHookPlugin.EntityId.PRE_VALIDATION_HOOK), + _accessControlHookModule, + uint32(MockAccessControlHookModule.EntityId.PRE_VALIDATION_HOOK), abi.encodeWithSignature("Error(string)", "Target not allowed") ) ); @@ -330,13 +330,13 @@ contract PerHookDataTest is CustomValidationTestBase { internal virtual override - returns (PluginEntity, bool, bool, bytes4[] memory, bytes memory, bytes memory, bytes memory) + returns (ModuleEntity, bool, bool, bytes4[] memory, bytes memory, bytes memory, bytes memory) { - PluginEntity accessControlHook = PluginEntityLib.pack( - address(_accessControlHookPlugin), uint32(MockAccessControlHookPlugin.EntityId.PRE_VALIDATION_HOOK) + ModuleEntity accessControlHook = ModuleEntityLib.pack( + address(_accessControlHookModule), uint32(MockAccessControlHookModule.EntityId.PRE_VALIDATION_HOOK) ); - PluginEntity[] memory preValidationHooks = new PluginEntity[](1); + ModuleEntity[] memory preValidationHooks = new ModuleEntity[](1); preValidationHooks[0] = accessControlHook; bytes[] memory preValidationHookData = new bytes[](1); diff --git a/test/account/PermittedCallPermissions.t.sol b/test/account/PermittedCallPermissions.t.sol index 607c50b4..1c378e9e 100644 --- a/test/account/PermittedCallPermissions.t.sol +++ b/test/account/PermittedCallPermissions.t.sol @@ -3,43 +3,43 @@ pragma solidity ^0.8.19; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {PermittedCallerPlugin} from "../mocks/plugins/PermittedCallMocks.sol"; -import {ResultCreatorPlugin} from "../mocks/plugins/ReturnDataPluginMocks.sol"; +import {PermittedCallerModule} from "../mocks/modules/PermittedCallMocks.sol"; +import {ResultCreatorModule} from "../mocks/modules/ReturnDataModuleMocks.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; contract PermittedCallPermissionsTest is AccountTestBase { - ResultCreatorPlugin public resultCreatorPlugin; + ResultCreatorModule public resultCreatorModule; - PermittedCallerPlugin public permittedCallerPlugin; + PermittedCallerModule public permittedCallerModule; function setUp() public { _transferOwnershipToTest(); - resultCreatorPlugin = new ResultCreatorPlugin(); + resultCreatorModule = new ResultCreatorModule(); - // Initialize the permitted caller plugins, which will attempt to use the permissions system to authorize + // Initialize the permitted caller modules, which will attempt to use the permissions system to authorize // calls. - permittedCallerPlugin = new PermittedCallerPlugin(); + permittedCallerModule = new PermittedCallerModule(); - // Add the result creator plugin to the account - bytes32 resultCreatorManifestHash = keccak256(abi.encode(resultCreatorPlugin.pluginManifest())); + // Add the result creator module to the account + bytes32 resultCreatorManifestHash = keccak256(abi.encode(resultCreatorModule.moduleManifest())); vm.prank(address(entryPoint)); - account1.installPlugin({ - plugin: address(resultCreatorPlugin), + account1.installModule({ + module: address(resultCreatorModule), manifestHash: resultCreatorManifestHash, - pluginInstallData: "" + moduleInstallData: "" }); - // Add the permitted caller plugin to the account - bytes32 permittedCallerManifestHash = keccak256(abi.encode(permittedCallerPlugin.pluginManifest())); + // Add the permitted caller module to the account + bytes32 permittedCallerManifestHash = keccak256(abi.encode(permittedCallerModule.moduleManifest())); vm.prank(address(entryPoint)); - account1.installPlugin({ - plugin: address(permittedCallerPlugin), + account1.installModule({ + module: address(permittedCallerModule), manifestHash: permittedCallerManifestHash, - pluginInstallData: "" + moduleInstallData: "" }); } function test_permittedCall_Allowed() public { - bytes memory result = PermittedCallerPlugin(address(account1)).usePermittedCallAllowed(); + bytes memory result = PermittedCallerModule(address(account1)).usePermittedCallAllowed(); bytes32 actual = abi.decode(result, (bytes32)); assertEq(actual, keccak256("bar")); @@ -48,9 +48,9 @@ contract PermittedCallPermissionsTest is AccountTestBase { function test_permittedCall_NotAllowed() public { vm.expectRevert( abi.encodeWithSelector( - UpgradeableModularAccount.ValidationFunctionMissing.selector, ResultCreatorPlugin.bar.selector + UpgradeableModularAccount.ValidationFunctionMissing.selector, ResultCreatorModule.bar.selector ) ); - PermittedCallerPlugin(address(account1)).usePermittedCallNotAllowed(); + PermittedCallerModule(address(account1)).usePermittedCallNotAllowed(); } } diff --git a/test/account/SelfCallAuthorization.t.sol b/test/account/SelfCallAuthorization.t.sol index 743a0241..aae886eb 100644 --- a/test/account/SelfCallAuthorization.t.sol +++ b/test/account/SelfCallAuthorization.t.sol @@ -7,41 +7,41 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; +import {ModuleEntity, ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import {Call, IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; -import {ComprehensivePlugin} from "../mocks/plugins/ComprehensivePlugin.sol"; +import {ComprehensiveModule} from "../mocks/modules/ComprehensiveModule.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; contract SelfCallAuthorizationTest is AccountTestBase { - ComprehensivePlugin public comprehensivePlugin; + ComprehensiveModule public comprehensiveModule; - PluginEntity public comprehensivePluginValidation; + ModuleEntity public comprehensiveModuleValidation; function setUp() public { - // install the comprehensive plugin to get new exec functions with different validations configured. + // install the comprehensive module to get new exec functions with different validations configured. - comprehensivePlugin = new ComprehensivePlugin(); + comprehensiveModule = new ComprehensiveModule(); - bytes32 manifestHash = keccak256(abi.encode(comprehensivePlugin.pluginManifest())); + bytes32 manifestHash = keccak256(abi.encode(comprehensiveModule.moduleManifest())); vm.prank(address(entryPoint)); - account1.installPlugin(address(comprehensivePlugin), manifestHash, ""); + account1.installModule(address(comprehensiveModule), manifestHash, ""); - comprehensivePluginValidation = - PluginEntityLib.pack(address(comprehensivePlugin), uint32(ComprehensivePlugin.EntityId.VALIDATION)); + comprehensiveModuleValidation = + ModuleEntityLib.pack(address(comprehensiveModule), uint32(ComprehensiveModule.EntityId.VALIDATION)); } function test_selfCallFails_userOp() public { // Uses global validation _runUserOp( - abi.encodeCall(ComprehensivePlugin.foo, ()), + abi.encodeCall(ComprehensiveModule.foo, ()), abi.encodeWithSelector( IEntryPoint.FailedOpWithRevert.selector, 0, "AA23 reverted", abi.encodeWithSelector( - UpgradeableModularAccount.ValidationFunctionMissing.selector, ComprehensivePlugin.foo.selector + UpgradeableModularAccount.ValidationFunctionMissing.selector, ComprehensiveModule.foo.selector ) ) ); @@ -50,13 +50,13 @@ contract SelfCallAuthorizationTest is AccountTestBase { function test_selfCallFails_execUserOp() public { // Uses global validation _runUserOp( - abi.encodePacked(IAccountExecute.executeUserOp.selector, abi.encodeCall(ComprehensivePlugin.foo, ())), + abi.encodePacked(IAccountExecute.executeUserOp.selector, abi.encodeCall(ComprehensiveModule.foo, ())), abi.encodeWithSelector( IEntryPoint.FailedOpWithRevert.selector, 0, "AA23 reverted", abi.encodeWithSelector( - UpgradeableModularAccount.ValidationFunctionMissing.selector, ComprehensivePlugin.foo.selector + UpgradeableModularAccount.ValidationFunctionMissing.selector, ComprehensiveModule.foo.selector ) ) ); @@ -65,19 +65,19 @@ contract SelfCallAuthorizationTest is AccountTestBase { function test_selfCallFails_runtime() public { // Uses global validation _runtimeCall( - abi.encodeCall(ComprehensivePlugin.foo, ()), + abi.encodeCall(ComprehensiveModule.foo, ()), abi.encodeWithSelector( - UpgradeableModularAccount.ValidationFunctionMissing.selector, ComprehensivePlugin.foo.selector + UpgradeableModularAccount.ValidationFunctionMissing.selector, ComprehensiveModule.foo.selector ) ); } function test_selfCallPrivilegeEscalation_prevented_userOp() public { - // Using global validation, self-call bypasses custom validation needed for ComprehensivePlugin.foo + // Using global validation, self-call bypasses custom validation needed for ComprehensiveModule.foo _runUserOp( abi.encodeCall( UpgradeableModularAccount.execute, - (address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())) + (address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())) ), abi.encodeWithSelector( IEntryPoint.FailedOpWithRevert.selector, @@ -88,7 +88,7 @@ contract SelfCallAuthorizationTest is AccountTestBase { ); Call[] memory calls = new Call[](1); - calls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())); + calls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())); _runUserOp( abi.encodeCall(IStandardExecutor.executeBatch, (calls)), @@ -97,20 +97,20 @@ contract SelfCallAuthorizationTest is AccountTestBase { 0, "AA23 reverted", abi.encodeWithSelector( - UpgradeableModularAccount.ValidationFunctionMissing.selector, ComprehensivePlugin.foo.selector + UpgradeableModularAccount.ValidationFunctionMissing.selector, ComprehensiveModule.foo.selector ) ) ); } function test_selfCallPrivilegeEscalation_prevented_execUserOp() public { - // Using global validation, self-call bypasses custom validation needed for ComprehensivePlugin.foo + // Using global validation, self-call bypasses custom validation needed for ComprehensiveModule.foo _runUserOp( abi.encodePacked( IAccountExecute.executeUserOp.selector, abi.encodeCall( UpgradeableModularAccount.execute, - (address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())) + (address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())) ) ), abi.encodeWithSelector( @@ -122,7 +122,7 @@ contract SelfCallAuthorizationTest is AccountTestBase { ); Call[] memory calls = new Call[](1); - calls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())); + calls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())); _runUserOp( abi.encodePacked( @@ -133,29 +133,29 @@ contract SelfCallAuthorizationTest is AccountTestBase { 0, "AA23 reverted", abi.encodeWithSelector( - UpgradeableModularAccount.ValidationFunctionMissing.selector, ComprehensivePlugin.foo.selector + UpgradeableModularAccount.ValidationFunctionMissing.selector, ComprehensiveModule.foo.selector ) ) ); } function test_selfCallPrivilegeEscalation_prevented_runtime() public { - // Using global validation, self-call bypasses custom validation needed for ComprehensivePlugin.foo + // Using global validation, self-call bypasses custom validation needed for ComprehensiveModule.foo _runtimeCall( abi.encodeCall( UpgradeableModularAccount.execute, - (address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())) + (address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())) ), abi.encodeWithSelector(UpgradeableModularAccount.SelfCallRecursionDepthExceeded.selector) ); Call[] memory calls = new Call[](1); - calls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())); + calls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())); _runtimeExecBatchExpFail( calls, abi.encodeWithSelector( - UpgradeableModularAccount.ValidationFunctionMissing.selector, ComprehensivePlugin.foo.selector + UpgradeableModularAccount.ValidationFunctionMissing.selector, ComprehensiveModule.foo.selector ) ); } @@ -164,17 +164,17 @@ contract SelfCallAuthorizationTest is AccountTestBase { _enableBatchValidation(); Call[] memory calls = new Call[](2); - calls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())); - calls[1] = Call(address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())); + calls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())); + calls[1] = Call(address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())); - PackedUserOperation memory userOp = _generateUserOpWithComprehensivePluginValidation( + PackedUserOperation memory userOp = _generateUserOpWithComprehensiveModuleValidation( abi.encodeCall(IStandardExecutor.executeBatch, (calls)) ); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; - vm.expectCall(address(comprehensivePlugin), abi.encodeCall(ComprehensivePlugin.foo, ()), 2); + vm.expectCall(address(comprehensiveModule), abi.encodeCall(ComprehensiveModule.foo, ()), 2); entryPoint.handleOps(userOps, beneficiary); } @@ -182,10 +182,10 @@ contract SelfCallAuthorizationTest is AccountTestBase { _enableBatchValidation(); Call[] memory calls = new Call[](2); - calls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())); - calls[1] = Call(address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())); + calls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())); + calls[1] = Call(address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())); - PackedUserOperation memory userOp = _generateUserOpWithComprehensivePluginValidation( + PackedUserOperation memory userOp = _generateUserOpWithComprehensiveModuleValidation( abi.encodePacked( IAccountExecute.executeUserOp.selector, abi.encodeCall(IStandardExecutor.executeBatch, (calls)) ) @@ -194,7 +194,7 @@ contract SelfCallAuthorizationTest is AccountTestBase { PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; - vm.expectCall(address(comprehensivePlugin), abi.encodeCall(ComprehensivePlugin.foo, ()), 2); + vm.expectCall(address(comprehensiveModule), abi.encodeCall(ComprehensiveModule.foo, ()), 2); entryPoint.handleOps(userOps, beneficiary); } @@ -202,13 +202,13 @@ contract SelfCallAuthorizationTest is AccountTestBase { _enableBatchValidation(); Call[] memory calls = new Call[](2); - calls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())); - calls[1] = Call(address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())); + calls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())); + calls[1] = Call(address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())); - vm.expectCall(address(comprehensivePlugin), abi.encodeCall(ComprehensivePlugin.foo, ()), 2); + vm.expectCall(address(comprehensiveModule), abi.encodeCall(ComprehensiveModule.foo, ()), 2); account1.executeWithAuthorization( abi.encodeCall(IStandardExecutor.executeBatch, (calls)), - _encodeSignature(comprehensivePluginValidation, SELECTOR_ASSOCIATED_VALIDATION, "") + _encodeSignature(comprehensiveModuleValidation, SELECTOR_ASSOCIATED_VALIDATION, "") ); } @@ -216,12 +216,12 @@ contract SelfCallAuthorizationTest is AccountTestBase { _enableBatchValidation(); Call[] memory innerCalls = new Call[](1); - innerCalls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())); + innerCalls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())); Call[] memory outerCalls = new Call[](1); outerCalls[0] = Call(address(account1), 0, abi.encodeCall(IStandardExecutor.executeBatch, (innerCalls))); - PackedUserOperation memory userOp = _generateUserOpWithComprehensivePluginValidation( + PackedUserOperation memory userOp = _generateUserOpWithComprehensiveModuleValidation( abi.encodeCall(IStandardExecutor.executeBatch, (outerCalls)) ); @@ -243,12 +243,12 @@ contract SelfCallAuthorizationTest is AccountTestBase { _enableBatchValidation(); Call[] memory innerCalls = new Call[](1); - innerCalls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())); + innerCalls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())); Call[] memory outerCalls = new Call[](1); outerCalls[0] = Call(address(account1), 0, abi.encodeCall(IStandardExecutor.executeBatch, (innerCalls))); - PackedUserOperation memory userOp = _generateUserOpWithComprehensivePluginValidation( + PackedUserOperation memory userOp = _generateUserOpWithComprehensiveModuleValidation( abi.encodePacked( IAccountExecute.executeUserOp.selector, abi.encodeCall(IStandardExecutor.executeBatch, (outerCalls)) @@ -273,7 +273,7 @@ contract SelfCallAuthorizationTest is AccountTestBase { _enableBatchValidation(); Call[] memory innerCalls = new Call[](1); - innerCalls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensivePlugin.foo, ())); + innerCalls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())); Call[] memory outerCalls = new Call[](1); outerCalls[0] = Call(address(account1), 0, abi.encodeCall(IStandardExecutor.executeBatch, (innerCalls))); @@ -281,12 +281,12 @@ contract SelfCallAuthorizationTest is AccountTestBase { vm.expectRevert(abi.encodeWithSelector(UpgradeableModularAccount.SelfCallRecursionDepthExceeded.selector)); account1.executeWithAuthorization( abi.encodeCall(IStandardExecutor.executeBatch, (outerCalls)), - _encodeSignature(comprehensivePluginValidation, SELECTOR_ASSOCIATED_VALIDATION, "") + _encodeSignature(comprehensiveModuleValidation, SELECTOR_ASSOCIATED_VALIDATION, "") ); } function _enableBatchValidation() internal { - // Extend ComprehensivePlugin's validation function to also validate `executeBatch`, to allow the + // Extend ComprehensiveModule's validation function to also validate `executeBatch`, to allow the // self-call. bytes4[] memory selectors = new bytes4[](1); @@ -296,13 +296,13 @@ contract SelfCallAuthorizationTest is AccountTestBase { account1.executeWithAuthorization( abi.encodeCall( UpgradeableModularAccount.installValidation, - (ValidationConfigLib.pack(comprehensivePluginValidation, false, false), selectors, "", "", "") + (ValidationConfigLib.pack(comprehensiveModuleValidation, false, false), selectors, "", "", "") ), _encodeSignature(_signerValidation, GLOBAL_VALIDATION, "") ); } - function _generateUserOpWithComprehensivePluginValidation(bytes memory callData) + function _generateUserOpWithComprehensiveModuleValidation(bytes memory callData) internal view returns (PackedUserOperation memory) @@ -318,9 +318,9 @@ contract SelfCallAuthorizationTest is AccountTestBase { gasFees: _encodeGas(1, 1), paymasterAndData: hex"", signature: _encodeSignature( - comprehensivePluginValidation, + comprehensiveModuleValidation, SELECTOR_ASSOCIATED_VALIDATION, - // Comprehensive plugin's validation function doesn't actually check anything, so we don't need to + // Comprehensive module's validation function doesn't actually check anything, so we don't need to // sign anything. "" ) diff --git a/test/account/UpgradeableModularAccount.t.sol b/test/account/UpgradeableModularAccount.t.sol index 8b77332f..76a1c4f0 100644 --- a/test/account/UpgradeableModularAccount.t.sol +++ b/test/account/UpgradeableModularAccount.t.sol @@ -9,21 +9,21 @@ import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; -import {PluginManagerInternals} from "../../src/account/PluginManagerInternals.sol"; +import {ModuleManagerInternals} from "../../src/account/ModuleManagerInternals.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {IAccountLoupe} from "../../src/interfaces/IAccountLoupe.sol"; -import {PluginManifest} from "../../src/interfaces/IPlugin.sol"; -import {IPluginManager} from "../../src/interfaces/IPluginManager.sol"; +import {ModuleManifest} from "../../src/interfaces/IModule.sol"; +import {IModuleManager} from "../../src/interfaces/IModuleManager.sol"; import {Call} from "../../src/interfaces/IStandardExecutor.sol"; -import {TokenReceiverPlugin} from "../../src/plugins/TokenReceiverPlugin.sol"; -import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; +import {TokenReceiverModule} from "../../src/modules/TokenReceiverModule.sol"; +import {SingleSignerValidation} from "../../src/modules/validation/SingleSignerValidation.sol"; import {Counter} from "../mocks/Counter.sol"; -import {MockPlugin} from "../mocks/MockPlugin.sol"; -import {ComprehensivePlugin} from "../mocks/plugins/ComprehensivePlugin.sol"; +import {MockModule} from "../mocks/MockModule.sol"; +import {ComprehensiveModule} from "../mocks/modules/ComprehensiveModule.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; import {TEST_DEFAULT_VALIDATION_ENTITY_ID} from "../utils/TestConstants.sol"; @@ -31,7 +31,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { using ECDSA for bytes32; using MessageHashUtils for bytes32; - TokenReceiverPlugin public tokenReceiverPlugin; + TokenReceiverModule public tokenReceiverModule; // A separate account and owner that isn't deployed yet, used to test initcode address public owner2; @@ -40,14 +40,14 @@ contract UpgradeableModularAccountTest is AccountTestBase { address public ethRecipient; Counter public counter; - PluginManifest internal _manifest; + ModuleManifest internal _manifest; - event PluginInstalled(address indexed plugin, bytes32 manifestHash); - event PluginUninstalled(address indexed plugin, bool indexed callbacksSucceeded); + event ModuleInstalled(address indexed module, bytes32 manifestHash); + event ModuleUninstalled(address indexed module, bool indexed callbacksSucceeded); event ReceivedCall(bytes msgData, uint256 msgValue); function setUp() public { - tokenReceiverPlugin = _deployTokenReceiverPlugin(); + tokenReceiverModule = _deployTokenReceiverModule(); (owner2, owner2Key) = makeAddrAndKey("owner2"); @@ -236,163 +236,163 @@ contract UpgradeableModularAccountTest is AccountTestBase { assertEq(ethRecipient.balance, 2 wei); } - function test_installPlugin() public { + function test_installModule() public { vm.startPrank(address(entryPoint)); - bytes32 manifestHash = keccak256(abi.encode(tokenReceiverPlugin.pluginManifest())); + bytes32 manifestHash = keccak256(abi.encode(tokenReceiverModule.moduleManifest())); vm.expectEmit(true, true, true, true); - emit PluginInstalled(address(tokenReceiverPlugin), manifestHash); - IPluginManager(account1).installPlugin({ - plugin: address(tokenReceiverPlugin), + emit ModuleInstalled(address(tokenReceiverModule), manifestHash); + IModuleManager(account1).installModule({ + module: address(tokenReceiverModule), manifestHash: manifestHash, - pluginInstallData: abi.encode(uint48(1 days)) + moduleInstallData: abi.encode(uint48(1 days)) }); - address[] memory plugins = IAccountLoupe(account1).getInstalledPlugins(); - assertEq(plugins.length, 1); - assertEq(plugins[0], address(tokenReceiverPlugin)); + address[] memory modules = IAccountLoupe(account1).getInstalledModules(); + assertEq(modules.length, 1); + assertEq(modules[0], address(tokenReceiverModule)); } - function test_installPlugin_PermittedCallSelectorNotInstalled() public { + function test_installModule_PermittedCallSelectorNotInstalled() public { vm.startPrank(address(entryPoint)); - PluginManifest memory m; + ModuleManifest memory m; - MockPlugin mockPluginWithBadPermittedExec = new MockPlugin(m); - bytes32 manifestHash = keccak256(abi.encode(mockPluginWithBadPermittedExec.pluginManifest())); + MockModule mockModuleWithBadPermittedExec = new MockModule(m); + bytes32 manifestHash = keccak256(abi.encode(mockModuleWithBadPermittedExec.moduleManifest())); - IPluginManager(account1).installPlugin({ - plugin: address(mockPluginWithBadPermittedExec), + IModuleManager(account1).installModule({ + module: address(mockModuleWithBadPermittedExec), manifestHash: manifestHash, - pluginInstallData: "" + moduleInstallData: "" }); } - function test_installPlugin_invalidManifest() public { + function test_installModule_invalidManifest() public { vm.startPrank(address(entryPoint)); - vm.expectRevert(abi.encodeWithSelector(PluginManagerInternals.InvalidPluginManifest.selector)); - IPluginManager(account1).installPlugin({ - plugin: address(tokenReceiverPlugin), + vm.expectRevert(abi.encodeWithSelector(ModuleManagerInternals.InvalidModuleManifest.selector)); + IModuleManager(account1).installModule({ + module: address(tokenReceiverModule), manifestHash: bytes32(0), - pluginInstallData: abi.encode(uint48(1 days)) + moduleInstallData: abi.encode(uint48(1 days)) }); } - function test_installPlugin_interfaceNotSupported() public { + function test_installModule_interfaceNotSupported() public { vm.startPrank(address(entryPoint)); - address badPlugin = address(1); + address badModule = address(1); vm.expectRevert( - abi.encodeWithSelector(PluginManagerInternals.PluginInterfaceNotSupported.selector, address(badPlugin)) + abi.encodeWithSelector(ModuleManagerInternals.ModuleInterfaceNotSupported.selector, address(badModule)) ); - IPluginManager(account1).installPlugin({ - plugin: address(badPlugin), + IModuleManager(account1).installModule({ + module: address(badModule), manifestHash: bytes32(0), - pluginInstallData: "" + moduleInstallData: "" }); } - function test_installPlugin_alreadyInstalled() public { + function test_installModule_alreadyInstalled() public { vm.startPrank(address(entryPoint)); - bytes32 manifestHash = keccak256(abi.encode(tokenReceiverPlugin.pluginManifest())); - IPluginManager(account1).installPlugin({ - plugin: address(tokenReceiverPlugin), + bytes32 manifestHash = keccak256(abi.encode(tokenReceiverModule.moduleManifest())); + IModuleManager(account1).installModule({ + module: address(tokenReceiverModule), manifestHash: manifestHash, - pluginInstallData: abi.encode(uint48(1 days)) + moduleInstallData: abi.encode(uint48(1 days)) }); vm.expectRevert( abi.encodeWithSelector( - PluginManagerInternals.PluginAlreadyInstalled.selector, address(tokenReceiverPlugin) + ModuleManagerInternals.ModuleAlreadyInstalled.selector, address(tokenReceiverModule) ) ); - IPluginManager(account1).installPlugin({ - plugin: address(tokenReceiverPlugin), + IModuleManager(account1).installModule({ + module: address(tokenReceiverModule), manifestHash: manifestHash, - pluginInstallData: abi.encode(uint48(1 days)) + moduleInstallData: abi.encode(uint48(1 days)) }); } - function test_uninstallPlugin_default() public { + function test_uninstallModule_default() public { vm.startPrank(address(entryPoint)); - ComprehensivePlugin plugin = new ComprehensivePlugin(); - bytes32 manifestHash = keccak256(abi.encode(plugin.pluginManifest())); - IPluginManager(account1).installPlugin({ - plugin: address(plugin), + ComprehensiveModule module = new ComprehensiveModule(); + bytes32 manifestHash = keccak256(abi.encode(module.moduleManifest())); + IModuleManager(account1).installModule({ + module: address(module), manifestHash: manifestHash, - pluginInstallData: "" + moduleInstallData: "" }); vm.expectEmit(true, true, true, true); - emit PluginUninstalled(address(plugin), true); - IPluginManager(account1).uninstallPlugin({plugin: address(plugin), config: "", pluginUninstallData: ""}); - address[] memory plugins = IAccountLoupe(account1).getInstalledPlugins(); - assertEq(plugins.length, 0); + emit ModuleUninstalled(address(module), true); + IModuleManager(account1).uninstallModule({module: address(module), config: "", moduleUninstallData: ""}); + address[] memory modules = IAccountLoupe(account1).getInstalledModules(); + assertEq(modules.length, 0); } - function test_uninstallPlugin_manifestParameter() public { + function test_uninstallModule_manifestParameter() public { vm.startPrank(address(entryPoint)); - ComprehensivePlugin plugin = new ComprehensivePlugin(); - bytes memory serializedManifest = abi.encode(plugin.pluginManifest()); + ComprehensiveModule module = new ComprehensiveModule(); + bytes memory serializedManifest = abi.encode(module.moduleManifest()); bytes32 manifestHash = keccak256(serializedManifest); - IPluginManager(account1).installPlugin({ - plugin: address(plugin), + IModuleManager(account1).installModule({ + module: address(module), manifestHash: manifestHash, - pluginInstallData: "" + moduleInstallData: "" }); vm.expectEmit(true, true, true, true); - emit PluginUninstalled(address(plugin), true); - IPluginManager(account1).uninstallPlugin({ - plugin: address(plugin), + emit ModuleUninstalled(address(module), true); + IModuleManager(account1).uninstallModule({ + module: address(module), config: serializedManifest, - pluginUninstallData: "" + moduleUninstallData: "" }); - address[] memory plugins = IAccountLoupe(account1).getInstalledPlugins(); - assertEq(plugins.length, 0); + address[] memory modules = IAccountLoupe(account1).getInstalledModules(); + assertEq(modules.length, 0); } - function test_uninstallPlugin_invalidManifestFails() public { + function test_uninstallModule_invalidManifestFails() public { vm.startPrank(address(entryPoint)); - ComprehensivePlugin plugin = new ComprehensivePlugin(); - bytes memory serializedManifest = abi.encode(plugin.pluginManifest()); + ComprehensiveModule module = new ComprehensiveModule(); + bytes memory serializedManifest = abi.encode(module.moduleManifest()); bytes32 manifestHash = keccak256(serializedManifest); - IPluginManager(account1).installPlugin({ - plugin: address(plugin), + IModuleManager(account1).installModule({ + module: address(module), manifestHash: manifestHash, - pluginInstallData: "" + moduleInstallData: "" }); // Attempt to uninstall with a blank _manifest - PluginManifest memory blankManifest; + ModuleManifest memory blankManifest; - vm.expectRevert(abi.encodeWithSelector(PluginManagerInternals.InvalidPluginManifest.selector)); - IPluginManager(account1).uninstallPlugin({ - plugin: address(plugin), + vm.expectRevert(abi.encodeWithSelector(ModuleManagerInternals.InvalidModuleManifest.selector)); + IModuleManager(account1).uninstallModule({ + module: address(module), config: abi.encode(blankManifest), - pluginUninstallData: "" + moduleUninstallData: "" }); - address[] memory plugins = IAccountLoupe(account1).getInstalledPlugins(); - assertEq(plugins.length, 1); - assertEq(plugins[0], address(plugin)); + address[] memory modules = IAccountLoupe(account1).getInstalledModules(); + assertEq(modules.length, 1); + assertEq(modules[0], address(module)); } - function _installPluginWithExecHooks() internal returns (MockPlugin plugin) { + function _installModuleWithExecHooks() internal returns (MockModule module) { vm.startPrank(address(entryPoint)); - plugin = new MockPlugin(_manifest); - bytes32 manifestHash = keccak256(abi.encode(plugin.pluginManifest())); + module = new MockModule(_manifest); + bytes32 manifestHash = keccak256(abi.encode(module.moduleManifest())); - IPluginManager(account1).installPlugin({ - plugin: address(plugin), + IModuleManager(account1).installModule({ + module: address(module), manifestHash: manifestHash, - pluginInstallData: "" + moduleInstallData: "" }); vm.stopPrank(); diff --git a/test/account/ValidationIntersection.t.sol b/test/account/ValidationIntersection.t.sol index 3bdaa1b6..82a8fa7f 100644 --- a/test/account/ValidationIntersection.t.sol +++ b/test/account/ValidationIntersection.t.sol @@ -4,65 +4,65 @@ pragma solidity ^0.8.19; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; +import {ModuleEntity, ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import { - MockBaseUserOpValidationPlugin, - MockUserOpValidation1HookPlugin, - MockUserOpValidation2HookPlugin, - MockUserOpValidationPlugin -} from "../mocks/plugins/ValidationPluginMocks.sol"; + MockBaseUserOpValidationModule, + MockUserOpValidation1HookModule, + MockUserOpValidation2HookModule, + MockUserOpValidationModule +} from "../mocks/modules/ValidationModuleMocks.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; contract ValidationIntersectionTest is AccountTestBase { uint256 internal constant _SIG_VALIDATION_FAILED = 1; - MockUserOpValidationPlugin public noHookPlugin; - MockUserOpValidation1HookPlugin public oneHookPlugin; - MockUserOpValidation2HookPlugin public twoHookPlugin; + MockUserOpValidationModule public noHookModule; + MockUserOpValidation1HookModule public oneHookModule; + MockUserOpValidation2HookModule public twoHookModule; - PluginEntity public noHookValidation; - PluginEntity public oneHookValidation; - PluginEntity public twoHookValidation; + ModuleEntity public noHookValidation; + ModuleEntity public oneHookValidation; + ModuleEntity public twoHookValidation; function setUp() public { - noHookPlugin = new MockUserOpValidationPlugin(); - oneHookPlugin = new MockUserOpValidation1HookPlugin(); - twoHookPlugin = new MockUserOpValidation2HookPlugin(); + noHookModule = new MockUserOpValidationModule(); + oneHookModule = new MockUserOpValidation1HookModule(); + twoHookModule = new MockUserOpValidation2HookModule(); - noHookValidation = PluginEntityLib.pack({ - addr: address(noHookPlugin), - entityId: uint32(MockBaseUserOpValidationPlugin.EntityId.USER_OP_VALIDATION) + noHookValidation = ModuleEntityLib.pack({ + addr: address(noHookModule), + entityId: uint32(MockBaseUserOpValidationModule.EntityId.USER_OP_VALIDATION) }); - oneHookValidation = PluginEntityLib.pack({ - addr: address(oneHookPlugin), - entityId: uint32(MockBaseUserOpValidationPlugin.EntityId.USER_OP_VALIDATION) + oneHookValidation = ModuleEntityLib.pack({ + addr: address(oneHookModule), + entityId: uint32(MockBaseUserOpValidationModule.EntityId.USER_OP_VALIDATION) }); - twoHookValidation = PluginEntityLib.pack({ - addr: address(twoHookPlugin), - entityId: uint32(MockBaseUserOpValidationPlugin.EntityId.USER_OP_VALIDATION) + twoHookValidation = ModuleEntityLib.pack({ + addr: address(twoHookModule), + entityId: uint32(MockBaseUserOpValidationModule.EntityId.USER_OP_VALIDATION) }); vm.startPrank(address(entryPoint)); - account1.installPlugin({ - plugin: address(noHookPlugin), - manifestHash: keccak256(abi.encode(noHookPlugin.pluginManifest())), - pluginInstallData: "" + account1.installModule({ + module: address(noHookModule), + manifestHash: keccak256(abi.encode(noHookModule.moduleManifest())), + moduleInstallData: "" }); - account1.installPlugin({ - plugin: address(oneHookPlugin), - manifestHash: keccak256(abi.encode(oneHookPlugin.pluginManifest())), - pluginInstallData: "" + account1.installModule({ + module: address(oneHookModule), + manifestHash: keccak256(abi.encode(oneHookModule.moduleManifest())), + moduleInstallData: "" }); // TODO: change with new install flow // temporary fix to add the pre-validation hook - PluginEntity[] memory preValidationHooks = new PluginEntity[](1); - preValidationHooks[0] = PluginEntityLib.pack({ - addr: address(oneHookPlugin), - entityId: uint32(MockBaseUserOpValidationPlugin.EntityId.PRE_VALIDATION_HOOK_1) + ModuleEntity[] memory preValidationHooks = new ModuleEntity[](1); + preValidationHooks[0] = ModuleEntityLib.pack({ + addr: address(oneHookModule), + entityId: uint32(MockBaseUserOpValidationModule.EntityId.PRE_VALIDATION_HOOK_1) }); bytes[] memory installDatas = new bytes[](1); account1.installValidation( @@ -72,20 +72,20 @@ contract ValidationIntersectionTest is AccountTestBase { abi.encode(preValidationHooks, installDatas), bytes("") ); - account1.installPlugin({ - plugin: address(twoHookPlugin), - manifestHash: keccak256(abi.encode(twoHookPlugin.pluginManifest())), - pluginInstallData: "" + account1.installModule({ + module: address(twoHookModule), + manifestHash: keccak256(abi.encode(twoHookModule.moduleManifest())), + moduleInstallData: "" }); // temporary fix to add the pre-validation hook - preValidationHooks = new PluginEntity[](2); - preValidationHooks[0] = PluginEntityLib.pack({ - addr: address(twoHookPlugin), - entityId: uint32(MockBaseUserOpValidationPlugin.EntityId.PRE_VALIDATION_HOOK_1) + preValidationHooks = new ModuleEntity[](2); + preValidationHooks[0] = ModuleEntityLib.pack({ + addr: address(twoHookModule), + entityId: uint32(MockBaseUserOpValidationModule.EntityId.PRE_VALIDATION_HOOK_1) }); - preValidationHooks[1] = PluginEntityLib.pack({ - addr: address(twoHookPlugin), - entityId: uint32(MockBaseUserOpValidationPlugin.EntityId.PRE_VALIDATION_HOOK_2) + preValidationHooks[1] = ModuleEntityLib.pack({ + addr: address(twoHookModule), + entityId: uint32(MockBaseUserOpValidationModule.EntityId.PRE_VALIDATION_HOOK_2) }); installDatas = new bytes[](2); account1.installValidation( @@ -99,10 +99,10 @@ contract ValidationIntersectionTest is AccountTestBase { } function testFuzz_validationIntersect_single(uint256 validationData) public { - noHookPlugin.setValidationData(validationData); + noHookModule.setValidationData(validationData); PackedUserOperation memory userOp; - userOp.callData = bytes.concat(noHookPlugin.foo.selector); + userOp.callData = bytes.concat(noHookModule.foo.selector); userOp.signature = _encodeSignature(noHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); @@ -113,13 +113,13 @@ contract ValidationIntersectionTest is AccountTestBase { } function test_validationIntersect_authorizer_sigfail_validationFunction() public { - oneHookPlugin.setValidationData( + oneHookModule.setValidationData( _SIG_VALIDATION_FAILED, 0 // returns OK ); PackedUserOperation memory userOp; - userOp.callData = bytes.concat(oneHookPlugin.bar.selector); + userOp.callData = bytes.concat(oneHookModule.bar.selector); userOp.signature = _encodeSignature(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); @@ -131,13 +131,13 @@ contract ValidationIntersectionTest is AccountTestBase { } function test_validationIntersect_authorizer_sigfail_hook() public { - oneHookPlugin.setValidationData( + oneHookModule.setValidationData( 0, // returns OK _SIG_VALIDATION_FAILED ); PackedUserOperation memory userOp; - userOp.callData = bytes.concat(oneHookPlugin.bar.selector); + userOp.callData = bytes.concat(oneHookModule.bar.selector); userOp.signature = _encodeSignature(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); @@ -155,12 +155,12 @@ contract ValidationIntersectionTest is AccountTestBase { uint48 start2 = uint48(15); uint48 end2 = uint48(25); - oneHookPlugin.setValidationData( + oneHookModule.setValidationData( _packValidationRes(address(0), start1, end1), _packValidationRes(address(0), start2, end2) ); PackedUserOperation memory userOp; - userOp.callData = bytes.concat(oneHookPlugin.bar.selector); + userOp.callData = bytes.concat(oneHookModule.bar.selector); userOp.signature = _encodeSignature(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); @@ -177,12 +177,12 @@ contract ValidationIntersectionTest is AccountTestBase { uint48 start2 = uint48(15); uint48 end2 = uint48(25); - oneHookPlugin.setValidationData( + oneHookModule.setValidationData( _packValidationRes(address(0), start2, end2), _packValidationRes(address(0), start1, end1) ); PackedUserOperation memory userOp; - userOp.callData = bytes.concat(oneHookPlugin.bar.selector); + userOp.callData = bytes.concat(oneHookModule.bar.selector); userOp.signature = _encodeSignature(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); @@ -195,14 +195,14 @@ contract ValidationIntersectionTest is AccountTestBase { function test_validationIntersect_revert_unexpectedAuthorizer() public { address badAuthorizer = makeAddr("badAuthorizer"); - oneHookPlugin.setValidationData( + oneHookModule.setValidationData( 0, // returns OK uint256(uint160(badAuthorizer)) // returns an aggregator, which preValidation hooks are not allowed to // do. ); PackedUserOperation memory userOp; - userOp.callData = bytes.concat(oneHookPlugin.bar.selector); + userOp.callData = bytes.concat(oneHookModule.bar.selector); userOp.signature = _encodeSignature(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); @@ -210,8 +210,8 @@ contract ValidationIntersectionTest is AccountTestBase { vm.expectRevert( abi.encodeWithSelector( UpgradeableModularAccount.UnexpectedAggregator.selector, - address(oneHookPlugin), - MockBaseUserOpValidationPlugin.EntityId.PRE_VALIDATION_HOOK_1, + address(oneHookModule), + MockBaseUserOpValidationModule.EntityId.PRE_VALIDATION_HOOK_1, badAuthorizer ) ); @@ -221,13 +221,13 @@ contract ValidationIntersectionTest is AccountTestBase { function test_validationIntersect_validAuthorizer() public { address goodAuthorizer = makeAddr("goodAuthorizer"); - oneHookPlugin.setValidationData( + oneHookModule.setValidationData( uint256(uint160(goodAuthorizer)), // returns a valid aggregator 0 // returns OK ); PackedUserOperation memory userOp; - userOp.callData = bytes.concat(oneHookPlugin.bar.selector); + userOp.callData = bytes.concat(oneHookModule.bar.selector); userOp.signature = _encodeSignature(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); @@ -246,12 +246,12 @@ contract ValidationIntersectionTest is AccountTestBase { address goodAuthorizer = makeAddr("goodAuthorizer"); - oneHookPlugin.setValidationData( + oneHookModule.setValidationData( _packValidationRes(goodAuthorizer, start1, end1), _packValidationRes(address(0), start2, end2) ); PackedUserOperation memory userOp; - userOp.callData = bytes.concat(oneHookPlugin.bar.selector); + userOp.callData = bytes.concat(oneHookModule.bar.selector); userOp.signature = _encodeSignature(oneHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); @@ -268,14 +268,14 @@ contract ValidationIntersectionTest is AccountTestBase { uint48 start2 = uint48(15); uint48 end2 = uint48(25); - twoHookPlugin.setValidationData( + twoHookModule.setValidationData( 0, // returns OK _packValidationRes(address(0), start1, end1), _packValidationRes(address(0), start2, end2) ); PackedUserOperation memory userOp; - userOp.callData = bytes.concat(twoHookPlugin.baz.selector); + userOp.callData = bytes.concat(twoHookModule.baz.selector); userOp.signature = _encodeSignature(twoHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); @@ -286,14 +286,14 @@ contract ValidationIntersectionTest is AccountTestBase { } function test_validationIntersect_multiplePreValidationHooksSigFail() public { - twoHookPlugin.setValidationData( + twoHookModule.setValidationData( 0, // returns OK 0, // returns OK _SIG_VALIDATION_FAILED ); PackedUserOperation memory userOp; - userOp.callData = bytes.concat(twoHookPlugin.baz.selector); + userOp.callData = bytes.concat(twoHookModule.baz.selector); userOp.signature = _encodeSignature(twoHookValidation, SELECTOR_ASSOCIATED_VALIDATION, ""); bytes32 uoHash = entryPoint.getUserOpHash(userOp); diff --git a/test/libraries/KnowSelectors.t.sol b/test/libraries/KnowSelectors.t.sol index 7ce0910d..48bd17e9 100644 --- a/test/libraries/KnowSelectors.t.sol +++ b/test/libraries/KnowSelectors.t.sol @@ -6,7 +6,7 @@ import {IPaymaster} from "@eth-infinitism/account-abstraction/interfaces/IPaymas import {Test} from "forge-std/Test.sol"; import {KnownSelectors} from "../../src/helpers/KnownSelectors.sol"; -import {IPlugin} from "../../src/interfaces/IPlugin.sol"; +import {IModule} from "../../src/interfaces/IModule.sol"; contract KnownSelectorsTest is Test { function test_isNativeFunction() public { @@ -17,7 +17,7 @@ contract KnownSelectorsTest is Test { assertTrue(KnownSelectors.isErc4337Function(IPaymaster.validatePaymasterUserOp.selector)); } - function test_isIPluginFunction() public { - assertTrue(KnownSelectors.isIPluginFunction(IPlugin.pluginMetadata.selector)); + function test_isIModuleFunction() public { + assertTrue(KnownSelectors.isIModuleFunction(IModule.moduleMetadata.selector)); } } diff --git a/test/libraries/PluginEntityLib.t.sol b/test/libraries/ModuleEntityLib.t.sol similarity index 56% rename from test/libraries/PluginEntityLib.t.sol rename to test/libraries/ModuleEntityLib.t.sol index c93e1b61..205e53b9 100644 --- a/test/libraries/PluginEntityLib.t.sol +++ b/test/libraries/ModuleEntityLib.t.sol @@ -3,29 +3,29 @@ pragma solidity ^0.8.19; import {Test} from "forge-std/Test.sol"; -import {PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; -import {PluginEntity} from "../../src/interfaces/IPluginManager.sol"; +import {ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; +import {ModuleEntity} from "../../src/interfaces/IModuleManager.sol"; -contract PluginEntityLibTest is Test { - using PluginEntityLib for PluginEntity; +contract ModuleEntityLibTest is Test { + using ModuleEntityLib for ModuleEntity; - function testFuzz_pluginEntity_packing(address addr, uint32 entityId) public { + function testFuzz_moduleEntity_packing(address addr, uint32 entityId) public { // console.log("addr: ", addr); // console.log("entityId: ", vm.toString(entityId)); - PluginEntity fr = PluginEntityLib.pack(addr, entityId); - // console.log("packed: ", vm.toString(PluginEntity.unwrap(fr))); - (address addr2, uint32 entityId2) = PluginEntityLib.unpack(fr); + 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_pluginEntity_operators(PluginEntity a, PluginEntity b) public { + function testFuzz_moduleEntity_operators(ModuleEntity a, ModuleEntity b) public { assertTrue(a.eq(a)); assertTrue(b.eq(b)); - if (PluginEntity.unwrap(a) == PluginEntity.unwrap(b)) { + if (ModuleEntity.unwrap(a) == ModuleEntity.unwrap(b)) { assertTrue(a.eq(b)); assertTrue(b.eq(a)); assertFalse(a.notEq(b)); diff --git a/test/mocks/MockPlugin.sol b/test/mocks/MockModule.sol similarity index 75% rename from test/mocks/MockPlugin.sol rename to test/mocks/MockModule.sol index ef79c018..c6866bd4 100644 --- a/test/mocks/MockPlugin.sol +++ b/test/mocks/MockModule.sol @@ -4,10 +4,10 @@ pragma solidity ^0.8.19; import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; import {IExecutionHook} from "../../src/interfaces/IExecutionHook.sol"; -import {IPlugin, PluginManifest, PluginMetadata} from "../../src/interfaces/IPlugin.sol"; +import {IModule, ModuleManifest, ModuleMetadata} from "../../src/interfaces/IModule.sol"; import {IValidation} from "../../src/interfaces/IValidation.sol"; -contract MockPlugin is ERC165 { +contract MockModule is ERC165 { // It's super inefficient to hold the entire abi-encoded manifest in storage, but this is fine since it's // just a mock. Note that the reason we do this is to allow copying the entire contents of the manifest // into storage in a single line, since solidity fails to compile with memory -> storage copying of nested @@ -17,41 +17,41 @@ contract MockPlugin is ERC165 { // struct ManifestAssociatedFunction memory[] memory to storage not yet supported. bytes internal _manifest; - string internal constant _NAME = "Mock Plugin Modifiable"; + string internal constant _NAME = "Mock Module Modifiable"; string internal constant _VERSION = "1.0.0"; string internal constant _AUTHOR = "ERC-6900 Authors"; event ReceivedCall(bytes msgData, uint256 msgValue); // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Plugin interface functions ┃ + // ┃ Module interface functions ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - constructor(PluginManifest memory _pluginManifest) { - _manifest = abi.encode(_pluginManifest); + constructor(ModuleManifest memory _moduleManifest) { + _manifest = abi.encode(_moduleManifest); } - function _getManifest() internal view returns (PluginManifest memory) { - PluginManifest memory m = abi.decode(_manifest, (PluginManifest)); + function _getManifest() internal view returns (ModuleManifest memory) { + ModuleManifest memory m = abi.decode(_manifest, (ModuleManifest)); return m; } - function _castToPure(function() internal view returns (PluginManifest memory) fnIn) + function _castToPure(function() internal view returns (ModuleManifest memory) fnIn) internal pure - returns (function() internal pure returns (PluginManifest memory) fnOut) + returns (function() internal pure returns (ModuleManifest memory) fnOut) { assembly ("memory-safe") { fnOut := fnIn } } - function pluginManifest() external pure returns (PluginManifest memory) { + function moduleManifest() external pure returns (ModuleManifest memory) { return _castToPure(_getManifest)(); } - function pluginMetadata() external pure returns (PluginMetadata memory) { - PluginMetadata memory metadata; + function moduleMetadata() external pure returns (ModuleMetadata memory) { + ModuleMetadata memory metadata; metadata.name = _NAME; metadata.version = _VERSION; metadata.author = _AUTHOR; @@ -65,14 +65,14 @@ contract MockPlugin is ERC165 { /// /// This function call must use less than 30 000 gas. /// - /// Supporting the IPlugin interface is a requirement for plugin installation. This is also used + /// Supporting the IModule interface is a requirement for module installation. This is also used /// by the modular account to prevent standard execution functions `execute` and `executeBatch` from - /// making calls to plugins. + /// making calls to modules. /// @param interfaceId The interface ID to check for support. /// @return True if the contract supports `interfaceId`. function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { - return interfaceId == type(IPlugin).interfaceId || super.supportsInterface(interfaceId); + return interfaceId == type(IModule).interfaceId || super.supportsInterface(interfaceId); } receive() external payable {} diff --git a/test/mocks/SingleSignerFactoryFixture.sol b/test/mocks/SingleSignerFactoryFixture.sol index b751d440..98dfc0b8 100644 --- a/test/mocks/SingleSignerFactoryFixture.sol +++ b/test/mocks/SingleSignerFactoryFixture.sol @@ -7,7 +7,7 @@ import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; -import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; +import {SingleSignerValidation} from "../../src/modules/validation/SingleSignerValidation.sol"; import {OptimizedTest} from "../utils/OptimizedTest.sol"; import {TEST_DEFAULT_VALIDATION_ENTITY_ID} from "../utils/TestConstants.sol"; @@ -45,17 +45,17 @@ contract SingleSignerFactoryFixture is OptimizedTest { // short circuit if exists if (addr.code.length == 0) { - bytes memory pluginInstallData = abi.encode(TEST_DEFAULT_VALIDATION_ENTITY_ID, owner); + bytes memory moduleInstallData = abi.encode(TEST_DEFAULT_VALIDATION_ENTITY_ID, owner); // not necessary to check return addr since next call will fail if so new ERC1967Proxy{salt: getSalt(owner, salt)}(address(accountImplementation), ""); - // point proxy to actual implementation and init plugins + // point proxy to actual implementation and init modules UpgradeableModularAccount(payable(addr)).initializeWithValidation( ValidationConfigLib.pack( address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID, true, true ), new bytes4[](0), - pluginInstallData, + moduleInstallData, "", "" ); diff --git a/test/mocks/plugins/ComprehensivePlugin.sol b/test/mocks/modules/ComprehensiveModule.sol similarity index 89% rename from test/mocks/plugins/ComprehensivePlugin.sol rename to test/mocks/modules/ComprehensiveModule.sol index e4233cf2..bb73b5db 100644 --- a/test/mocks/plugins/ComprehensivePlugin.sol +++ b/test/mocks/modules/ComprehensiveModule.sol @@ -8,16 +8,16 @@ import { ManifestExecutionFunction, ManifestExecutionHook, ManifestValidation, - PluginManifest, - PluginMetadata -} from "../../../src/interfaces/IPlugin.sol"; -import {PluginManifest} from "../../../src/interfaces/IPlugin.sol"; + ModuleManifest, + ModuleMetadata +} from "../../../src/interfaces/IModule.sol"; +import {ModuleManifest} from "../../../src/interfaces/IModule.sol"; import {IValidation} from "../../../src/interfaces/IValidation.sol"; import {IValidationHook} from "../../../src/interfaces/IValidationHook.sol"; -import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; +import {BaseModule} from "../../../src/modules/BaseModule.sol"; -contract ComprehensivePlugin is IValidation, IValidationHook, IExecutionHook, BasePlugin { +contract ComprehensiveModule is IValidation, IValidationHook, IExecutionHook, BaseModule { enum EntityId { PRE_VALIDATION_HOOK_1, PRE_VALIDATION_HOOK_2, @@ -28,7 +28,7 @@ contract ComprehensivePlugin is IValidation, IValidationHook, IExecutionHook, Ba SIG_VALIDATION } - string internal constant _NAME = "Comprehensive Plugin"; + string internal constant _NAME = "Comprehensive Module"; string internal constant _VERSION = "1.0.0"; string internal constant _AUTHOR = "ERC-6900 Authors"; @@ -39,7 +39,7 @@ contract ComprehensivePlugin is IValidation, IValidationHook, IExecutionHook, Ba function foo() external {} // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Plugin interface functions ┃ + // ┃ Module interface functions ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ function onInstall(bytes calldata) external override {} @@ -130,8 +130,8 @@ contract ComprehensivePlugin is IValidation, IValidationHook, IExecutionHook, Ba revert NotImplemented(); } - function pluginManifest() external pure override returns (PluginManifest memory) { - PluginManifest memory manifest; + function moduleManifest() external pure override returns (ModuleManifest memory) { + ModuleManifest memory manifest; manifest.executionFunctions = new ManifestExecutionFunction[](1); manifest.executionFunctions[0] = ManifestExecutionFunction({ @@ -174,8 +174,8 @@ contract ComprehensivePlugin is IValidation, IValidationHook, IExecutionHook, Ba return manifest; } - function pluginMetadata() external pure virtual override returns (PluginMetadata memory) { - PluginMetadata memory metadata; + function moduleMetadata() external pure virtual override returns (ModuleMetadata memory) { + ModuleMetadata memory metadata; metadata.name = _NAME; metadata.version = _VERSION; metadata.author = _AUTHOR; diff --git a/test/mocks/plugins/DirectCallPlugin.sol b/test/mocks/modules/DirectCallModule.sol similarity index 77% rename from test/mocks/plugins/DirectCallPlugin.sol rename to test/mocks/modules/DirectCallModule.sol index 7ccda4f0..9e0f611d 100644 --- a/test/mocks/plugins/DirectCallPlugin.sol +++ b/test/mocks/modules/DirectCallModule.sol @@ -2,12 +2,12 @@ pragma solidity ^0.8.19; import {IExecutionHook} from "../../../src/interfaces/IExecutionHook.sol"; -import {PluginManifest, PluginMetadata} from "../../../src/interfaces/IPlugin.sol"; +import {ModuleManifest, ModuleMetadata} from "../../../src/interfaces/IModule.sol"; import {IStandardExecutor} from "../../../src/interfaces/IStandardExecutor.sol"; -import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; +import {BaseModule} from "../../../src/modules/BaseModule.sol"; -contract DirectCallPlugin is BasePlugin, IExecutionHook { +contract DirectCallModule is BaseModule, IExecutionHook { bool public preHookRan = false; bool public postHookRan = false; @@ -15,7 +15,7 @@ contract DirectCallPlugin is BasePlugin, IExecutionHook { function onUninstall(bytes calldata) external override {} - function pluginManifest() external pure override returns (PluginManifest memory) {} + function moduleManifest() external pure override returns (ModuleManifest memory) {} function directCall() external returns (bytes memory) { return IStandardExecutor(msg.sender).execute(address(this), 0, abi.encodeCall(this.getData, ())); @@ -25,7 +25,7 @@ contract DirectCallPlugin is BasePlugin, IExecutionHook { return hex"04546b"; } - function pluginMetadata() external pure override returns (PluginMetadata memory) {} + function moduleMetadata() external pure override returns (ModuleMetadata memory) {} function preExecutionHook(uint32, address sender, uint256, bytes calldata) external diff --git a/test/mocks/plugins/MockAccessControlHookPlugin.sol b/test/mocks/modules/MockAccessControlHookModule.sol similarity index 85% rename from test/mocks/plugins/MockAccessControlHookPlugin.sol rename to test/mocks/modules/MockAccessControlHookModule.sol index 8fa49f8e..e3f47b86 100644 --- a/test/mocks/plugins/MockAccessControlHookPlugin.sol +++ b/test/mocks/modules/MockAccessControlHookModule.sol @@ -3,17 +3,17 @@ pragma solidity ^0.8.25; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; -import {PluginManifest, PluginMetadata} from "../../../src/interfaces/IPlugin.sol"; +import {ModuleManifest, ModuleMetadata} from "../../../src/interfaces/IModule.sol"; import {IStandardExecutor} from "../../../src/interfaces/IStandardExecutor.sol"; import {IValidationHook} from "../../../src/interfaces/IValidationHook.sol"; -import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; +import {BaseModule} from "../../../src/modules/BaseModule.sol"; -// A pre validaiton hook plugin that uses per-hook data. +// A pre validaiton hook module that uses per-hook data. // This example enforces that the target of an `execute` call must only be the previously specified address. // This is just a mock - it does not enforce this over `executeBatch` and other methods of making calls, and should // not be used in production.. -contract MockAccessControlHookPlugin is IValidationHook, BasePlugin { +contract MockAccessControlHookModule is IValidationHook, BaseModule { enum EntityId { PRE_VALIDATION_HOOK } @@ -73,7 +73,7 @@ contract MockAccessControlHookPlugin is IValidationHook, BasePlugin { revert NotImplemented(); } - function pluginMetadata() external pure override returns (PluginMetadata memory) {} + function moduleMetadata() external pure override returns (ModuleMetadata memory) {} - function pluginManifest() external pure override returns (PluginManifest memory) {} + function moduleManifest() external pure override returns (ModuleManifest memory) {} } diff --git a/test/mocks/plugins/PermittedCallMocks.sol b/test/mocks/modules/PermittedCallMocks.sol similarity index 53% rename from test/mocks/plugins/PermittedCallMocks.sol rename to test/mocks/modules/PermittedCallMocks.sol index 77548225..bea02e78 100644 --- a/test/mocks/plugins/PermittedCallMocks.sol +++ b/test/mocks/modules/PermittedCallMocks.sol @@ -1,18 +1,18 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {ManifestExecutionFunction, PluginManifest, PluginMetadata} from "../../../src/interfaces/IPlugin.sol"; +import {ManifestExecutionFunction, ModuleManifest, ModuleMetadata} from "../../../src/interfaces/IModule.sol"; -import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; -import {ResultCreatorPlugin} from "./ReturnDataPluginMocks.sol"; +import {BaseModule} from "../../../src/modules/BaseModule.sol"; +import {ResultCreatorModule} from "./ReturnDataModuleMocks.sol"; -contract PermittedCallerPlugin is BasePlugin { +contract PermittedCallerModule is BaseModule { function onInstall(bytes calldata) external override {} function onUninstall(bytes calldata) external override {} - function pluginManifest() external pure override returns (PluginManifest memory) { - PluginManifest memory manifest; + function moduleManifest() external pure override returns (ModuleManifest memory) { + ModuleManifest memory manifest; manifest.executionFunctions = new ManifestExecutionFunction[](2); manifest.executionFunctions[0].executionSelector = this.usePermittedCallAllowed.selector; @@ -25,15 +25,15 @@ contract PermittedCallerPlugin is BasePlugin { return manifest; } - function pluginMetadata() external pure override returns (PluginMetadata memory) {} + function moduleMetadata() external pure override returns (ModuleMetadata memory) {} - // The manifest requested access to use the plugin-defined method "foo" + // The manifest requested access to use the module-defined method "foo" function usePermittedCallAllowed() external view returns (bytes memory) { - return abi.encode(ResultCreatorPlugin(msg.sender).foo()); + return abi.encode(ResultCreatorModule(msg.sender).foo()); } - // The manifest has not requested access to use the plugin-defined method "bar", so this should revert. + // The manifest has not requested access to use the module-defined method "bar", so this should revert. function usePermittedCallNotAllowed() external view returns (bytes memory) { - return abi.encode(ResultCreatorPlugin(msg.sender).bar()); + return abi.encode(ResultCreatorModule(msg.sender).bar()); } } diff --git a/test/mocks/plugins/ReturnDataPluginMocks.sol b/test/mocks/modules/ReturnDataModuleMocks.sol similarity index 84% rename from test/mocks/plugins/ReturnDataPluginMocks.sol rename to test/mocks/modules/ReturnDataModuleMocks.sol index 96a77ffe..8fe3241d 100644 --- a/test/mocks/plugins/ReturnDataPluginMocks.sol +++ b/test/mocks/modules/ReturnDataModuleMocks.sol @@ -6,14 +6,14 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import { ManifestExecutionFunction, ManifestValidation, - PluginManifest, - PluginMetadata -} from "../../../src/interfaces/IPlugin.sol"; + ModuleManifest, + ModuleMetadata +} from "../../../src/interfaces/IModule.sol"; import {IStandardExecutor} from "../../../src/interfaces/IStandardExecutor.sol"; import {IValidation} from "../../../src/interfaces/IValidation.sol"; -import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; +import {BaseModule} from "../../../src/modules/BaseModule.sol"; contract RegularResultContract { function foo() external pure returns (bytes32) { @@ -25,7 +25,7 @@ contract RegularResultContract { } } -contract ResultCreatorPlugin is BasePlugin { +contract ResultCreatorModule is BaseModule { function onInstall(bytes calldata) external override {} function onUninstall(bytes calldata) external override {} @@ -38,8 +38,8 @@ contract ResultCreatorPlugin is BasePlugin { return keccak256("foo"); } - function pluginManifest() external pure override returns (PluginManifest memory) { - PluginManifest memory manifest; + function moduleManifest() external pure override returns (ModuleManifest memory) { + ModuleManifest memory manifest; manifest.executionFunctions = new ManifestExecutionFunction[](2); manifest.executionFunctions[0] = ManifestExecutionFunction({ @@ -56,16 +56,16 @@ contract ResultCreatorPlugin is BasePlugin { return manifest; } - function pluginMetadata() external pure override returns (PluginMetadata memory) {} + function moduleMetadata() external pure override returns (ModuleMetadata memory) {} } -contract ResultConsumerPlugin is BasePlugin, IValidation { - ResultCreatorPlugin public immutable RESULT_CREATOR; +contract ResultConsumerModule is BaseModule, IValidation { + ResultCreatorModule public immutable RESULT_CREATOR; RegularResultContract public immutable REGULAR_RESULT_CONTRACT; error NotAuthorized(); - constructor(ResultCreatorPlugin _resultCreator, RegularResultContract _regularResultContract) { + constructor(ResultCreatorModule _resultCreator, RegularResultContract _regularResultContract) { RESULT_CREATOR = _resultCreator; REGULAR_RESULT_CONTRACT = _regularResultContract; } @@ -93,7 +93,7 @@ contract ResultConsumerPlugin is BasePlugin, IValidation { // Check the return data through the fallback function checkResultFallback(bytes32 expected) external view returns (bool) { // This result should be allowed based on the manifest permission request - bytes32 actual = ResultCreatorPlugin(msg.sender).foo(); + bytes32 actual = ResultCreatorModule(msg.sender).foo(); return actual == expected; } @@ -116,8 +116,8 @@ contract ResultConsumerPlugin is BasePlugin, IValidation { function onUninstall(bytes calldata) external override {} - function pluginManifest() external pure override returns (PluginManifest memory) { - PluginManifest memory manifest; + function moduleManifest() external pure override returns (ModuleManifest memory) { + ModuleManifest memory manifest; // todo: this is the exact workflow that would benefit from a "permiteed call" setup in the manifest. bytes4[] memory validationSelectors = new bytes4[](1); @@ -146,5 +146,5 @@ contract ResultConsumerPlugin is BasePlugin, IValidation { return manifest; } - function pluginMetadata() external pure override returns (PluginMetadata memory) {} + function moduleMetadata() external pure override returns (ModuleMetadata memory) {} } diff --git a/test/mocks/plugins/ValidationPluginMocks.sol b/test/mocks/modules/ValidationModuleMocks.sol similarity index 86% rename from test/mocks/plugins/ValidationPluginMocks.sol rename to test/mocks/modules/ValidationModuleMocks.sol index 3cfe1d09..ed152af2 100644 --- a/test/mocks/plugins/ValidationPluginMocks.sol +++ b/test/mocks/modules/ValidationModuleMocks.sol @@ -6,14 +6,14 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import { ManifestExecutionFunction, ManifestValidation, - PluginManifest, - PluginMetadata -} from "../../../src/interfaces/IPlugin.sol"; + ModuleManifest, + ModuleMetadata +} from "../../../src/interfaces/IModule.sol"; import {IValidation} from "../../../src/interfaces/IValidation.sol"; import {IValidationHook} from "../../../src/interfaces/IValidationHook.sol"; -import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; +import {BaseModule} from "../../../src/modules/BaseModule.sol"; -abstract contract MockBaseUserOpValidationPlugin is IValidation, IValidationHook, BasePlugin { +abstract contract MockBaseUserOpValidationModule is IValidation, IValidationHook, BaseModule { enum EntityId { USER_OP_VALIDATION, PRE_VALIDATION_HOOK_1, @@ -25,7 +25,7 @@ abstract contract MockBaseUserOpValidationPlugin is IValidation, IValidationHook uint256 internal _preUserOpValidationHook2Data; // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Plugin interface functions ┃ + // ┃ Module interface functions ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ function onInstall(bytes calldata) external override {} @@ -68,7 +68,7 @@ abstract contract MockBaseUserOpValidationPlugin is IValidation, IValidationHook } // Empty stubs - function pluginMetadata() external pure override returns (PluginMetadata memory) {} + function moduleMetadata() external pure override returns (ModuleMetadata memory) {} function preRuntimeValidationHook(uint32, address, uint256, bytes calldata, bytes calldata) external @@ -87,7 +87,7 @@ abstract contract MockBaseUserOpValidationPlugin is IValidation, IValidationHook } } -contract MockUserOpValidationPlugin is MockBaseUserOpValidationPlugin { +contract MockUserOpValidationModule is MockBaseUserOpValidationModule { function setValidationData(uint256 userOpValidationFunctionData) external { _userOpValidationFunctionData = userOpValidationFunctionData; } @@ -99,11 +99,11 @@ contract MockUserOpValidationPlugin is MockBaseUserOpValidationPlugin { function foo() external {} // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Plugin interface functions ┃ + // ┃ Module interface functions ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - function pluginManifest() external pure override returns (PluginManifest memory) { - PluginManifest memory manifest; + function moduleManifest() external pure override returns (ModuleManifest memory) { + ModuleManifest memory manifest; manifest.executionFunctions = new ManifestExecutionFunction[](1); manifest.executionFunctions[0] = ManifestExecutionFunction({ @@ -127,7 +127,7 @@ contract MockUserOpValidationPlugin is MockBaseUserOpValidationPlugin { } } -contract MockUserOpValidation1HookPlugin is MockBaseUserOpValidationPlugin { +contract MockUserOpValidation1HookModule is MockBaseUserOpValidationModule { function setValidationData(uint256 userOpValidationFunctionData, uint256 preUserOpValidationHook1Data) external { @@ -142,11 +142,11 @@ contract MockUserOpValidation1HookPlugin is MockBaseUserOpValidationPlugin { function bar() external {} // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Plugin interface functions ┃ + // ┃ Module interface functions ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - function pluginManifest() external pure override returns (PluginManifest memory) { - PluginManifest memory manifest; + function moduleManifest() external pure override returns (ModuleManifest memory) { + ModuleManifest memory manifest; manifest.executionFunctions = new ManifestExecutionFunction[](1); manifest.executionFunctions[0] = ManifestExecutionFunction({ @@ -170,7 +170,7 @@ contract MockUserOpValidation1HookPlugin is MockBaseUserOpValidationPlugin { } } -contract MockUserOpValidation2HookPlugin is MockBaseUserOpValidationPlugin { +contract MockUserOpValidation2HookModule is MockBaseUserOpValidationModule { function setValidationData( uint256 userOpValidationFunctionData, uint256 preUserOpValidationHook1Data, @@ -188,11 +188,11 @@ contract MockUserOpValidation2HookPlugin is MockBaseUserOpValidationPlugin { function baz() external {} // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - // ┃ Plugin interface functions ┃ + // ┃ Module interface functions ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - function pluginManifest() external pure override returns (PluginManifest memory) { - PluginManifest memory manifest; + function moduleManifest() external pure override returns (ModuleManifest memory) { + ModuleManifest memory manifest; manifest.executionFunctions = new ManifestExecutionFunction[](1); manifest.executionFunctions[0] = ManifestExecutionFunction({ diff --git a/test/plugin/ERC20TokenLimitPlugin.t.sol b/test/module/ERC20TokenLimitModule.t.sol similarity index 75% rename from test/plugin/ERC20TokenLimitPlugin.t.sol rename to test/module/ERC20TokenLimitModule.t.sol index cf7b422a..e88a4527 100644 --- a/test/plugin/ERC20TokenLimitPlugin.t.sol +++ b/test/module/ERC20TokenLimitModule.t.sol @@ -6,33 +6,33 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {PluginEntity} from "../../src/helpers/PluginEntityLib.sol"; +import {ModuleEntity} from "../../src/helpers/ModuleEntityLib.sol"; -import {PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; +import {ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; -import {PluginManifest} from "../../src/interfaces/IPlugin.sol"; +import {ModuleManifest} from "../../src/interfaces/IModule.sol"; import {Call, IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; -import {ERC20TokenLimitPlugin} from "../../src/plugins/ERC20TokenLimitPlugin.sol"; -import {MockPlugin} from "../mocks/MockPlugin.sol"; +import {ERC20TokenLimitModule} from "../../src/modules/ERC20TokenLimitModule.sol"; +import {MockModule} from "../mocks/MockModule.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; -contract ERC20TokenLimitPluginTest is AccountTestBase { +contract ERC20TokenLimitModuleTest is AccountTestBase { address public recipient = address(1); MockERC20 public erc20; address payable public bundler = payable(address(2)); - PluginManifest internal _m; - MockPlugin public validationPlugin = new MockPlugin(_m); - PluginEntity public validationFunction; + ModuleManifest internal _m; + MockModule public validationModule = new MockModule(_m); + ModuleEntity public validationFunction; UpgradeableModularAccount public acct; - ERC20TokenLimitPlugin public plugin = new ERC20TokenLimitPlugin(); + ERC20TokenLimitModule public module = new ERC20TokenLimitModule(); uint256 public spendLimit = 10 ether; function setUp() public { - // Set up a validator with hooks from the erc20 spend limit plugin attached + // Set up a validator with hooks from the erc20 spend limit module attached acct = factory.createAccount(address(this), 0); erc20 = new MockERC20(); @@ -40,7 +40,7 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { ExecutionHook[] memory permissionHooks = new ExecutionHook[](1); permissionHooks[0] = ExecutionHook({ - hookFunction: PluginEntityLib.pack(address(plugin), 0), + hookFunction: ModuleEntityLib.pack(address(module), 0), isPreHook: true, isPostHook: false }); @@ -49,22 +49,22 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { uint256[] memory limits = new uint256[](1); limits[0] = spendLimit; - ERC20TokenLimitPlugin.ERC20SpendLimit[] memory limit = new ERC20TokenLimitPlugin.ERC20SpendLimit[](1); - limit[0] = ERC20TokenLimitPlugin.ERC20SpendLimit({token: address(erc20), limits: limits}); + ERC20TokenLimitModule.ERC20SpendLimit[] memory limit = new ERC20TokenLimitModule.ERC20SpendLimit[](1); + limit[0] = ERC20TokenLimitModule.ERC20SpendLimit({token: address(erc20), limits: limits}); bytes[] memory permissionInitDatas = new bytes[](1); permissionInitDatas[0] = abi.encode(uint8(0), limit); vm.prank(address(acct)); acct.installValidation( - ValidationConfigLib.pack(address(validationPlugin), 0, true, true), + ValidationConfigLib.pack(address(validationModule), 0, true, true), new bytes4[](0), new bytes(0), new bytes(0), abi.encode(permissionHooks, permissionInitDatas) ); - validationFunction = PluginEntityLib.pack(address(validationPlugin), 0); + validationFunction = ModuleEntityLib.pack(address(validationModule), 0); } function _getPackedUO(bytes memory callData) internal view returns (PackedUserOperation memory uo) { @@ -77,7 +77,7 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { preVerificationGas: 200_000, gasFees: bytes32(uint256(uint128(0))), paymasterAndData: "", - signature: _encodeSignature(PluginEntityLib.pack(address(validationPlugin), 0), 1, "") + signature: _encodeSignature(ModuleEntityLib.pack(address(validationModule), 0), 1, "") }); } @@ -90,9 +90,9 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { function test_userOp_executeLimit() public { vm.startPrank(address(entryPoint)); - assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether); + assertEq(module.limits(0, address(erc20), address(acct)), 10 ether); acct.executeUserOp(_getPackedUO(_getExecuteWithSpend(5 ether)), bytes32(0)); - assertEq(plugin.limits(0, address(erc20), address(acct)), 5 ether); + assertEq(module.limits(0, address(erc20), address(acct)), 5 ether); } function test_userOp_executeBatchLimit() public { @@ -108,9 +108,9 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { }); vm.startPrank(address(entryPoint)); - assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether); + assertEq(module.limits(0, address(erc20), address(acct)), 10 ether); acct.executeUserOp(_getPackedUO(abi.encodeCall(IStandardExecutor.executeBatch, (calls))), bytes32(0)); - assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether - 6 ether - 100_001); + assertEq(module.limits(0, address(erc20), address(acct)), 10 ether - 6 ether - 100_001); } function test_userOp_executeBatch_approveAndTransferLimit() public { @@ -126,9 +126,9 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { }); vm.startPrank(address(entryPoint)); - assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether); + assertEq(module.limits(0, address(erc20), address(acct)), 10 ether); acct.executeUserOp(_getPackedUO(abi.encodeCall(IStandardExecutor.executeBatch, (calls))), bytes32(0)); - assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether - 6 ether - 100_001); + assertEq(module.limits(0, address(erc20), address(acct)), 10 ether - 6 ether - 100_001); } function test_userOp_executeBatch_approveAndTransferLimit_fail() public { @@ -144,21 +144,21 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { }); vm.startPrank(address(entryPoint)); - assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether); + assertEq(module.limits(0, address(erc20), address(acct)), 10 ether); PackedUserOperation[] memory uos = new PackedUserOperation[](1); uos[0] = _getPackedUO(abi.encodeCall(IStandardExecutor.executeBatch, (calls))); entryPoint.handleOps(uos, bundler); // no spend consumed - assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether); + assertEq(module.limits(0, address(erc20), address(acct)), 10 ether); } function test_runtime_executeLimit() public { - assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether); + assertEq(module.limits(0, address(erc20), address(acct)), 10 ether); acct.executeWithAuthorization( _getExecuteWithSpend(5 ether), - _encodeSignature(PluginEntityLib.pack(address(validationPlugin), 0), 1, "") + _encodeSignature(ModuleEntityLib.pack(address(validationModule), 0), 1, "") ); - assertEq(plugin.limits(0, address(erc20), address(acct)), 5 ether); + assertEq(module.limits(0, address(erc20), address(acct)), 5 ether); } function test_runtime_executeBatchLimit() public { @@ -173,11 +173,11 @@ contract ERC20TokenLimitPluginTest is AccountTestBase { data: abi.encodeCall(IERC20.approve, (recipient, 5 ether + 100_000)) }); - assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether); + assertEq(module.limits(0, address(erc20), address(acct)), 10 ether); acct.executeWithAuthorization( abi.encodeCall(IStandardExecutor.executeBatch, (calls)), - _encodeSignature(PluginEntityLib.pack(address(validationPlugin), 0), 1, "") + _encodeSignature(ModuleEntityLib.pack(address(validationModule), 0), 1, "") ); - assertEq(plugin.limits(0, address(erc20), address(acct)), 10 ether - 6 ether - 100_001); + assertEq(module.limits(0, address(erc20), address(acct)), 10 ether - 6 ether - 100_001); } } diff --git a/test/plugin/NativeTokenLimitPlugin.t.sol b/test/module/NativeTokenLimitModule.t.sol similarity index 74% rename from test/plugin/NativeTokenLimitPlugin.t.sol rename to test/module/NativeTokenLimitModule.t.sol index 0ae9ae40..a5e20389 100644 --- a/test/plugin/NativeTokenLimitPlugin.t.sol +++ b/test/module/NativeTokenLimitModule.t.sol @@ -4,43 +4,43 @@ pragma solidity ^0.8.19; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {PluginEntity} from "../../src/helpers/PluginEntityLib.sol"; +import {ModuleEntity} from "../../src/helpers/ModuleEntityLib.sol"; -import {PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; +import {ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; -import {PluginManifest} from "../../src/interfaces/IPlugin.sol"; +import {ModuleManifest} from "../../src/interfaces/IModule.sol"; import {Call, IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; -import {NativeTokenLimitPlugin} from "../../src/plugins/NativeTokenLimitPlugin.sol"; -import {MockPlugin} from "../mocks/MockPlugin.sol"; +import {NativeTokenLimitModule} from "../../src/modules/NativeTokenLimitModule.sol"; +import {MockModule} from "../mocks/MockModule.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; -contract NativeTokenLimitPluginTest is AccountTestBase { +contract NativeTokenLimitModuleTest is AccountTestBase { address public recipient = address(1); address payable public bundler = payable(address(2)); - PluginManifest internal _m; - MockPlugin public validationPlugin = new MockPlugin(_m); - PluginEntity public validationFunction; + ModuleManifest internal _m; + MockModule public validationModule = new MockModule(_m); + ModuleEntity public validationFunction; UpgradeableModularAccount public acct; - NativeTokenLimitPlugin public plugin = new NativeTokenLimitPlugin(); + NativeTokenLimitModule public module = new NativeTokenLimitModule(); uint256 public spendLimit = 10 ether; function setUp() public { - // Set up a validator with hooks from the gas spend limit plugin attached + // Set up a validator with hooks from the gas spend limit module attached acct = factory.createAccount(address(this), 0); vm.deal(address(acct), 10 ether); - PluginEntity[] memory preValidationHooks = new PluginEntity[](1); - preValidationHooks[0] = PluginEntityLib.pack(address(plugin), 0); + ModuleEntity[] memory preValidationHooks = new ModuleEntity[](1); + preValidationHooks[0] = ModuleEntityLib.pack(address(module), 0); ExecutionHook[] memory permissionHooks = new ExecutionHook[](1); permissionHooks[0] = ExecutionHook({ - hookFunction: PluginEntityLib.pack(address(plugin), 0), + hookFunction: ModuleEntityLib.pack(address(module), 0), isPreHook: true, isPostHook: false }); @@ -56,14 +56,14 @@ contract NativeTokenLimitPluginTest is AccountTestBase { vm.prank(address(acct)); acct.installValidation( - ValidationConfigLib.pack(address(validationPlugin), 0, true, true), + ValidationConfigLib.pack(address(validationModule), 0, true, true), new bytes4[](0), new bytes(0), abi.encode(preValidationHooks, preValHooksInitDatas), abi.encode(permissionHooks, permissionInitDatas) ); - validationFunction = PluginEntityLib.pack(address(validationPlugin), 0); + validationFunction = ModuleEntityLib.pack(address(validationModule), 0); } function _getExecuteWithValue(uint256 value) internal view returns (bytes memory) { @@ -84,7 +84,7 @@ contract NativeTokenLimitPluginTest is AccountTestBase { preVerificationGas: gas3, gasFees: bytes32(uint256(uint128(gasPrice))), paymasterAndData: "", - signature: _encodeSignature(PluginEntityLib.pack(address(validationPlugin), 0), 1, "") + signature: _encodeSignature(ModuleEntityLib.pack(address(validationModule), 0), 1, "") }); } @@ -92,17 +92,17 @@ contract NativeTokenLimitPluginTest is AccountTestBase { vm.startPrank(address(entryPoint)); // uses 10e - 200000 of gas - assertEq(plugin.limits(0, address(acct)), 10 ether); + assertEq(module.limits(0, address(acct)), 10 ether); uint256 result = acct.validateUserOp( _getPackedUO(100_000, 100_000, 10 ether - 400_000, 1, _getExecuteWithValue(0)), bytes32(0), 0 ); - assertEq(plugin.limits(0, address(acct)), 200_000); + assertEq(module.limits(0, address(acct)), 200_000); uint256 expected = uint256(type(uint48).max) << 160; assertEq(result, expected); // uses 200k + 1 wei of gas - vm.expectRevert(NativeTokenLimitPlugin.ExceededNativeTokenLimit.selector); + vm.expectRevert(NativeTokenLimitModule.ExceededNativeTokenLimit.selector); result = acct.validateUserOp(_getPackedUO(100_000, 100_000, 1, 1, _getExecuteWithValue(0)), bytes32(0), 0); } @@ -110,18 +110,18 @@ contract NativeTokenLimitPluginTest is AccountTestBase { vm.startPrank(address(entryPoint)); // uses 5e of native tokens - assertEq(plugin.limits(0, address(acct)), 10 ether); + assertEq(module.limits(0, address(acct)), 10 ether); acct.executeUserOp(_getPackedUO(0, 0, 0, 0, _getExecuteWithValue(5 ether)), bytes32(0)); - assertEq(plugin.limits(0, address(acct)), 5 ether); + assertEq(module.limits(0, address(acct)), 5 ether); // uses 5e + 1wei of native tokens vm.expectRevert( abi.encodePacked( UpgradeableModularAccount.PreExecHookReverted.selector, abi.encode( - address(plugin), + address(module), uint8(0), - abi.encodePacked(NativeTokenLimitPlugin.ExceededNativeTokenLimit.selector) + abi.encodePacked(NativeTokenLimitModule.ExceededNativeTokenLimit.selector) ) ) ); @@ -135,21 +135,21 @@ contract NativeTokenLimitPluginTest is AccountTestBase { calls[2] = Call({target: recipient, value: 5 ether + 100_000, data: ""}); vm.startPrank(address(entryPoint)); - assertEq(plugin.limits(0, address(acct)), 10 ether); + assertEq(module.limits(0, address(acct)), 10 ether); acct.executeUserOp( _getPackedUO(0, 0, 0, 0, abi.encodeCall(IStandardExecutor.executeBatch, (calls))), bytes32(0) ); - assertEq(plugin.limits(0, address(acct)), 10 ether - 6 ether - 100_001); + assertEq(module.limits(0, address(acct)), 10 ether - 6 ether - 100_001); assertEq(recipient.balance, 6 ether + 100_001); } function test_userOp_combinedExecLimit_success() public { - assertEq(plugin.limits(0, address(acct)), 10 ether); + assertEq(module.limits(0, address(acct)), 10 ether); PackedUserOperation[] memory uos = new PackedUserOperation[](1); uos[0] = _getPackedUO(200_000, 200_000, 200_000, 1, _getExecuteWithValue(5 ether)); entryPoint.handleOps(uos, bundler); - assertEq(plugin.limits(0, address(acct)), 5 ether - 600_000); + assertEq(module.limits(0, address(acct)), 5 ether - 600_000); assertEq(recipient.balance, 5 ether); } @@ -160,30 +160,30 @@ contract NativeTokenLimitPluginTest is AccountTestBase { calls[2] = Call({target: recipient, value: 5 ether + 100_000, data: ""}); vm.startPrank(address(entryPoint)); - assertEq(plugin.limits(0, address(acct)), 10 ether); + assertEq(module.limits(0, address(acct)), 10 ether); PackedUserOperation[] memory uos = new PackedUserOperation[](1); uos[0] = _getPackedUO(200_000, 200_000, 200_000, 1, abi.encodeCall(IStandardExecutor.executeBatch, (calls))); entryPoint.handleOps(uos, bundler); - assertEq(plugin.limits(0, address(acct)), 10 ether - 6 ether - 700_001); + assertEq(module.limits(0, address(acct)), 10 ether - 6 ether - 700_001); assertEq(recipient.balance, 6 ether + 100_001); } function test_userOp_combinedExecLimit_failExec() public { - assertEq(plugin.limits(0, address(acct)), 10 ether); + assertEq(module.limits(0, address(acct)), 10 ether); PackedUserOperation[] memory uos = new PackedUserOperation[](1); uos[0] = _getPackedUO(200_000, 200_000, 200_000, 1, _getExecuteWithValue(10 ether)); entryPoint.handleOps(uos, bundler); - assertEq(plugin.limits(0, address(acct)), 10 ether - 600_000); + assertEq(module.limits(0, address(acct)), 10 ether - 600_000); assertEq(recipient.balance, 0); } function test_runtime_executeLimit() public { - assertEq(plugin.limits(0, address(acct)), 10 ether); + assertEq(module.limits(0, address(acct)), 10 ether); acct.executeWithAuthorization(_getExecuteWithValue(5 ether), _encodeSignature(validationFunction, 1, "")); - assertEq(plugin.limits(0, address(acct)), 5 ether); + assertEq(module.limits(0, address(acct)), 5 ether); } function test_runtime_executeBatchLimit() public { @@ -192,10 +192,10 @@ contract NativeTokenLimitPluginTest is AccountTestBase { calls[1] = Call({target: recipient, value: 1 ether, data: ""}); calls[2] = Call({target: recipient, value: 5 ether + 100_000, data: ""}); - assertEq(plugin.limits(0, address(acct)), 10 ether); + assertEq(module.limits(0, address(acct)), 10 ether); acct.executeWithAuthorization( abi.encodeCall(IStandardExecutor.executeBatch, (calls)), _encodeSignature(validationFunction, 1, "") ); - assertEq(plugin.limits(0, address(acct)), 4 ether - 100_001); + assertEq(module.limits(0, address(acct)), 4 ether - 100_001); } } diff --git a/test/plugin/TokenReceiverPlugin.t.sol b/test/module/TokenReceiverModule.t.sol similarity index 92% rename from test/plugin/TokenReceiverPlugin.t.sol rename to test/module/TokenReceiverModule.t.sol index 32fa2a9a..ffe85e86 100644 --- a/test/plugin/TokenReceiverPlugin.t.sol +++ b/test/module/TokenReceiverModule.t.sol @@ -7,7 +7,7 @@ import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Re import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {TokenReceiverPlugin} from "../../src/plugins/TokenReceiverPlugin.sol"; +import {TokenReceiverModule} from "../../src/modules/TokenReceiverModule.sol"; import {MockERC1155} from "../mocks/MockERC1155.sol"; import {MockERC721} from "../mocks/MockERC721.sol"; @@ -15,10 +15,10 @@ import {SingleSignerFactoryFixture} from "../mocks/SingleSignerFactoryFixture.so import {OptimizedTest} from "../utils/OptimizedTest.sol"; -contract TokenReceiverPluginTest is OptimizedTest, IERC1155Receiver { +contract TokenReceiverModuleTest is OptimizedTest, IERC1155Receiver { EntryPoint public entryPoint; UpgradeableModularAccount public acct; - TokenReceiverPlugin public plugin; + TokenReceiverModule public module; MockERC721 public t0; MockERC1155 public t1; @@ -39,7 +39,7 @@ contract TokenReceiverPluginTest is OptimizedTest, IERC1155Receiver { new SingleSignerFactoryFixture(entryPoint, _deploySingleSignerValidation()); acct = factory.createAccount(address(this), 0); - plugin = _deployTokenReceiverPlugin(); + module = _deployTokenReceiverModule(); t0 = new MockERC721("t0", "t0"); t0.mint(address(this), _TOKEN_ID); @@ -54,11 +54,11 @@ contract TokenReceiverPluginTest is OptimizedTest, IERC1155Receiver { } } - function _initPlugin() internal { - bytes32 manifestHash = keccak256(abi.encode(plugin.pluginManifest())); + function _initModule() internal { + bytes32 manifestHash = keccak256(abi.encode(module.moduleManifest())); vm.prank(address(entryPoint)); - acct.installPlugin(address(plugin), manifestHash, ""); + acct.installModule(address(module), manifestHash, ""); } function test_failERC721Transfer() public { @@ -73,7 +73,7 @@ contract TokenReceiverPluginTest is OptimizedTest, IERC1155Receiver { } function test_passERC721Transfer() public { - _initPlugin(); + _initModule(); assertEq(t0.ownerOf(_TOKEN_ID), address(this)); t0.safeTransferFrom(address(this), address(acct), _TOKEN_ID); assertEq(t0.ownerOf(_TOKEN_ID), address(acct)); @@ -102,7 +102,7 @@ contract TokenReceiverPluginTest is OptimizedTest, IERC1155Receiver { } function test_passERC1155Transfer() public { - _initPlugin(); + _initModule(); assertEq(t1.balanceOf(address(this), _TOKEN_ID), _TOKEN_AMOUNT); assertEq(t1.balanceOf(address(acct), _TOKEN_ID), 0); @@ -131,7 +131,7 @@ contract TokenReceiverPluginTest is OptimizedTest, IERC1155Receiver { } function test_passIntrospection() public { - _initPlugin(); + _initModule(); bool isSupported; diff --git a/test/samples/AllowlistPlugin.t.sol b/test/samples/AllowlistModule.t.sol similarity index 83% rename from test/samples/AllowlistPlugin.t.sol rename to test/samples/AllowlistModule.t.sol index d2a28f5e..80b91fb5 100644 --- a/test/samples/AllowlistPlugin.t.sol +++ b/test/samples/AllowlistModule.t.sol @@ -4,23 +4,23 @@ pragma solidity ^0.8.25; import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; +import {ModuleEntity, ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {Call} from "../../src/interfaces/IStandardExecutor.sol"; -import {AllowlistPlugin} from "../../src/samples/permissionhooks/AllowlistPlugin.sol"; +import {AllowlistModule} from "../../src/samples/permissionhooks/AllowlistModule.sol"; import {Counter} from "../mocks/Counter.sol"; import {CustomValidationTestBase} from "../utils/CustomValidationTestBase.sol"; -contract AllowlistPluginTest is CustomValidationTestBase { - AllowlistPlugin public allowlistPlugin; +contract AllowlistModuleTest is CustomValidationTestBase { + AllowlistModule public allowlistModule; - AllowlistPlugin.AllowlistInit[] public allowlistInit; + AllowlistModule.AllowlistInit[] public allowlistInit; Counter[] public counters; function setUp() public { - allowlistPlugin = new AllowlistPlugin(); + allowlistModule = new AllowlistModule(); counters = new Counter[](10); @@ -32,7 +32,7 @@ contract AllowlistPluginTest is CustomValidationTestBase { } function testFuzz_allowlistHook_userOp_single(uint256 seed) public { - AllowlistPlugin.AllowlistInit[] memory inits; + AllowlistModule.AllowlistInit[] memory inits; (inits, seed) = _generateRandomizedAllowlistInit(seed); _copyInitToStorage(inits); @@ -46,7 +46,7 @@ contract AllowlistPluginTest is CustomValidationTestBase { } function testFuzz_allowlistHook_userOp_batch(uint256 seed) public { - AllowlistPlugin.AllowlistInit[] memory inits; + AllowlistModule.AllowlistInit[] memory inits; (inits, seed) = _generateRandomizedAllowlistInit(seed); _copyInitToStorage(inits); @@ -60,7 +60,7 @@ contract AllowlistPluginTest is CustomValidationTestBase { } function testFuzz_allowlistHook_runtime_single(uint256 seed) public { - AllowlistPlugin.AllowlistInit[] memory inits; + AllowlistModule.AllowlistInit[] memory inits; (inits, seed) = _generateRandomizedAllowlistInit(seed); _copyInitToStorage(inits); @@ -78,7 +78,7 @@ contract AllowlistPluginTest is CustomValidationTestBase { } function testFuzz_allowlistHook_runtime_batch(uint256 seed) public { - AllowlistPlugin.AllowlistInit[] memory inits; + AllowlistModule.AllowlistInit[] memory inits; (inits, seed) = _generateRandomizedAllowlistInit(seed); _copyInitToStorage(inits); @@ -145,17 +145,17 @@ contract AllowlistPluginTest is CustomValidationTestBase { Call memory call = calls[i]; (bool allowed, bool hasSelectorAllowlist) = - allowlistPlugin.targetAllowlist(call.target, address(account1)); + allowlistModule.targetAllowlist(call.target, address(account1)); if (allowed) { if ( hasSelectorAllowlist - && !allowlistPlugin.selectorAllowlist(call.target, bytes4(call.data), address(account1)) + && !allowlistModule.selectorAllowlist(call.target, bytes4(call.data), address(account1)) ) { return abi.encodeWithSelector( IEntryPoint.FailedOpWithRevert.selector, 0, "AA23 reverted", - abi.encodeWithSelector(AllowlistPlugin.SelectorNotAllowed.selector) + abi.encodeWithSelector(AllowlistModule.SelectorNotAllowed.selector) ); } } else { @@ -163,7 +163,7 @@ contract AllowlistPluginTest is CustomValidationTestBase { IEntryPoint.FailedOpWithRevert.selector, 0, "AA23 reverted", - abi.encodeWithSelector(AllowlistPlugin.TargetNotAllowed.selector) + abi.encodeWithSelector(AllowlistModule.TargetNotAllowed.selector) ); } } @@ -176,30 +176,30 @@ contract AllowlistPluginTest is CustomValidationTestBase { Call memory call = calls[i]; (bool allowed, bool hasSelectorAllowlist) = - allowlistPlugin.targetAllowlist(call.target, address(account1)); + allowlistModule.targetAllowlist(call.target, address(account1)); if (allowed) { if ( hasSelectorAllowlist - && !allowlistPlugin.selectorAllowlist(call.target, bytes4(call.data), address(account1)) + && !allowlistModule.selectorAllowlist(call.target, bytes4(call.data), address(account1)) ) { return abi.encodeWithSelector( UpgradeableModularAccount.PreRuntimeValidationHookFailed.selector, - address(allowlistPlugin), - uint32(AllowlistPlugin.EntityId.PRE_VALIDATION_HOOK), - abi.encodeWithSelector(AllowlistPlugin.SelectorNotAllowed.selector) + address(allowlistModule), + uint32(AllowlistModule.EntityId.PRE_VALIDATION_HOOK), + abi.encodeWithSelector(AllowlistModule.SelectorNotAllowed.selector) ); } } else { return abi.encodeWithSelector( UpgradeableModularAccount.PreRuntimeValidationHookFailed.selector, - address(allowlistPlugin), - uint32(AllowlistPlugin.EntityId.PRE_VALIDATION_HOOK), - abi.encodeWithSelector(AllowlistPlugin.TargetNotAllowed.selector) + address(allowlistModule), + uint32(AllowlistModule.EntityId.PRE_VALIDATION_HOOK), + abi.encodeWithSelector(AllowlistModule.TargetNotAllowed.selector) ); } } - // At this point, we have returned any error that would come from the AllowlistPlugin. + // At this point, we have returned any error that would come from the AllowlistModule. // But, because this is in the runtime path, the Counter itself may throw if it is not a valid selector. for (uint256 i = 0; i < calls.length; i++) { @@ -221,12 +221,12 @@ contract AllowlistPluginTest is CustomValidationTestBase { function _generateRandomizedAllowlistInit(uint256 seed) internal view - returns (AllowlistPlugin.AllowlistInit[] memory, uint256) + returns (AllowlistModule.AllowlistInit[] memory, uint256) { uint256 length = seed % 10; seed = _next(seed); - AllowlistPlugin.AllowlistInit[] memory init = new AllowlistPlugin.AllowlistInit[](length); + AllowlistModule.AllowlistInit[] memory init = new AllowlistModule.AllowlistInit[](length); for (uint256 i = 0; i < length; i++) { // Half the time, the target is a random counter, the other half, it's a random address. @@ -271,7 +271,7 @@ contract AllowlistPluginTest is CustomValidationTestBase { seed = _next(seed); } - init[i] = AllowlistPlugin.AllowlistInit(target, hasSelectorAllowlist, selectors); + init[i] = AllowlistModule.AllowlistInit(target, hasSelectorAllowlist, selectors); } return (init, seed); @@ -291,12 +291,12 @@ contract AllowlistPluginTest is CustomValidationTestBase { internal virtual override - returns (PluginEntity, bool, bool, bytes4[] memory, bytes memory, bytes memory, bytes memory) + returns (ModuleEntity, bool, bool, bytes4[] memory, bytes memory, bytes memory, bytes memory) { - PluginEntity accessControlHook = - PluginEntityLib.pack(address(allowlistPlugin), uint32(AllowlistPlugin.EntityId.PRE_VALIDATION_HOOK)); + ModuleEntity accessControlHook = + ModuleEntityLib.pack(address(allowlistModule), uint32(AllowlistModule.EntityId.PRE_VALIDATION_HOOK)); - PluginEntity[] memory preValidationHooks = new PluginEntity[](1); + ModuleEntity[] memory preValidationHooks = new ModuleEntity[](1); preValidationHooks[0] = accessControlHook; bytes[] memory preValidationHookData = new bytes[](1); @@ -318,7 +318,7 @@ contract AllowlistPluginTest is CustomValidationTestBase { // Unfortunately, this is a feature that solidity has only implemented in via-ir, so we need to do it manually // to be able to run the tests in lite mode. - function _copyInitToStorage(AllowlistPlugin.AllowlistInit[] memory init) internal { + function _copyInitToStorage(AllowlistModule.AllowlistInit[] memory init) internal { for (uint256 i = 0; i < init.length; i++) { allowlistInit.push(init[i]); } diff --git a/test/utils/AccountTestBase.sol b/test/utils/AccountTestBase.sol index 94caaabb..733b93e4 100644 --- a/test/utils/AccountTestBase.sol +++ b/test/utils/AccountTestBase.sol @@ -6,9 +6,9 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {PluginEntity, PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; +import {ModuleEntity, ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {Call, IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; -import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; +import {SingleSignerValidation} from "../../src/modules/validation/SingleSignerValidation.sol"; import {OptimizedTest} from "./OptimizedTest.sol"; import {TEST_DEFAULT_VALIDATION_ENTITY_ID as EXT_CONST_TEST_DEFAULT_VALIDATION_ENTITY_ID} from @@ -19,7 +19,7 @@ import {SingleSignerFactoryFixture} from "../mocks/SingleSignerFactoryFixture.so /// @dev This contract handles common boilerplate setup for tests using UpgradeableModularAccount with /// SingleSignerValidation. abstract contract AccountTestBase is OptimizedTest { - using PluginEntityLib for PluginEntity; + using ModuleEntityLib for ModuleEntity; using MessageHashUtils for bytes32; EntryPoint public entryPoint; @@ -32,7 +32,7 @@ abstract contract AccountTestBase is OptimizedTest { uint256 public owner1Key; UpgradeableModularAccount public account1; - PluginEntity internal _signerValidation; + ModuleEntity internal _signerValidation; uint8 public constant SELECTOR_ASSOCIATED_VALIDATION = 0; uint8 public constant GLOBAL_VALIDATION = 1; @@ -60,7 +60,7 @@ abstract contract AccountTestBase is OptimizedTest { vm.deal(address(account1), 100 ether); _signerValidation = - PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID); + ModuleEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID); } function _runExecUserOp(address target, bytes memory callData) internal { @@ -103,7 +103,7 @@ abstract contract AccountTestBase is OptimizedTest { (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); userOp.signature = _encodeSignature( - PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), + ModuleEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, abi.encodePacked(r, s, v) ); @@ -156,7 +156,7 @@ abstract contract AccountTestBase is OptimizedTest { account1.executeWithAuthorization( callData, _encodeSignature( - PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), + ModuleEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, "" ) @@ -171,7 +171,7 @@ abstract contract AccountTestBase is OptimizedTest { account1.executeWithAuthorization( callData, _encodeSignature( - PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), + ModuleEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, "" ) @@ -193,7 +193,7 @@ abstract contract AccountTestBase is OptimizedTest { ) ), _encodeSignature( - PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), + ModuleEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, "" ) @@ -207,7 +207,7 @@ abstract contract AccountTestBase is OptimizedTest { // helper function to encode a signature, according to the per-hook and per-validation data format. function _encodeSignature( - PluginEntity validationFunction, + ModuleEntity validationFunction, uint8 globalOrNot, PreValidationHookData[] memory preValidationHookData, bytes memory validationData @@ -231,7 +231,7 @@ abstract contract AccountTestBase is OptimizedTest { } // overload for the case where there are no pre-validation hooks - function _encodeSignature(PluginEntity validationFunction, uint8 globalOrNot, bytes memory validationData) + function _encodeSignature(ModuleEntity validationFunction, uint8 globalOrNot, bytes memory validationData) internal pure returns (bytes memory) diff --git a/test/utils/CustomValidationTestBase.sol b/test/utils/CustomValidationTestBase.sol index f9e37c27..18fd6ae4 100644 --- a/test/utils/CustomValidationTestBase.sol +++ b/test/utils/CustomValidationTestBase.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.25; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {PluginEntity} from "../../src/helpers/PluginEntityLib.sol"; +import {ModuleEntity} from "../../src/helpers/ModuleEntityLib.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import {AccountTestBase} from "./AccountTestBase.sol"; @@ -16,7 +16,7 @@ import {AccountTestBase} from "./AccountTestBase.sol"; abstract contract CustomValidationTestBase is AccountTestBase { function _customValidationSetup() internal { ( - PluginEntity validationFunction, + ModuleEntity validationFunction, bool isGlobal, bool isSignatureValidation, bytes4[] memory selectors, @@ -44,7 +44,7 @@ abstract contract CustomValidationTestBase is AccountTestBase { internal virtual returns ( - PluginEntity validationFunction, + ModuleEntity validationFunction, bool shared, bool isSignatureValidation, bytes4[] memory selectors, diff --git a/test/utils/OptimizedTest.sol b/test/utils/OptimizedTest.sol index dc94380b..870d416a 100644 --- a/test/utils/OptimizedTest.sol +++ b/test/utils/OptimizedTest.sol @@ -7,8 +7,8 @@ import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntry import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {TokenReceiverPlugin} from "../../src/plugins/TokenReceiverPlugin.sol"; -import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; +import {TokenReceiverModule} from "../../src/modules/TokenReceiverModule.sol"; +import {SingleSignerValidation} from "../../src/modules/validation/SingleSignerValidation.sol"; /// @dev This contract provides functions to deploy optimized (via IR) precompiled contracts. By compiling just /// the source contracts (excluding the test suite) via IR, and using the resulting bytecode within the tests @@ -45,10 +45,10 @@ abstract contract OptimizedTest is Test { : new UpgradeableModularAccount(entryPoint); } - function _deployTokenReceiverPlugin() internal returns (TokenReceiverPlugin) { + function _deployTokenReceiverModule() internal returns (TokenReceiverModule) { return _isOptimizedTest() - ? TokenReceiverPlugin(deployCode("out-optimized/TokenReceiverPlugin.sol/TokenReceiverPlugin.json")) - : new TokenReceiverPlugin(); + ? TokenReceiverModule(deployCode("out-optimized/TokenReceiverModule.sol/TokenReceiverModule.json")) + : new TokenReceiverModule(); } function _deploySingleSignerValidation() internal returns (SingleSignerValidation) { diff --git a/test/validation/SingleSignerValidation.t.sol b/test/validation/SingleSignerValidation.t.sol index e81706fe..ae3be4e5 100644 --- a/test/validation/SingleSignerValidation.t.sol +++ b/test/validation/SingleSignerValidation.t.sol @@ -5,7 +5,7 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {PluginEntityLib} from "../../src/helpers/PluginEntityLib.sol"; +import {ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import {ContractOwner} from "../mocks/ContractOwner.sol"; @@ -50,7 +50,7 @@ contract SingleSignerValidationTest is AccountTestBase { bytes32 userOpHash = entryPoint.getUserOpHash(userOp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); userOp.signature = _encodeSignature( - PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), + ModuleEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, abi.encodePacked(r, s, v) ); @@ -68,7 +68,7 @@ contract SingleSignerValidationTest is AccountTestBase { account.executeWithAuthorization( abi.encodeCall(UpgradeableModularAccount.execute, (ethRecipient, 1 wei, "")), _encodeSignature( - PluginEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), + ModuleEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), GLOBAL_VALIDATION, "" ) @@ -91,7 +91,7 @@ contract SingleSignerValidationTest is AccountTestBase { account.executeWithAuthorization( abi.encodeCall(UpgradeableModularAccount.execute, (ethRecipient, 1 wei, "")), _encodeSignature( - PluginEntityLib.pack(address(singleSignerValidation), newEntityId), GLOBAL_VALIDATION, "" + ModuleEntityLib.pack(address(singleSignerValidation), newEntityId), GLOBAL_VALIDATION, "" ) ); assertEq(ethRecipient.balance, 1 wei); From ee292ad3687ca80cd706e65dbe636936fbe045d1 Mon Sep 17 00:00:00 2001 From: fangting-alchemy <119372438+fangting-alchemy@users.noreply.github.com> Date: Fri, 19 Jul 2024 10:45:56 -0700 Subject: [PATCH 086/111] fix: organize all modules under modules folder (#107) --- src/{samples => modules}/permissionhooks/AllowlistModule.sol | 0 test/{samples => module}/AllowlistModule.t.sol | 2 +- test/{validation => module}/SingleSignerValidation.t.sol | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename src/{samples => modules}/permissionhooks/AllowlistModule.sol (100%) rename test/{samples => module}/AllowlistModule.t.sol (99%) rename test/{validation => module}/SingleSignerValidation.t.sol (100%) diff --git a/src/samples/permissionhooks/AllowlistModule.sol b/src/modules/permissionhooks/AllowlistModule.sol similarity index 100% rename from src/samples/permissionhooks/AllowlistModule.sol rename to src/modules/permissionhooks/AllowlistModule.sol diff --git a/test/samples/AllowlistModule.t.sol b/test/module/AllowlistModule.t.sol similarity index 99% rename from test/samples/AllowlistModule.t.sol rename to test/module/AllowlistModule.t.sol index 80b91fb5..136ae211 100644 --- a/test/samples/AllowlistModule.t.sol +++ b/test/module/AllowlistModule.t.sol @@ -7,7 +7,7 @@ import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAcc import {ModuleEntity, ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {Call} from "../../src/interfaces/IStandardExecutor.sol"; -import {AllowlistModule} from "../../src/samples/permissionhooks/AllowlistModule.sol"; +import {AllowlistModule} from "../../src/modules/permissionhooks/AllowlistModule.sol"; import {Counter} from "../mocks/Counter.sol"; import {CustomValidationTestBase} from "../utils/CustomValidationTestBase.sol"; diff --git a/test/validation/SingleSignerValidation.t.sol b/test/module/SingleSignerValidation.t.sol similarity index 100% rename from test/validation/SingleSignerValidation.t.sol rename to test/module/SingleSignerValidation.t.sol From 5cb215f8042e28d2b88246d6981704573a00fa63 Mon Sep 17 00:00:00 2001 From: Adam Egyed <5456061+adamegyed@users.noreply.github.com> Date: Tue, 23 Jul 2024 12:59:06 -0400 Subject: [PATCH 087/111] feat: [v0.8-develop] User controlled install 1/N (#101) --- src/account/AccountLoupe.sol | 5 - src/account/AccountStorage.sol | 2 - src/account/ModuleManagerInternals.sol | 50 ++------- src/account/UpgradeableModularAccount.sol | 24 ++--- src/helpers/KnownSelectors.sol | 3 +- src/interfaces/IAccountLoupe.sol | 4 - src/interfaces/IModuleManager.sol | 15 +-- test/account/AccountExecHooks.t.sol | 17 ++- test/account/AccountLoupe.t.sol | 14 +-- test/account/AccountReturnData.t.sol | 10 +- test/account/PermittedCallPermissions.t.sol | 10 +- test/account/SelfCallAuthorization.t.sol | 6 +- test/account/UpgradeableModularAccount.t.sol | 105 ++++--------------- test/account/ValidationIntersection.t.sol | 6 +- test/module/TokenReceiverModule.t.sol | 7 +- 15 files changed, 75 insertions(+), 203 deletions(-) diff --git a/src/account/AccountLoupe.sol b/src/account/AccountLoupe.sol index 851c9cfa..f0afc1b2 100644 --- a/src/account/AccountLoupe.sol +++ b/src/account/AccountLoupe.sol @@ -88,9 +88,4 @@ abstract contract AccountLoupe is IAccountLoupe { { preValidationHooks = getAccountStorage().validationData[validationFunction].preValidationHooks; } - - /// @inheritdoc IAccountLoupe - function getInstalledModules() external view override returns (address[] memory moduleAddresses) { - moduleAddresses = getAccountStorage().moduleManifestHashes.keys(); - } } diff --git a/src/account/AccountStorage.sol b/src/account/AccountStorage.sol index 2c480e32..c9845a8e 100644 --- a/src/account/AccountStorage.sol +++ b/src/account/AccountStorage.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.25; -import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; @@ -43,7 +42,6 @@ struct AccountStorage { // AccountStorageInitializable variables uint8 initialized; bool initializing; - EnumerableMap.AddressToUintMap moduleManifestHashes; // Execution functions and their associated functions mapping(bytes4 => SelectorData) selectorData; mapping(ModuleEntity validationFunction => ValidationData) validationData; diff --git a/src/account/ModuleManagerInternals.sol b/src/account/ModuleManagerInternals.sol index 68d6ea1d..a6a275b6 100644 --- a/src/account/ModuleManagerInternals.sol +++ b/src/account/ModuleManagerInternals.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.25; import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; -import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {KnownSelectors} from "../helpers/KnownSelectors.sol"; @@ -15,18 +14,14 @@ import {AccountStorage, SelectorData, getAccountStorage, toSetValue} from "./Acc abstract contract ModuleManagerInternals is IModuleManager { using EnumerableSet for EnumerableSet.Bytes32Set; - using EnumerableMap for EnumerableMap.AddressToUintMap; using ModuleEntityLib for ModuleEntity; error ArrayLengthMismatch(); error Erc4337FunctionNotAllowed(bytes4 selector); error ExecutionFunctionAlreadySet(bytes4 selector); - error InvalidModuleManifest(); error IModuleFunctionNotAllowed(bytes4 selector); error NativeFunctionNotAllowed(bytes4 selector); - error NullModuleEntity(); error NullModule(); - error ModuleAlreadyInstalled(address module); error ModuleInstallCallbackFailed(address module, bytes revertReason); error ModuleInterfaceNotSupported(address module); error ModuleNotInstalled(address module); @@ -138,32 +133,21 @@ abstract contract ModuleManagerInternals is IModuleManager { ); } - function _installModule(address module, bytes32 manifestHash, bytes memory moduleInstallData) internal { + function _installModule(address module, ModuleManifest calldata manifest, bytes memory moduleInstallData) + internal + { AccountStorage storage _storage = getAccountStorage(); if (module == address(0)) { revert NullModule(); } - // Check if the module exists. - if (_storage.moduleManifestHashes.contains(module)) { - revert ModuleAlreadyInstalled(module); - } - + // TODO: do we need this check? Or switch to a non-165 checking function? // Check that the module supports the IModule interface. if (!ERC165Checker.supportsInterface(module, type(IModule).interfaceId)) { revert ModuleInterfaceNotSupported(module); } - // Check manifest hash. - ModuleManifest memory manifest = IModule(module).moduleManifest(); - if (!_isValidModuleManifest(manifest, manifestHash)) { - revert InvalidModuleManifest(); - } - - // Add the module metadata to the account - _storage.moduleManifestHashes.set(module, uint256(manifestHash)); - // Update components according to the manifest. uint256 length = manifest.executionFunctions.length; for (uint256 i = 0; i < length; ++i) { @@ -200,25 +184,14 @@ abstract contract ModuleManagerInternals is IModuleManager { revert ModuleInstallCallbackFailed(module, revertReason); } - emit ModuleInstalled(module, manifestHash); + emit ModuleInstalled(module); } - function _uninstallModule(address module, ModuleManifest memory manifest, bytes memory uninstallData) + function _uninstallModule(address module, ModuleManifest calldata manifest, bytes memory uninstallData) internal { AccountStorage storage _storage = getAccountStorage(); - // Check if the module exists. - if (!_storage.moduleManifestHashes.contains(module)) { - revert ModuleNotInstalled(module); - } - - // Check manifest hash. - bytes32 manifestHash = bytes32(_storage.moduleManifestHashes.get(module)); - if (!_isValidModuleManifest(manifest, manifestHash)) { - revert InvalidModuleManifest(); - } - // Remove components according to the manifest, in reverse order (by component type) of their installation. uint256 length = manifest.executionHooks.length; @@ -245,9 +218,6 @@ abstract contract ModuleManagerInternals is IModuleManager { _storage.supportedIfaces[manifest.interfaceIds[i]] -= 1; } - // Remove the module metadata from the account. - _storage.moduleManifestHashes.remove(module); - // Clear the module storage for the account. bool onUninstallSuccess = true; // solhint-disable-next-line no-empty-blocks @@ -258,12 +228,4 @@ abstract contract ModuleManagerInternals is IModuleManager { emit ModuleUninstalled(module, onUninstallSuccess); } - - function _isValidModuleManifest(ModuleManifest memory manifest, bytes32 manifestHash) - internal - pure - returns (bool) - { - return manifestHash == keccak256(abi.encode(manifest)); - } } diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 9f102313..2f1da16d 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -19,7 +19,7 @@ import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; import {_coalescePreValidation, _coalesceValidation} from "../helpers/ValidationResHelpers.sol"; import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; -import {IModule, ModuleManifest} from "../interfaces/IModule.sol"; +import {ModuleManifest} from "../interfaces/IModule.sol"; import {IModuleManager, ModuleEntity, ValidationConfig} from "../interfaces/IModuleManager.sol"; import {Call, IStandardExecutor} from "../interfaces/IStandardExecutor.sol"; import {IValidation} from "../interfaces/IValidation.sol"; @@ -109,21 +109,21 @@ contract UpgradeableModularAccount is /// @notice Initializes the account with a set of modules /// @param modules The modules to install - /// @param manifestHashes The manifest hashes of the modules to install + /// @param manifests The manifests of the modules to install /// @param moduleInstallDatas The module install datas of the modules to install function initialize( address[] memory modules, - bytes32[] memory manifestHashes, + ModuleManifest[] calldata manifests, bytes[] memory moduleInstallDatas ) external initializer { uint256 length = modules.length; - if (length != manifestHashes.length || length != moduleInstallDatas.length) { + if (length != manifests.length || length != moduleInstallDatas.length) { revert ArrayLengthMismatch(); } for (uint256 i = 0; i < length; ++i) { - _installModule(modules[i], manifestHashes[i], moduleInstallDatas[i]); + _installModule(modules[i], manifests[i], moduleInstallDatas[i]); } emit ModularAccountInitialized(_ENTRY_POINT); @@ -249,29 +249,21 @@ contract UpgradeableModularAccount is /// @inheritdoc IModuleManager /// @notice May be validated by a global validation. - function installModule(address module, bytes32 manifestHash, bytes calldata moduleInstallData) + function installModule(address module, ModuleManifest calldata manifest, bytes calldata moduleInstallData) external override wrapNativeFunction { - _installModule(module, manifestHash, moduleInstallData); + _installModule(module, manifest, moduleInstallData); } /// @inheritdoc IModuleManager /// @notice May be validated by a global validation. - function uninstallModule(address module, bytes calldata config, bytes calldata moduleUninstallData) + function uninstallModule(address module, ModuleManifest calldata manifest, bytes calldata moduleUninstallData) external override wrapNativeFunction { - ModuleManifest memory manifest; - - if (config.length > 0) { - manifest = abi.decode(config, (ModuleManifest)); - } else { - manifest = IModule(module).moduleManifest(); - } - _uninstallModule(module, manifest, moduleUninstallData); } diff --git a/src/helpers/KnownSelectors.sol b/src/helpers/KnownSelectors.sol index 811f2f5d..ec3b6e6f 100644 --- a/src/helpers/KnownSelectors.sol +++ b/src/helpers/KnownSelectors.sol @@ -35,8 +35,7 @@ library KnownSelectors { // check against IAccountLoupe methods || selector == IAccountLoupe.getExecutionFunctionHandler.selector || selector == IAccountLoupe.getSelectors.selector || selector == IAccountLoupe.getExecutionHooks.selector - || selector == IAccountLoupe.getPreValidationHooks.selector - || selector == IAccountLoupe.getInstalledModules.selector; + || selector == IAccountLoupe.getPreValidationHooks.selector; } function isErc4337Function(bytes4 selector) internal pure returns (bool) { diff --git a/src/interfaces/IAccountLoupe.sol b/src/interfaces/IAccountLoupe.sol index 01cca512..f076de61 100644 --- a/src/interfaces/IAccountLoupe.sol +++ b/src/interfaces/IAccountLoupe.sol @@ -40,8 +40,4 @@ interface IAccountLoupe { external view returns (ModuleEntity[] memory preValidationHooks); - - /// @notice Get an array of all installed modules. - /// @return The addresses of all installed modules. - function getInstalledModules() external view returns (address[] memory); } diff --git a/src/interfaces/IModuleManager.sol b/src/interfaces/IModuleManager.sol index eb2db1ea..9923bd34 100644 --- a/src/interfaces/IModuleManager.sol +++ b/src/interfaces/IModuleManager.sol @@ -1,21 +1,24 @@ // SPDX-License-Identifier: CC0-1.0 pragma solidity ^0.8.25; +import {ModuleManifest} from "./IModule.sol"; + type ModuleEntity is bytes24; type ValidationConfig is bytes26; interface IModuleManager { - event ModuleInstalled(address indexed module, bytes32 manifestHash); + event ModuleInstalled(address indexed module); event ModuleUninstalled(address indexed module, bool indexed onUninstallSucceeded); /// @notice Install a module to the modular account. /// @param module The module to install. - /// @param manifestHash The hash of the module manifest. + /// @param manifest the manifest describing functions to install /// @param moduleInstallData Optional data to be decoded and used by the module to setup initial module data /// for the modular account. - function installModule(address module, bytes32 manifestHash, bytes calldata moduleInstallData) external; + function installModule(address module, ModuleManifest calldata manifest, bytes calldata moduleInstallData) + external; /// @notice Temporary install function - pending a different user-supplied install config & manifest validation /// path. @@ -53,9 +56,9 @@ interface IModuleManager { /// @notice Uninstall a module from the modular account. /// @param module The module to uninstall. - /// @param config An optional, implementation-specific field that accounts may use to ensure consistency - /// guarantees. + /// @param manifest the manifest describing functions to uninstall. /// @param moduleUninstallData Optional data to be decoded and used by the module to clear module data for the /// modular account. - function uninstallModule(address module, bytes calldata config, bytes calldata moduleUninstallData) external; + function uninstallModule(address module, ModuleManifest calldata manifest, bytes calldata moduleUninstallData) + external; } diff --git a/test/account/AccountExecHooks.t.sol b/test/account/AccountExecHooks.t.sol index 052cea4e..434ad0e6 100644 --- a/test/account/AccountExecHooks.t.sol +++ b/test/account/AccountExecHooks.t.sol @@ -14,8 +14,6 @@ import {AccountTestBase} from "../utils/AccountTestBase.sol"; contract AccountExecHooksTest is AccountTestBase { MockModule public mockModule1; - bytes32 public manifestHash1; - bytes32 public manifestHash2; bytes4 internal constant _EXEC_SELECTOR = bytes4(uint32(1)); uint32 internal constant _PRE_HOOK_FUNCTION_ID_1 = 1; @@ -24,7 +22,7 @@ contract AccountExecHooksTest is AccountTestBase { ModuleManifest internal _m1; - event ModuleInstalled(address indexed module, bytes32 manifestHash); + event ModuleInstalled(address indexed module); event ModuleUninstalled(address indexed module, bool indexed callbacksSucceeded); // emitted by MockModule event ReceivedCall(bytes msgData, uint256 msgValue); @@ -162,19 +160,19 @@ contract AccountExecHooksTest is AccountTestBase { function _installModule1WithHooks(ManifestExecutionHook memory execHooks) internal { _m1.executionHooks.push(execHooks); mockModule1 = new MockModule(_m1); - manifestHash1 = keccak256(abi.encode(mockModule1.moduleManifest())); vm.expectEmit(true, true, true, true); emit ReceivedCall(abi.encodeCall(IModule.onInstall, (bytes(""))), 0); vm.expectEmit(true, true, true, true); - emit ModuleInstalled(address(mockModule1), manifestHash1); + emit ModuleInstalled(address(mockModule1)); - vm.prank(address(entryPoint)); + vm.startPrank(address(entryPoint)); account1.installModule({ module: address(mockModule1), - manifestHash: manifestHash1, + manifest: mockModule1.moduleManifest(), moduleInstallData: bytes("") }); + vm.stopPrank(); } function _uninstallModule(MockModule module) internal { @@ -183,7 +181,8 @@ contract AccountExecHooksTest is AccountTestBase { vm.expectEmit(true, true, true, true); emit ModuleUninstalled(address(module), true); - vm.prank(address(entryPoint)); - account1.uninstallModule(address(module), bytes(""), bytes("")); + vm.startPrank(address(entryPoint)); + account1.uninstallModule(address(module), module.moduleManifest(), bytes("")); + vm.stopPrank(); } } diff --git a/test/account/AccountLoupe.t.sol b/test/account/AccountLoupe.t.sol index c4f4abc0..c10540fb 100644 --- a/test/account/AccountLoupe.t.sol +++ b/test/account/AccountLoupe.t.sol @@ -21,17 +21,9 @@ contract AccountLoupeTest is CustomValidationTestBase { _customValidationSetup(); - bytes32 manifestHash = keccak256(abi.encode(comprehensiveModule.moduleManifest())); - vm.prank(address(entryPoint)); - account1.installModule(address(comprehensiveModule), manifestHash, ""); - } - - function test_moduleLoupe_getInstalledModules_initial() public { - address[] memory modules = account1.getInstalledModules(); - - assertEq(modules.length, 1); - - assertEq(modules[0], address(comprehensiveModule)); + vm.startPrank(address(entryPoint)); + account1.installModule(address(comprehensiveModule), comprehensiveModule.moduleManifest(), ""); + vm.stopPrank(); } function test_moduleLoupe_getExecutionFunctionHandler_native() public { diff --git a/test/account/AccountReturnData.t.sol b/test/account/AccountReturnData.t.sol index febaa54b..7d51e9d1 100644 --- a/test/account/AccountReturnData.t.sol +++ b/test/account/AccountReturnData.t.sol @@ -26,21 +26,19 @@ contract AccountReturnDataTest is AccountTestBase { resultConsumerModule = new ResultConsumerModule(resultCreatorModule, regularResultContract); // Add the result creator module to the account - bytes32 resultCreatorManifestHash = keccak256(abi.encode(resultCreatorModule.moduleManifest())); - vm.prank(address(entryPoint)); + vm.startPrank(address(entryPoint)); account1.installModule({ module: address(resultCreatorModule), - manifestHash: resultCreatorManifestHash, + manifest: resultCreatorModule.moduleManifest(), moduleInstallData: "" }); // Add the result consumer module to the account - bytes32 resultConsumerManifestHash = keccak256(abi.encode(resultConsumerModule.moduleManifest())); - vm.prank(address(entryPoint)); account1.installModule({ module: address(resultConsumerModule), - manifestHash: resultConsumerManifestHash, + manifest: resultConsumerModule.moduleManifest(), moduleInstallData: "" }); + vm.stopPrank(); } // Tests the ability to read the result of module execution functions via the account's fallback diff --git a/test/account/PermittedCallPermissions.t.sol b/test/account/PermittedCallPermissions.t.sol index 1c378e9e..0b1007f7 100644 --- a/test/account/PermittedCallPermissions.t.sol +++ b/test/account/PermittedCallPermissions.t.sol @@ -21,21 +21,19 @@ contract PermittedCallPermissionsTest is AccountTestBase { permittedCallerModule = new PermittedCallerModule(); // Add the result creator module to the account - bytes32 resultCreatorManifestHash = keccak256(abi.encode(resultCreatorModule.moduleManifest())); - vm.prank(address(entryPoint)); + vm.startPrank(address(entryPoint)); account1.installModule({ module: address(resultCreatorModule), - manifestHash: resultCreatorManifestHash, + manifest: resultCreatorModule.moduleManifest(), moduleInstallData: "" }); // Add the permitted caller module to the account - bytes32 permittedCallerManifestHash = keccak256(abi.encode(permittedCallerModule.moduleManifest())); - vm.prank(address(entryPoint)); account1.installModule({ module: address(permittedCallerModule), - manifestHash: permittedCallerManifestHash, + manifest: permittedCallerModule.moduleManifest(), moduleInstallData: "" }); + vm.stopPrank(); } function test_permittedCall_Allowed() public { diff --git a/test/account/SelfCallAuthorization.t.sol b/test/account/SelfCallAuthorization.t.sol index aae886eb..a9f61905 100644 --- a/test/account/SelfCallAuthorization.t.sol +++ b/test/account/SelfCallAuthorization.t.sol @@ -24,9 +24,9 @@ contract SelfCallAuthorizationTest is AccountTestBase { comprehensiveModule = new ComprehensiveModule(); - bytes32 manifestHash = keccak256(abi.encode(comprehensiveModule.moduleManifest())); - vm.prank(address(entryPoint)); - account1.installModule(address(comprehensiveModule), manifestHash, ""); + vm.startPrank(address(entryPoint)); + account1.installModule(address(comprehensiveModule), comprehensiveModule.moduleManifest(), ""); + vm.stopPrank(); comprehensiveModuleValidation = ModuleEntityLib.pack(address(comprehensiveModule), uint32(ComprehensiveModule.EntityId.VALIDATION)); diff --git a/test/account/UpgradeableModularAccount.t.sol b/test/account/UpgradeableModularAccount.t.sol index 76a1c4f0..847ab552 100644 --- a/test/account/UpgradeableModularAccount.t.sol +++ b/test/account/UpgradeableModularAccount.t.sol @@ -42,7 +42,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { Counter public counter; ModuleManifest internal _manifest; - event ModuleInstalled(address indexed module, bytes32 manifestHash); + event ModuleInstalled(address indexed module); event ModuleUninstalled(address indexed module, bool indexed callbacksSucceeded); event ReceivedCall(bytes msgData, uint256 msgValue); @@ -239,19 +239,17 @@ contract UpgradeableModularAccountTest is AccountTestBase { function test_installModule() public { vm.startPrank(address(entryPoint)); - bytes32 manifestHash = keccak256(abi.encode(tokenReceiverModule.moduleManifest())); - vm.expectEmit(true, true, true, true); - emit ModuleInstalled(address(tokenReceiverModule), manifestHash); + emit ModuleInstalled(address(tokenReceiverModule)); IModuleManager(account1).installModule({ module: address(tokenReceiverModule), - manifestHash: manifestHash, + manifest: tokenReceiverModule.moduleManifest(), moduleInstallData: abi.encode(uint48(1 days)) }); - address[] memory modules = IAccountLoupe(account1).getInstalledModules(); - assertEq(modules.length, 1); - assertEq(modules[0], address(tokenReceiverModule)); + address handler = + IAccountLoupe(account1).getExecutionFunctionHandler(TokenReceiverModule.onERC721Received.selector); + assertEq(handler, address(tokenReceiverModule)); } function test_installModule_PermittedCallSelectorNotInstalled() public { @@ -260,26 +258,14 @@ contract UpgradeableModularAccountTest is AccountTestBase { ModuleManifest memory m; MockModule mockModuleWithBadPermittedExec = new MockModule(m); - bytes32 manifestHash = keccak256(abi.encode(mockModuleWithBadPermittedExec.moduleManifest())); IModuleManager(account1).installModule({ module: address(mockModuleWithBadPermittedExec), - manifestHash: manifestHash, + manifest: mockModuleWithBadPermittedExec.moduleManifest(), moduleInstallData: "" }); } - function test_installModule_invalidManifest() public { - vm.startPrank(address(entryPoint)); - - vm.expectRevert(abi.encodeWithSelector(ModuleManagerInternals.InvalidModuleManifest.selector)); - IModuleManager(account1).installModule({ - module: address(tokenReceiverModule), - manifestHash: bytes32(0), - moduleInstallData: abi.encode(uint48(1 days)) - }); - } - function test_installModule_interfaceNotSupported() public { vm.startPrank(address(entryPoint)); @@ -287,31 +273,32 @@ contract UpgradeableModularAccountTest is AccountTestBase { vm.expectRevert( abi.encodeWithSelector(ModuleManagerInternals.ModuleInterfaceNotSupported.selector, address(badModule)) ); - IModuleManager(account1).installModule({ - module: address(badModule), - manifestHash: bytes32(0), - moduleInstallData: "" - }); + + ModuleManifest memory m; + + IModuleManager(account1).installModule({module: address(badModule), manifest: m, moduleInstallData: ""}); } function test_installModule_alreadyInstalled() public { - vm.startPrank(address(entryPoint)); + ModuleManifest memory m = tokenReceiverModule.moduleManifest(); - bytes32 manifestHash = keccak256(abi.encode(tokenReceiverModule.moduleManifest())); + vm.prank(address(entryPoint)); IModuleManager(account1).installModule({ module: address(tokenReceiverModule), - manifestHash: manifestHash, + manifest: m, moduleInstallData: abi.encode(uint48(1 days)) }); + vm.prank(address(entryPoint)); vm.expectRevert( abi.encodeWithSelector( - ModuleManagerInternals.ModuleAlreadyInstalled.selector, address(tokenReceiverModule) + ModuleManagerInternals.ExecutionFunctionAlreadySet.selector, + TokenReceiverModule.onERC721Received.selector ) ); IModuleManager(account1).installModule({ module: address(tokenReceiverModule), - manifestHash: manifestHash, + manifest: m, moduleInstallData: abi.encode(uint48(1 days)) }); } @@ -320,29 +307,9 @@ contract UpgradeableModularAccountTest is AccountTestBase { vm.startPrank(address(entryPoint)); ComprehensiveModule module = new ComprehensiveModule(); - bytes32 manifestHash = keccak256(abi.encode(module.moduleManifest())); - IModuleManager(account1).installModule({ - module: address(module), - manifestHash: manifestHash, - moduleInstallData: "" - }); - - vm.expectEmit(true, true, true, true); - emit ModuleUninstalled(address(module), true); - IModuleManager(account1).uninstallModule({module: address(module), config: "", moduleUninstallData: ""}); - address[] memory modules = IAccountLoupe(account1).getInstalledModules(); - assertEq(modules.length, 0); - } - - function test_uninstallModule_manifestParameter() public { - vm.startPrank(address(entryPoint)); - - ComprehensiveModule module = new ComprehensiveModule(); - bytes memory serializedManifest = abi.encode(module.moduleManifest()); - bytes32 manifestHash = keccak256(serializedManifest); IModuleManager(account1).installModule({ module: address(module), - manifestHash: manifestHash, + manifest: module.moduleManifest(), moduleInstallData: "" }); @@ -350,48 +317,22 @@ contract UpgradeableModularAccountTest is AccountTestBase { emit ModuleUninstalled(address(module), true); IModuleManager(account1).uninstallModule({ module: address(module), - config: serializedManifest, + manifest: module.moduleManifest(), moduleUninstallData: "" }); - address[] memory modules = IAccountLoupe(account1).getInstalledModules(); - assertEq(modules.length, 0); - } - - function test_uninstallModule_invalidManifestFails() public { - vm.startPrank(address(entryPoint)); - - ComprehensiveModule module = new ComprehensiveModule(); - bytes memory serializedManifest = abi.encode(module.moduleManifest()); - bytes32 manifestHash = keccak256(serializedManifest); - IModuleManager(account1).installModule({ - module: address(module), - manifestHash: manifestHash, - moduleInstallData: "" - }); - // Attempt to uninstall with a blank _manifest - ModuleManifest memory blankManifest; - - vm.expectRevert(abi.encodeWithSelector(ModuleManagerInternals.InvalidModuleManifest.selector)); - IModuleManager(account1).uninstallModule({ - module: address(module), - config: abi.encode(blankManifest), - moduleUninstallData: "" - }); - address[] memory modules = IAccountLoupe(account1).getInstalledModules(); - assertEq(modules.length, 1); - assertEq(modules[0], address(module)); + address handler = IAccountLoupe(account1).getExecutionFunctionHandler(module.foo.selector); + assertEq(handler, address(0)); } function _installModuleWithExecHooks() internal returns (MockModule module) { vm.startPrank(address(entryPoint)); module = new MockModule(_manifest); - bytes32 manifestHash = keccak256(abi.encode(module.moduleManifest())); IModuleManager(account1).installModule({ module: address(module), - manifestHash: manifestHash, + manifest: module.moduleManifest(), moduleInstallData: "" }); diff --git a/test/account/ValidationIntersection.t.sol b/test/account/ValidationIntersection.t.sol index 82a8fa7f..9662702f 100644 --- a/test/account/ValidationIntersection.t.sol +++ b/test/account/ValidationIntersection.t.sol @@ -49,12 +49,12 @@ contract ValidationIntersectionTest is AccountTestBase { vm.startPrank(address(entryPoint)); account1.installModule({ module: address(noHookModule), - manifestHash: keccak256(abi.encode(noHookModule.moduleManifest())), + manifest: noHookModule.moduleManifest(), moduleInstallData: "" }); account1.installModule({ module: address(oneHookModule), - manifestHash: keccak256(abi.encode(oneHookModule.moduleManifest())), + manifest: oneHookModule.moduleManifest(), moduleInstallData: "" }); // TODO: change with new install flow @@ -74,7 +74,7 @@ contract ValidationIntersectionTest is AccountTestBase { ); account1.installModule({ module: address(twoHookModule), - manifestHash: keccak256(abi.encode(twoHookModule.moduleManifest())), + manifest: twoHookModule.moduleManifest(), moduleInstallData: "" }); // temporary fix to add the pre-validation hook diff --git a/test/module/TokenReceiverModule.t.sol b/test/module/TokenReceiverModule.t.sol index ffe85e86..3230d221 100644 --- a/test/module/TokenReceiverModule.t.sol +++ b/test/module/TokenReceiverModule.t.sol @@ -55,10 +55,9 @@ contract TokenReceiverModuleTest is OptimizedTest, IERC1155Receiver { } function _initModule() internal { - bytes32 manifestHash = keccak256(abi.encode(module.moduleManifest())); - - vm.prank(address(entryPoint)); - acct.installModule(address(module), manifestHash, ""); + vm.startPrank(address(entryPoint)); + acct.installModule(address(module), module.moduleManifest(), ""); + vm.stopPrank(); } function test_failERC721Transfer() public { From 9ebbe95aa255e919f91da962c8ffc2047068491c Mon Sep 17 00:00:00 2001 From: Adam Egyed <5456061+adamegyed@users.noreply.github.com> Date: Tue, 23 Jul 2024 13:04:52 -0400 Subject: [PATCH 088/111] feat: [v0.8-develop] merge plugin manager contracts 2/N (#102) --- src/account/ModuleManager2.sol | 148 --------------- src/account/ModuleManagerInternals.sol | 181 ++++++++++++++++--- src/account/UpgradeableModularAccount.sol | 2 - src/interfaces/IModule.sol | 2 +- test/mocks/modules/ComprehensiveModule.sol | 2 +- test/mocks/modules/ReturnDataModuleMocks.sol | 2 +- test/mocks/modules/ValidationModuleMocks.sol | 6 +- 7 files changed, 162 insertions(+), 181 deletions(-) delete mode 100644 src/account/ModuleManager2.sol diff --git a/src/account/ModuleManager2.sol b/src/account/ModuleManager2.sol deleted file mode 100644 index e1868403..00000000 --- a/src/account/ModuleManager2.sol +++ /dev/null @@ -1,148 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.25; - -import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; - -import {ModuleEntityLib} from "../helpers/ModuleEntityLib.sol"; -import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; - -import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; -import {IModule} from "../interfaces/IModule.sol"; -import {ModuleEntity, ValidationConfig} from "../interfaces/IModuleManager.sol"; -import {ValidationData, getAccountStorage, toSetValue} from "./AccountStorage.sol"; - -// Temporary additional functions for a user-controlled install flow for validation functions. -abstract contract ModuleManager2 { - using EnumerableSet for EnumerableSet.Bytes32Set; - using ValidationConfigLib for ValidationConfig; - - // Index marking the start of the data for the validation function. - uint8 internal constant _RESERVED_VALIDATION_DATA_INDEX = 255; - uint32 internal constant _SELF_PERMIT_VALIDATION_FUNCTIONID = type(uint32).max; - - error PreValidationAlreadySet(ModuleEntity validationFunction, ModuleEntity preValidationFunction); - error ValidationAlreadySet(bytes4 selector, ModuleEntity validationFunction); - error ValidationNotSet(bytes4 selector, ModuleEntity validationFunction); - error PermissionAlreadySet(ModuleEntity validationFunction, ExecutionHook hook); - error PreValidationHookLimitExceeded(); - - function _installValidation( - ValidationConfig validationConfig, - bytes4[] memory selectors, - bytes calldata installData, - bytes memory preValidationHooks, - bytes memory permissionHooks - ) internal { - ValidationData storage _validationData = - getAccountStorage().validationData[validationConfig.moduleEntity()]; - - if (preValidationHooks.length > 0) { - (ModuleEntity[] memory preValidationFunctions, bytes[] memory initDatas) = - abi.decode(preValidationHooks, (ModuleEntity[], bytes[])); - - for (uint256 i = 0; i < preValidationFunctions.length; ++i) { - ModuleEntity preValidationFunction = preValidationFunctions[i]; - - _validationData.preValidationHooks.push(preValidationFunction); - - if (initDatas[i].length > 0) { - (address preValidationModule,) = ModuleEntityLib.unpack(preValidationFunction); - IModule(preValidationModule).onInstall(initDatas[i]); - } - } - - // Avoid collision between reserved index and actual indices - if (_validationData.preValidationHooks.length > _RESERVED_VALIDATION_DATA_INDEX) { - revert PreValidationHookLimitExceeded(); - } - } - - if (permissionHooks.length > 0) { - (ExecutionHook[] memory permissionFunctions, bytes[] memory initDatas) = - abi.decode(permissionHooks, (ExecutionHook[], bytes[])); - - for (uint256 i = 0; i < permissionFunctions.length; ++i) { - ExecutionHook memory permissionFunction = permissionFunctions[i]; - - if (!_validationData.permissionHooks.add(toSetValue(permissionFunction))) { - revert PermissionAlreadySet(validationConfig.moduleEntity(), permissionFunction); - } - - if (initDatas[i].length > 0) { - (address executionModule,) = ModuleEntityLib.unpack(permissionFunction.hookFunction); - IModule(executionModule).onInstall(initDatas[i]); - } - } - } - - for (uint256 i = 0; i < selectors.length; ++i) { - bytes4 selector = selectors[i]; - if (!_validationData.selectors.add(toSetValue(selector))) { - revert ValidationAlreadySet(selector, validationConfig.moduleEntity()); - } - } - - if (validationConfig.entityId() != _SELF_PERMIT_VALIDATION_FUNCTIONID) { - // Only allow global validations and signature validations if they're not direct-call validations. - - _validationData.isGlobal = validationConfig.isGlobal(); - _validationData.isSignatureValidation = validationConfig.isSignatureValidation(); - if (installData.length > 0) { - IModule(validationConfig.module()).onInstall(installData); - } - } - } - - function _uninstallValidation( - ModuleEntity validationFunction, - bytes calldata uninstallData, - bytes calldata preValidationHookUninstallData, - bytes calldata permissionHookUninstallData - ) internal { - ValidationData storage _validationData = getAccountStorage().validationData[validationFunction]; - - _validationData.isGlobal = false; - _validationData.isSignatureValidation = false; - - { - bytes[] memory preValidationHookUninstallDatas = abi.decode(preValidationHookUninstallData, (bytes[])); - - // Clear pre validation hooks - ModuleEntity[] storage preValidationHooks = _validationData.preValidationHooks; - for (uint256 i = 0; i < preValidationHooks.length; ++i) { - ModuleEntity preValidationFunction = preValidationHooks[i]; - if (preValidationHookUninstallDatas[0].length > 0) { - (address preValidationModule,) = ModuleEntityLib.unpack(preValidationFunction); - IModule(preValidationModule).onUninstall(preValidationHookUninstallDatas[0]); - } - } - delete _validationData.preValidationHooks; - } - - { - bytes[] memory permissionHookUninstallDatas = abi.decode(permissionHookUninstallData, (bytes[])); - - // Clear permission hooks - EnumerableSet.Bytes32Set storage permissionHooks = _validationData.permissionHooks; - uint256 permissionHookLen = permissionHooks.length(); - for (uint256 i = 0; i < permissionHookLen; ++i) { - bytes32 permissionHook = permissionHooks.at(0); - permissionHooks.remove(permissionHook); - address permissionHookModule = address(uint160(bytes20(permissionHook))); - IModule(permissionHookModule).onUninstall(permissionHookUninstallDatas[i]); - } - } - - // Clear selectors - uint256 selectorLen = _validationData.selectors.length(); - for (uint256 i = 0; i < selectorLen; ++i) { - bytes32 selectorSetValue = _validationData.selectors.at(0); - _validationData.selectors.remove(selectorSetValue); - } - - if (uninstallData.length > 0) { - (address module,) = ModuleEntityLib.unpack(validationFunction); - IModule(module).onUninstall(uninstallData); - } - } -} diff --git a/src/account/ModuleManagerInternals.sol b/src/account/ModuleManagerInternals.sol index a6a275b6..9fd60c45 100644 --- a/src/account/ModuleManagerInternals.sol +++ b/src/account/ModuleManagerInternals.sol @@ -7,14 +7,22 @@ import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet import {KnownSelectors} from "../helpers/KnownSelectors.sol"; import {ModuleEntityLib} from "../helpers/ModuleEntityLib.sol"; +import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; import {IModule, ManifestExecutionHook, ManifestValidation, ModuleManifest} from "../interfaces/IModule.sol"; -import {IModuleManager, ModuleEntity} from "../interfaces/IModuleManager.sol"; -import {AccountStorage, SelectorData, getAccountStorage, toSetValue} from "./AccountStorage.sol"; +import {IModuleManager, ModuleEntity, ValidationConfig} from "../interfaces/IModuleManager.sol"; +import {AccountStorage, SelectorData, ValidationData, getAccountStorage, toSetValue} from "./AccountStorage.sol"; abstract contract ModuleManagerInternals is IModuleManager { using EnumerableSet for EnumerableSet.Bytes32Set; using ModuleEntityLib for ModuleEntity; + using ValidationConfigLib for ValidationConfig; + + // Index marking the start of the data for the validation function. + uint8 internal constant _RESERVED_VALIDATION_DATA_INDEX = 255; + + // Magic value for the Entity ID of direct call validation. + uint32 internal constant _SELF_PERMIT_VALIDATION_FUNCTIONID = type(uint32).max; error ArrayLengthMismatch(); error Erc4337FunctionNotAllowed(bytes4 selector); @@ -22,10 +30,12 @@ abstract contract ModuleManagerInternals is IModuleManager { error IModuleFunctionNotAllowed(bytes4 selector); error NativeFunctionNotAllowed(bytes4 selector); error NullModule(); + error PermissionAlreadySet(ModuleEntity validationFunction, ExecutionHook hook); error ModuleInstallCallbackFailed(address module, bytes revertReason); error ModuleInterfaceNotSupported(address module); error ModuleNotInstalled(address module); - error ValidationFunctionAlreadySet(bytes4 selector, ModuleEntity validationFunction); + error PreValidationHookLimitExceeded(); + error ValidationAlreadySet(bytes4 selector, ModuleEntity validationFunction); // Storage update operations @@ -71,39 +81,35 @@ abstract contract ModuleManagerInternals is IModuleManager { _selectorData.allowGlobalValidation = false; } - function _addValidationFunction(address module, ManifestValidation memory mv) internal { - AccountStorage storage _storage = getAccountStorage(); + function _addValidationFunction(ValidationConfig validationConfig, bytes4[] memory selectors) internal { + ValidationData storage _validationData = + getAccountStorage().validationData[validationConfig.moduleEntity()]; - ModuleEntity validationFunction = ModuleEntityLib.pack(module, mv.entityId); - - if (mv.isDefault) { - _storage.validationData[validationFunction].isGlobal = true; + if (validationConfig.isGlobal()) { + _validationData.isGlobal = true; } - if (mv.isSignatureValidation) { - _storage.validationData[validationFunction].isSignatureValidation = true; + if (validationConfig.isSignatureValidation()) { + _validationData.isSignatureValidation = true; } // Add the validation function to the selectors. - uint256 length = mv.selectors.length; + uint256 length = selectors.length; for (uint256 i = 0; i < length; ++i) { - bytes4 selector = mv.selectors[i]; - _storage.validationData[validationFunction].selectors.add(toSetValue(selector)); + _validationData.selectors.add(toSetValue(selectors[i])); } } - function _removeValidationFunction(address module, ManifestValidation memory mv) internal { - AccountStorage storage _storage = getAccountStorage(); - - ModuleEntity validationFunction = ModuleEntityLib.pack(module, mv.entityId); + function _removeValidationFunction(ModuleEntity validationFunction) internal { + ValidationData storage _validationData = getAccountStorage().validationData[validationFunction]; - _storage.validationData[validationFunction].isGlobal = false; - _storage.validationData[validationFunction].isSignatureValidation = false; + _validationData.isGlobal = false; + _validationData.isSignatureValidation = false; // Clear the selectors - while (_storage.validationData[validationFunction].selectors.length() > 0) { - bytes32 selector = _storage.validationData[validationFunction].selectors.at(0); - _storage.validationData[validationFunction].selectors.remove(selector); + uint256 length = _validationData.selectors.length(); + for (uint256 i = 0; i < length; ++i) { + _validationData.selectors.remove(_validationData.selectors.at(0)); } } @@ -161,7 +167,11 @@ abstract contract ModuleManagerInternals is IModuleManager { for (uint256 i = 0; i < length; ++i) { // Todo: limit this to only "direct runtime call" validation path (old EFP), // and add a way for the user to specify permission/pre-val hooks here. - _addValidationFunction(module, manifest.validationFunctions[i]); + ManifestValidation memory mv = manifest.validationFunctions[i]; + + ValidationConfig validationConfig = + ValidationConfigLib.pack(module, mv.entityId, mv.isGlobal, mv.isSignatureValidation); + _addValidationFunction(validationConfig, mv.selectors); } length = manifest.executionHooks.length; @@ -204,7 +214,9 @@ abstract contract ModuleManagerInternals is IModuleManager { length = manifest.validationFunctions.length; for (uint256 i = 0; i < length; ++i) { - _removeValidationFunction(module, manifest.validationFunctions[i]); + ModuleEntity validationFunction = + ModuleEntityLib.pack(module, manifest.validationFunctions[i].entityId); + _removeValidationFunction(validationFunction); } length = manifest.executionFunctions.length; @@ -228,4 +240,123 @@ abstract contract ModuleManagerInternals is IModuleManager { emit ModuleUninstalled(module, onUninstallSuccess); } + + function _installValidation( + ValidationConfig validationConfig, + bytes4[] memory selectors, + bytes calldata installData, + bytes memory preValidationHooks, + bytes memory permissionHooks + ) internal { + ValidationData storage _validationData = + getAccountStorage().validationData[validationConfig.moduleEntity()]; + + if (preValidationHooks.length > 0) { + (ModuleEntity[] memory preValidationFunctions, bytes[] memory initDatas) = + abi.decode(preValidationHooks, (ModuleEntity[], bytes[])); + + for (uint256 i = 0; i < preValidationFunctions.length; ++i) { + ModuleEntity preValidationFunction = preValidationFunctions[i]; + + _validationData.preValidationHooks.push(preValidationFunction); + + if (initDatas[i].length > 0) { + (address preValidationPlugin,) = ModuleEntityLib.unpack(preValidationFunction); + IModule(preValidationPlugin).onInstall(initDatas[i]); + } + } + + // Avoid collision between reserved index and actual indices + if (_validationData.preValidationHooks.length > _RESERVED_VALIDATION_DATA_INDEX) { + revert PreValidationHookLimitExceeded(); + } + } + + if (permissionHooks.length > 0) { + (ExecutionHook[] memory permissionFunctions, bytes[] memory initDatas) = + abi.decode(permissionHooks, (ExecutionHook[], bytes[])); + + for (uint256 i = 0; i < permissionFunctions.length; ++i) { + ExecutionHook memory permissionFunction = permissionFunctions[i]; + + if (!_validationData.permissionHooks.add(toSetValue(permissionFunction))) { + revert PermissionAlreadySet(validationConfig.moduleEntity(), permissionFunction); + } + + if (initDatas[i].length > 0) { + (address executionPlugin,) = ModuleEntityLib.unpack(permissionFunction.hookFunction); + IModule(executionPlugin).onInstall(initDatas[i]); + } + } + } + + for (uint256 i = 0; i < selectors.length; ++i) { + bytes4 selector = selectors[i]; + if (!_validationData.selectors.add(toSetValue(selector))) { + revert ValidationAlreadySet(selector, validationConfig.moduleEntity()); + } + } + + if (validationConfig.entityId() != _SELF_PERMIT_VALIDATION_FUNCTIONID) { + // Only allow global validations and signature validations if they're not direct-call validations. + + _validationData.isGlobal = validationConfig.isGlobal(); + _validationData.isSignatureValidation = validationConfig.isSignatureValidation(); + if (installData.length > 0) { + IModule(validationConfig.module()).onInstall(installData); + } + } + } + + function _uninstallValidation( + ModuleEntity validationFunction, + bytes calldata uninstallData, + bytes calldata preValidationHookUninstallData, + bytes calldata permissionHookUninstallData + ) internal { + ValidationData storage _validationData = getAccountStorage().validationData[validationFunction]; + + _removeValidationFunction(validationFunction); + + { + bytes[] memory preValidationHookUninstallDatas = abi.decode(preValidationHookUninstallData, (bytes[])); + + // Clear pre validation hooks + ModuleEntity[] storage preValidationHooks = _validationData.preValidationHooks; + for (uint256 i = 0; i < preValidationHooks.length; ++i) { + ModuleEntity preValidationFunction = preValidationHooks[i]; + if (preValidationHookUninstallDatas[0].length > 0) { + (address preValidationPlugin,) = ModuleEntityLib.unpack(preValidationFunction); + IModule(preValidationPlugin).onUninstall(preValidationHookUninstallDatas[0]); + } + } + delete _validationData.preValidationHooks; + } + + { + bytes[] memory permissionHookUninstallDatas = abi.decode(permissionHookUninstallData, (bytes[])); + + // Clear permission hooks + EnumerableSet.Bytes32Set storage permissionHooks = _validationData.permissionHooks; + uint256 permissionHookLen = permissionHooks.length(); + for (uint256 i = 0; i < permissionHookLen; ++i) { + bytes32 permissionHook = permissionHooks.at(0); + permissionHooks.remove(permissionHook); + address permissionHookPlugin = address(uint160(bytes20(permissionHook))); + IModule(permissionHookPlugin).onUninstall(permissionHookUninstallDatas[i]); + } + } + + // Clear selectors + uint256 selectorLen = _validationData.selectors.length(); + for (uint256 i = 0; i < selectorLen; ++i) { + bytes32 selectorSetValue = _validationData.selectors.at(0); + _validationData.selectors.remove(selectorSetValue); + } + + if (uninstallData.length > 0) { + (address plugin,) = ModuleEntityLib.unpack(validationFunction); + IModule(plugin).onUninstall(uninstallData); + } + } } diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 2f1da16d..737beebb 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -29,7 +29,6 @@ import {AccountLoupe} from "./AccountLoupe.sol"; import {AccountStorage, getAccountStorage, toExecutionHook, toSetValue} from "./AccountStorage.sol"; import {AccountStorageInitializable} from "./AccountStorageInitializable.sol"; -import {ModuleManager2} from "./ModuleManager2.sol"; import {ModuleManagerInternals} from "./ModuleManagerInternals.sol"; contract UpgradeableModularAccount is @@ -42,7 +41,6 @@ contract UpgradeableModularAccount is IStandardExecutor, IAccountExecute, ModuleManagerInternals, - ModuleManager2, UUPSUpgradeable { using EnumerableSet for EnumerableSet.Bytes32Set; diff --git a/src/interfaces/IModule.sol b/src/interfaces/IModule.sol index 8851acaf..ab78b222 100644 --- a/src/interfaces/IModule.sol +++ b/src/interfaces/IModule.sol @@ -16,7 +16,7 @@ struct ManifestExecutionFunction { // todo: do we need these at all? Or do we fully switch to `installValidation`? struct ManifestValidation { uint32 entityId; - bool isDefault; + bool isGlobal; bool isSignatureValidation; bytes4[] selectors; } diff --git a/test/mocks/modules/ComprehensiveModule.sol b/test/mocks/modules/ComprehensiveModule.sol index bb73b5db..aca09f92 100644 --- a/test/mocks/modules/ComprehensiveModule.sol +++ b/test/mocks/modules/ComprehensiveModule.sol @@ -146,7 +146,7 @@ contract ComprehensiveModule is IValidation, IValidationHook, IExecutionHook, Ba manifest.validationFunctions = new ManifestValidation[](1); manifest.validationFunctions[0] = ManifestValidation({ entityId: uint32(EntityId.VALIDATION), - isDefault: true, + isGlobal: true, isSignatureValidation: false, selectors: validationSelectors }); diff --git a/test/mocks/modules/ReturnDataModuleMocks.sol b/test/mocks/modules/ReturnDataModuleMocks.sol index 8fe3241d..1f219e8b 100644 --- a/test/mocks/modules/ReturnDataModuleMocks.sol +++ b/test/mocks/modules/ReturnDataModuleMocks.sol @@ -126,7 +126,7 @@ contract ResultConsumerModule is BaseModule, IValidation { manifest.validationFunctions = new ManifestValidation[](1); manifest.validationFunctions[0] = ManifestValidation({ entityId: 0, - isDefault: true, + isGlobal: true, isSignatureValidation: false, selectors: validationSelectors }); diff --git a/test/mocks/modules/ValidationModuleMocks.sol b/test/mocks/modules/ValidationModuleMocks.sol index ed152af2..4c37190f 100644 --- a/test/mocks/modules/ValidationModuleMocks.sol +++ b/test/mocks/modules/ValidationModuleMocks.sol @@ -118,7 +118,7 @@ contract MockUserOpValidationModule is MockBaseUserOpValidationModule { manifest.validationFunctions = new ManifestValidation[](1); manifest.validationFunctions[0] = ManifestValidation({ entityId: uint32(EntityId.USER_OP_VALIDATION), - isDefault: false, + isGlobal: false, isSignatureValidation: false, selectors: validationSelectors }); @@ -161,7 +161,7 @@ contract MockUserOpValidation1HookModule is MockBaseUserOpValidationModule { manifest.validationFunctions = new ManifestValidation[](2); manifest.validationFunctions[0] = ManifestValidation({ entityId: uint32(EntityId.USER_OP_VALIDATION), - isDefault: false, + isGlobal: false, isSignatureValidation: false, selectors: validationSelectors }); @@ -207,7 +207,7 @@ contract MockUserOpValidation2HookModule is MockBaseUserOpValidationModule { manifest.validationFunctions = new ManifestValidation[](1); manifest.validationFunctions[0] = ManifestValidation({ entityId: uint32(EntityId.USER_OP_VALIDATION), - isDefault: false, + isGlobal: false, isSignatureValidation: false, selectors: validationSelectors }); From 547c8b4c5c4a2111f9cb7e5d114a333d2430f250 Mon Sep 17 00:00:00 2001 From: howydev <132113803+howydev@users.noreply.github.com> Date: Thu, 18 Jul 2024 15:53:09 -0700 Subject: [PATCH 089/111] feat: add factory --- src/account/AccountFactory.sol | 76 ++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 src/account/AccountFactory.sol diff --git a/src/account/AccountFactory.sol b/src/account/AccountFactory.sol new file mode 100644 index 00000000..4a7e9686 --- /dev/null +++ b/src/account/AccountFactory.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; + +import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; +import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; +import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; + +contract AccountFactory { + UpgradeableModularAccount public accountImplementation; + bytes32 private immutable _PROXY_BYTECODE_HASH; + uint32 public constant UNSTAKE_DELAY = 1 weeks; + IEntryPoint public entryPoint; + address public self; + + constructor(IEntryPoint _entryPoint, UpgradeableModularAccount _accountImpl) { + entryPoint = _entryPoint; + _PROXY_BYTECODE_HASH = keccak256( + abi.encodePacked(type(ERC1967Proxy).creationCode, abi.encode(address(accountImplementation), "")) + ); + accountImplementation = _accountImpl; + self = address(this); + } + + /** + * create an account, and return its address. + * returns the address even if the account is already deployed. + * Note that during user operation execution, this method is called only if the account is not deployed. + * This method returns an existing account address so that entryPoint.getSenderAddress() would work even after + * account creation + */ + function createAccount( + address owner, + uint256 salt, + uint32 entityId, + SingleSignerValidation singleSignerValidation + ) public returns (UpgradeableModularAccount) { + address addr = Create2.computeAddress(getSalt(owner, salt), _PROXY_BYTECODE_HASH); + + // short circuit if exists + if (addr.code.length == 0) { + bytes memory pluginInstallData = abi.encode(entityId, owner); + // not necessary to check return addr since next call will fail if so + new ERC1967Proxy{salt: getSalt(owner, salt)}(address(accountImplementation), ""); + + // point proxy to actual implementation and init plugins + UpgradeableModularAccount(payable(addr)).initializeWithValidation( + ValidationConfigLib.pack(address(singleSignerValidation), entityId, true, true), + new bytes4[](0), + pluginInstallData, + "", + "" + ); + } + + return UpgradeableModularAccount(payable(addr)); + } + + /** + * alculate the counterfactual address of this account as it would be returned by createAccount() + */ + function getAddress(address owner, uint256 salt) public view returns (address) { + return Create2.computeAddress(getSalt(owner, salt), _PROXY_BYTECODE_HASH); + } + + function addStake() external payable { + entryPoint.addStake{value: msg.value}(UNSTAKE_DELAY); + } + + function getSalt(address owner, uint256 salt) public pure returns (bytes32) { + return keccak256(abi.encodePacked(owner, salt)); + } +} From 0fa034aba9173a49d96d0109634dd71e8fb965ba Mon Sep 17 00:00:00 2001 From: howydev <132113803+howydev@users.noreply.github.com> Date: Thu, 18 Jul 2024 15:57:48 -0700 Subject: [PATCH 090/111] update: add ownable, access control --- src/account/AccountFactory.sol | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/account/AccountFactory.sol b/src/account/AccountFactory.sol index 4a7e9686..362c6ad7 100644 --- a/src/account/AccountFactory.sol +++ b/src/account/AccountFactory.sol @@ -4,12 +4,13 @@ pragma solidity ^0.8.19; import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; -contract AccountFactory { +contract AccountFactory is Ownable { UpgradeableModularAccount public accountImplementation; bytes32 private immutable _PROXY_BYTECODE_HASH; uint32 public constant UNSTAKE_DELAY = 1 weeks; @@ -66,10 +67,18 @@ contract AccountFactory { return Create2.computeAddress(getSalt(owner, salt), _PROXY_BYTECODE_HASH); } - function addStake() external payable { + function addStake() external payable onlyOwner { entryPoint.addStake{value: msg.value}(UNSTAKE_DELAY); } + function unlockStake() external onlyOwner { + entryPoint.unlockStake(); + } + + function withdrawStake(address payable withdrawAddress) external onlyOwner { + entryPoint.withdrawStake(withdrawAddress); + } + function getSalt(address owner, uint256 salt) public pure returns (bytes32) { return keccak256(abi.encodePacked(owner, salt)); } From 182d0244bcadb515cf1ba752778aa2577e57c891 Mon Sep 17 00:00:00 2001 From: howydev <132113803+howydev@users.noreply.github.com> Date: Thu, 18 Jul 2024 16:01:31 -0700 Subject: [PATCH 091/111] chore: comments --- src/account/AccountFactory.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/account/AccountFactory.sol b/src/account/AccountFactory.sol index 362c6ad7..bd9662ee 100644 --- a/src/account/AccountFactory.sol +++ b/src/account/AccountFactory.sol @@ -27,8 +27,8 @@ contract AccountFactory is Ownable { } /** - * create an account, and return its address. - * returns the address even if the account is already deployed. + * Create an account, and return its address. + * Returns the address even if the account is already deployed. * Note that during user operation execution, this method is called only if the account is not deployed. * This method returns an existing account address so that entryPoint.getSenderAddress() would work even after * account creation @@ -61,7 +61,7 @@ contract AccountFactory is Ownable { } /** - * alculate the counterfactual address of this account as it would be returned by createAccount() + * Calculate the counterfactual address of this account as it would be returned by createAccount() */ function getAddress(address owner, uint256 salt) public view returns (address) { return Create2.computeAddress(getSalt(owner, salt), _PROXY_BYTECODE_HASH); From 2a14f7beb1a6edd75e7c72b05b3f99a4b4516263 Mon Sep 17 00:00:00 2001 From: howydev <132113803+howydev@users.noreply.github.com> Date: Thu, 18 Jul 2024 18:50:14 -0700 Subject: [PATCH 092/111] fix: lint --- src/account/AccountFactory.sol | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/account/AccountFactory.sol b/src/account/AccountFactory.sol index bd9662ee..36837087 100644 --- a/src/account/AccountFactory.sol +++ b/src/account/AccountFactory.sol @@ -2,9 +2,10 @@ pragma solidity ^0.8.19; import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; + +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; @@ -17,7 +18,7 @@ contract AccountFactory is Ownable { IEntryPoint public entryPoint; address public self; - constructor(IEntryPoint _entryPoint, UpgradeableModularAccount _accountImpl) { + constructor(IEntryPoint _entryPoint, UpgradeableModularAccount _accountImpl) Ownable(msg.sender) { entryPoint = _entryPoint; _PROXY_BYTECODE_HASH = keccak256( abi.encodePacked(type(ERC1967Proxy).creationCode, abi.encode(address(accountImplementation), "")) @@ -38,7 +39,7 @@ contract AccountFactory is Ownable { uint256 salt, uint32 entityId, SingleSignerValidation singleSignerValidation - ) public returns (UpgradeableModularAccount) { + ) external returns (UpgradeableModularAccount) { address addr = Create2.computeAddress(getSalt(owner, salt), _PROXY_BYTECODE_HASH); // short circuit if exists @@ -60,13 +61,6 @@ contract AccountFactory is Ownable { return UpgradeableModularAccount(payable(addr)); } - /** - * Calculate the counterfactual address of this account as it would be returned by createAccount() - */ - function getAddress(address owner, uint256 salt) public view returns (address) { - return Create2.computeAddress(getSalt(owner, salt), _PROXY_BYTECODE_HASH); - } - function addStake() external payable onlyOwner { entryPoint.addStake{value: msg.value}(UNSTAKE_DELAY); } @@ -79,6 +73,13 @@ contract AccountFactory is Ownable { entryPoint.withdrawStake(withdrawAddress); } + /** + * Calculate the counterfactual address of this account as it would be returned by createAccount() + */ + function getAddress(address owner, uint256 salt) external view returns (address) { + return Create2.computeAddress(getSalt(owner, salt), _PROXY_BYTECODE_HASH); + } + function getSalt(address owner, uint256 salt) public pure returns (bytes32) { return keccak256(abi.encodePacked(owner, salt)); } From ce0484e6cc49620c58015e95baeaf5fee69376a8 Mon Sep 17 00:00:00 2001 From: howydev <132113803+howydev@users.noreply.github.com> Date: Fri, 19 Jul 2024 11:57:25 -0700 Subject: [PATCH 093/111] test: add tests, cleanup --- src/account/AccountFactory.sol | 45 ++++++++++++++++++--------------- test/account/AccountFactory.sol | 37 +++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 20 deletions(-) create mode 100644 test/account/AccountFactory.sol diff --git a/src/account/AccountFactory.sol b/src/account/AccountFactory.sol index 36837087..f1feea53 100644 --- a/src/account/AccountFactory.sol +++ b/src/account/AccountFactory.sol @@ -7,24 +7,21 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; -import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; -import {SingleSignerValidation} from "../../src/plugins/validation/SingleSignerValidation.sol"; +import {UpgradeableModularAccount} from "../account/UpgradeableModularAccount.sol"; +import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; +import {SingleSignerValidation} from "../plugins/validation/SingleSignerValidation.sol"; contract AccountFactory is Ownable { UpgradeableModularAccount public accountImplementation; bytes32 private immutable _PROXY_BYTECODE_HASH; uint32 public constant UNSTAKE_DELAY = 1 weeks; - IEntryPoint public entryPoint; - address public self; + IEntryPoint public immutable ENTRY_POINT; constructor(IEntryPoint _entryPoint, UpgradeableModularAccount _accountImpl) Ownable(msg.sender) { - entryPoint = _entryPoint; - _PROXY_BYTECODE_HASH = keccak256( - abi.encodePacked(type(ERC1967Proxy).creationCode, abi.encode(address(accountImplementation), "")) - ); + ENTRY_POINT = _entryPoint; + _PROXY_BYTECODE_HASH = + keccak256(abi.encodePacked(type(ERC1967Proxy).creationCode, abi.encode(address(_accountImpl), ""))); accountImplementation = _accountImpl; - self = address(this); } /** @@ -40,14 +37,14 @@ contract AccountFactory is Ownable { uint32 entityId, SingleSignerValidation singleSignerValidation ) external returns (UpgradeableModularAccount) { - address addr = Create2.computeAddress(getSalt(owner, salt), _PROXY_BYTECODE_HASH); + bytes32 combinedSalt = getSalt(owner, salt, entityId, address(singleSignerValidation)); + address addr = Create2.computeAddress(combinedSalt, _PROXY_BYTECODE_HASH); // short circuit if exists if (addr.code.length == 0) { bytes memory pluginInstallData = abi.encode(entityId, owner); // not necessary to check return addr since next call will fail if so - new ERC1967Proxy{salt: getSalt(owner, salt)}(address(accountImplementation), ""); - + new ERC1967Proxy{salt: combinedSalt}(address(accountImplementation), ""); // point proxy to actual implementation and init plugins UpgradeableModularAccount(payable(addr)).initializeWithValidation( ValidationConfigLib.pack(address(singleSignerValidation), entityId, true, true), @@ -62,25 +59,33 @@ contract AccountFactory is Ownable { } function addStake() external payable onlyOwner { - entryPoint.addStake{value: msg.value}(UNSTAKE_DELAY); + ENTRY_POINT.addStake{value: msg.value}(UNSTAKE_DELAY); } function unlockStake() external onlyOwner { - entryPoint.unlockStake(); + ENTRY_POINT.unlockStake(); } function withdrawStake(address payable withdrawAddress) external onlyOwner { - entryPoint.withdrawStake(withdrawAddress); + ENTRY_POINT.withdrawStake(withdrawAddress); } /** * Calculate the counterfactual address of this account as it would be returned by createAccount() */ - function getAddress(address owner, uint256 salt) external view returns (address) { - return Create2.computeAddress(getSalt(owner, salt), _PROXY_BYTECODE_HASH); + function getAddress(address owner, uint256 salt, uint32 entityId, address validation) + external + view + returns (address) + { + return Create2.computeAddress(getSalt(owner, salt, entityId, validation), _PROXY_BYTECODE_HASH); } - function getSalt(address owner, uint256 salt) public pure returns (bytes32) { - return keccak256(abi.encodePacked(owner, salt)); + function getSalt(address owner, uint256 salt, uint32 entityId, address validation) + public + pure + returns (bytes32) + { + return keccak256(abi.encodePacked(owner, salt, entityId, validation)); } } diff --git a/test/account/AccountFactory.sol b/test/account/AccountFactory.sol new file mode 100644 index 00000000..ebd3b1e7 --- /dev/null +++ b/test/account/AccountFactory.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {AccountFactory} from "../../src/account/AccountFactory.sol"; +import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; +import {AccountTestBase} from "../utils/AccountTestBase.sol"; + +contract AccountFactoryTest is AccountTestBase { + AccountFactory internal _factory; + UpgradeableModularAccount internal _account; + + function setUp() public { + _account = new UpgradeableModularAccount(entryPoint); + _factory = new AccountFactory(entryPoint, _account); + } + + function test_createAccount() public { + UpgradeableModularAccount account = _factory.createAccount(address(this), 100, 0, singleSignerValidation); + + assertEq(address(account.entryPoint()), address(entryPoint)); + } + + function test_multipleDeploy() public { + UpgradeableModularAccount account = _factory.createAccount(address(this), 100, 0, singleSignerValidation); + + uint256 startGas = gasleft(); + + UpgradeableModularAccount account2 = _factory.createAccount(address(this), 100, 0, singleSignerValidation); + + // Assert that the 2nd deployment call cost less than 1 sstore + // Implies that no deployment was done on the second calls + assertLe(startGas - 22_000, gasleft()); + + // Assert the return addresses are the same + assertEq(address(account), address(account2)); + } +} From d5667b5199411dbf6cf8918d000d64370519b09e Mon Sep 17 00:00:00 2001 From: howydev <132113803+howydev@users.noreply.github.com> Date: Fri, 19 Jul 2024 13:12:22 -0700 Subject: [PATCH 094/111] chore: use immutable single signer validation --- src/account/AccountFactory.sol | 37 +++++++++++++-------------------- test/account/AccountFactory.sol | 8 +++---- 2 files changed, 19 insertions(+), 26 deletions(-) diff --git a/src/account/AccountFactory.sol b/src/account/AccountFactory.sol index f1feea53..cc0368f3 100644 --- a/src/account/AccountFactory.sol +++ b/src/account/AccountFactory.sol @@ -9,19 +9,22 @@ import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; import {UpgradeableModularAccount} from "../account/UpgradeableModularAccount.sol"; import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; -import {SingleSignerValidation} from "../plugins/validation/SingleSignerValidation.sol"; contract AccountFactory is Ownable { UpgradeableModularAccount public accountImplementation; bytes32 private immutable _PROXY_BYTECODE_HASH; uint32 public constant UNSTAKE_DELAY = 1 weeks; IEntryPoint public immutable ENTRY_POINT; + address public immutable SINGLE_SIGNER_VALIDATION; - constructor(IEntryPoint _entryPoint, UpgradeableModularAccount _accountImpl) Ownable(msg.sender) { + constructor(IEntryPoint _entryPoint, UpgradeableModularAccount _accountImpl, address _singleSignerValidation) + Ownable(msg.sender) + { ENTRY_POINT = _entryPoint; _PROXY_BYTECODE_HASH = keccak256(abi.encodePacked(type(ERC1967Proxy).creationCode, abi.encode(address(_accountImpl), ""))); accountImplementation = _accountImpl; + SINGLE_SIGNER_VALIDATION = _singleSignerValidation; } /** @@ -31,13 +34,11 @@ contract AccountFactory is Ownable { * This method returns an existing account address so that entryPoint.getSenderAddress() would work even after * account creation */ - function createAccount( - address owner, - uint256 salt, - uint32 entityId, - SingleSignerValidation singleSignerValidation - ) external returns (UpgradeableModularAccount) { - bytes32 combinedSalt = getSalt(owner, salt, entityId, address(singleSignerValidation)); + function createAccount(address owner, uint256 salt, uint32 entityId) + external + returns (UpgradeableModularAccount) + { + bytes32 combinedSalt = getSalt(owner, salt, entityId); address addr = Create2.computeAddress(combinedSalt, _PROXY_BYTECODE_HASH); // short circuit if exists @@ -47,7 +48,7 @@ contract AccountFactory is Ownable { new ERC1967Proxy{salt: combinedSalt}(address(accountImplementation), ""); // point proxy to actual implementation and init plugins UpgradeableModularAccount(payable(addr)).initializeWithValidation( - ValidationConfigLib.pack(address(singleSignerValidation), entityId, true, true), + ValidationConfigLib.pack(SINGLE_SIGNER_VALIDATION, entityId, true, true), new bytes4[](0), pluginInstallData, "", @@ -73,19 +74,11 @@ contract AccountFactory is Ownable { /** * Calculate the counterfactual address of this account as it would be returned by createAccount() */ - function getAddress(address owner, uint256 salt, uint32 entityId, address validation) - external - view - returns (address) - { - return Create2.computeAddress(getSalt(owner, salt, entityId, validation), _PROXY_BYTECODE_HASH); + function getAddress(address owner, uint256 salt, uint32 entityId) external view returns (address) { + return Create2.computeAddress(getSalt(owner, salt, entityId), _PROXY_BYTECODE_HASH); } - function getSalt(address owner, uint256 salt, uint32 entityId, address validation) - public - pure - returns (bytes32) - { - return keccak256(abi.encodePacked(owner, salt, entityId, validation)); + function getSalt(address owner, uint256 salt, uint32 entityId) public pure returns (bytes32) { + return keccak256(abi.encodePacked(owner, salt, entityId)); } } diff --git a/test/account/AccountFactory.sol b/test/account/AccountFactory.sol index ebd3b1e7..363725ca 100644 --- a/test/account/AccountFactory.sol +++ b/test/account/AccountFactory.sol @@ -11,21 +11,21 @@ contract AccountFactoryTest is AccountTestBase { function setUp() public { _account = new UpgradeableModularAccount(entryPoint); - _factory = new AccountFactory(entryPoint, _account); + _factory = new AccountFactory(entryPoint, _account, address(singleSignerValidation)); } function test_createAccount() public { - UpgradeableModularAccount account = _factory.createAccount(address(this), 100, 0, singleSignerValidation); + UpgradeableModularAccount account = _factory.createAccount(address(this), 100, 0); assertEq(address(account.entryPoint()), address(entryPoint)); } function test_multipleDeploy() public { - UpgradeableModularAccount account = _factory.createAccount(address(this), 100, 0, singleSignerValidation); + UpgradeableModularAccount account = _factory.createAccount(address(this), 100, 0); uint256 startGas = gasleft(); - UpgradeableModularAccount account2 = _factory.createAccount(address(this), 100, 0, singleSignerValidation); + UpgradeableModularAccount account2 = _factory.createAccount(address(this), 100, 0); // Assert that the 2nd deployment call cost less than 1 sstore // Implies that no deployment was done on the second calls From 125920fa51e5ccaf3267f1ba569346386c4a96cb Mon Sep 17 00:00:00 2001 From: howydev <132113803+howydev@users.noreply.github.com> Date: Mon, 22 Jul 2024 13:45:23 -0700 Subject: [PATCH 095/111] fix: pr review fixes --- src/account/AccountFactory.sol | 6 +++--- test/account/{AccountFactory.sol => AccountFactory.t.sol} | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) rename test/account/{AccountFactory.sol => AccountFactory.t.sol} (85%) diff --git a/src/account/AccountFactory.sol b/src/account/AccountFactory.sol index cc0368f3..67fcd8c6 100644 --- a/src/account/AccountFactory.sol +++ b/src/account/AccountFactory.sol @@ -11,7 +11,7 @@ import {UpgradeableModularAccount} from "../account/UpgradeableModularAccount.so import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; contract AccountFactory is Ownable { - UpgradeableModularAccount public accountImplementation; + UpgradeableModularAccount public immutable ACCOUNT_IMPL; bytes32 private immutable _PROXY_BYTECODE_HASH; uint32 public constant UNSTAKE_DELAY = 1 weeks; IEntryPoint public immutable ENTRY_POINT; @@ -23,7 +23,7 @@ contract AccountFactory is Ownable { ENTRY_POINT = _entryPoint; _PROXY_BYTECODE_HASH = keccak256(abi.encodePacked(type(ERC1967Proxy).creationCode, abi.encode(address(_accountImpl), ""))); - accountImplementation = _accountImpl; + ACCOUNT_IMPL = _accountImpl; SINGLE_SIGNER_VALIDATION = _singleSignerValidation; } @@ -45,7 +45,7 @@ contract AccountFactory is Ownable { if (addr.code.length == 0) { bytes memory pluginInstallData = abi.encode(entityId, owner); // not necessary to check return addr since next call will fail if so - new ERC1967Proxy{salt: combinedSalt}(address(accountImplementation), ""); + new ERC1967Proxy{salt: combinedSalt}(address(ACCOUNT_IMPL), ""); // point proxy to actual implementation and init plugins UpgradeableModularAccount(payable(addr)).initializeWithValidation( ValidationConfigLib.pack(SINGLE_SIGNER_VALIDATION, entityId, true, true), diff --git a/test/account/AccountFactory.sol b/test/account/AccountFactory.t.sol similarity index 85% rename from test/account/AccountFactory.sol rename to test/account/AccountFactory.t.sol index 363725ca..6e79b1c3 100644 --- a/test/account/AccountFactory.sol +++ b/test/account/AccountFactory.t.sol @@ -20,6 +20,12 @@ contract AccountFactoryTest is AccountTestBase { assertEq(address(account.entryPoint()), address(entryPoint)); } + function test_createAccountAndGetAddress() public { + UpgradeableModularAccount account = _factory.createAccount(address(this), 100, 0); + + assertEq(address(account), address(_factory.createAccount(address(this), 100, 0))); + } + function test_multipleDeploy() public { UpgradeableModularAccount account = _factory.createAccount(address(this), 100, 0); From 16b6e97e47ac1ad331021bfa928ca0692c2b581f Mon Sep 17 00:00:00 2001 From: Adam Egyed <5456061+adamegyed@users.noreply.github.com> Date: Tue, 23 Jul 2024 15:59:02 -0400 Subject: [PATCH 096/111] feat: [v0.8-develop] Remove validation installation from the manifest 3/N (#104) --- src/account/ModuleManagerInternals.sol | 51 ++++++-------------- src/account/UpgradeableModularAccount.sol | 30 ++---------- src/helpers/Constants.sol | 11 +++++ src/interfaces/IModule.sol | 9 ---- test/account/AccountLoupe.t.sol | 16 +++--- test/account/AccountReturnData.t.sol | 13 +++++ test/account/SelfCallAuthorization.t.sol | 12 +++-- test/account/ValidationIntersection.t.sol | 39 ++++++++------- test/mocks/modules/ComprehensiveModule.sol | 12 ----- test/mocks/modules/ReturnDataModuleMocks.sol | 24 ++------- test/mocks/modules/ValidationModuleMocks.sol | 40 +-------------- 11 files changed, 86 insertions(+), 171 deletions(-) create mode 100644 src/helpers/Constants.sol diff --git a/src/account/ModuleManagerInternals.sol b/src/account/ModuleManagerInternals.sol index 9fd60c45..5eeeb77b 100644 --- a/src/account/ModuleManagerInternals.sol +++ b/src/account/ModuleManagerInternals.sol @@ -5,11 +5,12 @@ import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165C import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import {DIRECT_CALL_VALIDATION_ENTITYID, MAX_PRE_VALIDATION_HOOKS} from "../helpers/Constants.sol"; import {KnownSelectors} from "../helpers/KnownSelectors.sol"; import {ModuleEntityLib} from "../helpers/ModuleEntityLib.sol"; import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; -import {IModule, ManifestExecutionHook, ManifestValidation, ModuleManifest} from "../interfaces/IModule.sol"; +import {IModule, ManifestExecutionHook, ModuleManifest} from "../interfaces/IModule.sol"; import {IModuleManager, ModuleEntity, ValidationConfig} from "../interfaces/IModuleManager.sol"; import {AccountStorage, SelectorData, ValidationData, getAccountStorage, toSetValue} from "./AccountStorage.sol"; @@ -18,12 +19,6 @@ abstract contract ModuleManagerInternals is IModuleManager { using ModuleEntityLib for ModuleEntity; using ValidationConfigLib for ValidationConfig; - // Index marking the start of the data for the validation function. - uint8 internal constant _RESERVED_VALIDATION_DATA_INDEX = 255; - - // Magic value for the Entity ID of direct call validation. - uint32 internal constant _SELF_PERMIT_VALIDATION_FUNCTIONID = type(uint32).max; - error ArrayLengthMismatch(); error Erc4337FunctionNotAllowed(bytes4 selector); error ExecutionFunctionAlreadySet(bytes4 selector); @@ -163,17 +158,6 @@ abstract contract ModuleManagerInternals is IModuleManager { _setExecutionFunction(selector, isPublic, allowGlobalValidation, module); } - length = manifest.validationFunctions.length; - for (uint256 i = 0; i < length; ++i) { - // Todo: limit this to only "direct runtime call" validation path (old EFP), - // and add a way for the user to specify permission/pre-val hooks here. - ManifestValidation memory mv = manifest.validationFunctions[i]; - - ValidationConfig validationConfig = - ValidationConfigLib.pack(module, mv.entityId, mv.isGlobal, mv.isSignatureValidation); - _addValidationFunction(validationConfig, mv.selectors); - } - length = manifest.executionHooks.length; for (uint256 i = 0; i < length; ++i) { ManifestExecutionHook memory mh = manifest.executionHooks[i]; @@ -212,13 +196,6 @@ abstract contract ModuleManagerInternals is IModuleManager { _removeExecHooks(execHooks, hookFunction, mh.isPreHook, mh.isPostHook); } - length = manifest.validationFunctions.length; - for (uint256 i = 0; i < length; ++i) { - ModuleEntity validationFunction = - ModuleEntityLib.pack(module, manifest.validationFunctions[i].entityId); - _removeValidationFunction(validationFunction); - } - length = manifest.executionFunctions.length; for (uint256 i = 0; i < length; ++i) { bytes4 selector = manifest.executionFunctions[i].executionSelector; @@ -261,13 +238,13 @@ abstract contract ModuleManagerInternals is IModuleManager { _validationData.preValidationHooks.push(preValidationFunction); if (initDatas[i].length > 0) { - (address preValidationPlugin,) = ModuleEntityLib.unpack(preValidationFunction); - IModule(preValidationPlugin).onInstall(initDatas[i]); + (address preValidationModule,) = ModuleEntityLib.unpack(preValidationFunction); + IModule(preValidationModule).onInstall(initDatas[i]); } } // Avoid collision between reserved index and actual indices - if (_validationData.preValidationHooks.length > _RESERVED_VALIDATION_DATA_INDEX) { + if (_validationData.preValidationHooks.length > MAX_PRE_VALIDATION_HOOKS) { revert PreValidationHookLimitExceeded(); } } @@ -284,8 +261,8 @@ abstract contract ModuleManagerInternals is IModuleManager { } if (initDatas[i].length > 0) { - (address executionPlugin,) = ModuleEntityLib.unpack(permissionFunction.hookFunction); - IModule(executionPlugin).onInstall(initDatas[i]); + (address executionModule,) = ModuleEntityLib.unpack(permissionFunction.hookFunction); + IModule(executionModule).onInstall(initDatas[i]); } } } @@ -297,7 +274,7 @@ abstract contract ModuleManagerInternals is IModuleManager { } } - if (validationConfig.entityId() != _SELF_PERMIT_VALIDATION_FUNCTIONID) { + if (validationConfig.entityId() != DIRECT_CALL_VALIDATION_ENTITYID) { // Only allow global validations and signature validations if they're not direct-call validations. _validationData.isGlobal = validationConfig.isGlobal(); @@ -326,8 +303,8 @@ abstract contract ModuleManagerInternals is IModuleManager { for (uint256 i = 0; i < preValidationHooks.length; ++i) { ModuleEntity preValidationFunction = preValidationHooks[i]; if (preValidationHookUninstallDatas[0].length > 0) { - (address preValidationPlugin,) = ModuleEntityLib.unpack(preValidationFunction); - IModule(preValidationPlugin).onUninstall(preValidationHookUninstallDatas[0]); + (address preValidationModule,) = ModuleEntityLib.unpack(preValidationFunction); + IModule(preValidationModule).onUninstall(preValidationHookUninstallDatas[0]); } } delete _validationData.preValidationHooks; @@ -342,8 +319,8 @@ abstract contract ModuleManagerInternals is IModuleManager { for (uint256 i = 0; i < permissionHookLen; ++i) { bytes32 permissionHook = permissionHooks.at(0); permissionHooks.remove(permissionHook); - address permissionHookPlugin = address(uint160(bytes20(permissionHook))); - IModule(permissionHookPlugin).onUninstall(permissionHookUninstallDatas[i]); + address permissionHookModule = address(uint160(bytes20(permissionHook))); + IModule(permissionHookModule).onUninstall(permissionHookUninstallDatas[i]); } } @@ -355,8 +332,8 @@ abstract contract ModuleManagerInternals is IModuleManager { } if (uninstallData.length > 0) { - (address plugin,) = ModuleEntityLib.unpack(validationFunction); - IModule(plugin).onUninstall(uninstallData); + (address module,) = ModuleEntityLib.unpack(validationFunction); + IModule(module).onUninstall(uninstallData); } } } diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 737beebb..87379241 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -18,6 +18,7 @@ import {SparseCalldataSegmentLib} from "../helpers/SparseCalldataSegmentLib.sol" import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; import {_coalescePreValidation, _coalesceValidation} from "../helpers/ValidationResHelpers.sol"; +import {DIRECT_CALL_VALIDATION_ENTITYID, RESERVED_VALIDATION_DATA_INDEX} from "../helpers/Constants.sol"; import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; import {ModuleManifest} from "../interfaces/IModule.sol"; import {IModuleManager, ModuleEntity, ValidationConfig} from "../interfaces/IModuleManager.sol"; @@ -28,7 +29,6 @@ import {AccountExecutor} from "./AccountExecutor.sol"; import {AccountLoupe} from "./AccountLoupe.sol"; import {AccountStorage, getAccountStorage, toExecutionHook, toSetValue} from "./AccountStorage.sol"; import {AccountStorageInitializable} from "./AccountStorageInitializable.sol"; - import {ModuleManagerInternals} from "./ModuleManagerInternals.sol"; contract UpgradeableModularAccount is @@ -105,28 +105,6 @@ contract UpgradeableModularAccount is // EXTERNAL FUNCTIONS - /// @notice Initializes the account with a set of modules - /// @param modules The modules to install - /// @param manifests The manifests of the modules to install - /// @param moduleInstallDatas The module install datas of the modules to install - function initialize( - address[] memory modules, - ModuleManifest[] calldata manifests, - bytes[] memory moduleInstallDatas - ) external initializer { - uint256 length = modules.length; - - if (length != manifests.length || length != moduleInstallDatas.length) { - revert ArrayLengthMismatch(); - } - - for (uint256 i = 0; i < length; ++i) { - _installModule(modules[i], manifests[i], moduleInstallDatas[i]); - } - - emit ModularAccountInitialized(_ENTRY_POINT); - } - receive() external payable {} /// @notice Fallback function @@ -441,7 +419,7 @@ contract UpgradeableModularAccount is // Run the user op validationFunction { - if (signatureSegment.getIndex() != _RESERVED_VALIDATION_DATA_INDEX) { + if (signatureSegment.getIndex() != RESERVED_VALIDATION_DATA_INDEX) { revert ValidationSignatureSegmentMissing(); } @@ -497,7 +475,7 @@ contract UpgradeableModularAccount is _doPreRuntimeValidationHook(preRuntimeValidationHooks[i], callData, currentAuthData); } - if (authSegment.getIndex() != _RESERVED_VALIDATION_DATA_INDEX) { + if (authSegment.getIndex() != RESERVED_VALIDATION_DATA_INDEX) { revert ValidationSignatureSegmentMissing(); } @@ -635,7 +613,7 @@ contract UpgradeableModularAccount is return (new PostExecToRun[](0), new PostExecToRun[](0)); } - ModuleEntity directCallValidationKey = ModuleEntityLib.pack(msg.sender, _SELF_PERMIT_VALIDATION_FUNCTIONID); + ModuleEntity directCallValidationKey = ModuleEntityLib.pack(msg.sender, DIRECT_CALL_VALIDATION_ENTITYID); _checkIfValidationAppliesCallData(msg.data, directCallValidationKey, false); diff --git a/src/helpers/Constants.sol b/src/helpers/Constants.sol new file mode 100644 index 00000000..4ad649c1 --- /dev/null +++ b/src/helpers/Constants.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.25; + +// Index marking the start of the data for the validation function. +uint8 constant RESERVED_VALIDATION_DATA_INDEX = type(uint8).max; + +// Maximum number of pre-validation hooks that can be registered. +uint8 constant MAX_PRE_VALIDATION_HOOKS = type(uint8).max; + +// Magic value for the Entity ID of direct call validation. +uint32 constant DIRECT_CALL_VALIDATION_ENTITYID = type(uint32).max; diff --git a/src/interfaces/IModule.sol b/src/interfaces/IModule.sol index ab78b222..87272404 100644 --- a/src/interfaces/IModule.sol +++ b/src/interfaces/IModule.sol @@ -13,14 +13,6 @@ struct ManifestExecutionFunction { bool allowGlobalValidation; } -// todo: do we need these at all? Or do we fully switch to `installValidation`? -struct ManifestValidation { - uint32 entityId; - bool isGlobal; - bool isSignatureValidation; - bytes4[] selectors; -} - struct ManifestExecutionHook { // TODO(erc6900 spec): These fields can be packed into a single word bytes4 executionSelector; @@ -54,7 +46,6 @@ struct ModuleMetadata { struct ModuleManifest { // Execution functions defined in this module to be installed on the MSCA. ManifestExecutionFunction[] executionFunctions; - ManifestValidation[] validationFunctions; ManifestExecutionHook[] executionHooks; // List of ERC-165 interface IDs to add to account to support introspection checks. This MUST NOT include // IModule's interface ID. diff --git a/test/account/AccountLoupe.t.sol b/test/account/AccountLoupe.t.sol index c10540fb..8b03d86f 100644 --- a/test/account/AccountLoupe.t.sol +++ b/test/account/AccountLoupe.t.sol @@ -16,8 +16,12 @@ contract AccountLoupeTest is CustomValidationTestBase { event ReceivedCall(bytes msgData, uint256 msgValue); + ModuleEntity public comprehensiveModuleValidation; + function setUp() public { comprehensiveModule = new ComprehensiveModule(); + comprehensiveModuleValidation = + ModuleEntityLib.pack(address(comprehensiveModule), uint32(ComprehensiveModule.EntityId.VALIDATION)); _customValidationSetup(); @@ -61,9 +65,6 @@ contract AccountLoupeTest is CustomValidationTestBase { } function test_moduleLoupe_getSelectors() public { - ModuleEntity comprehensiveModuleValidation = - ModuleEntityLib.pack(address(comprehensiveModule), uint32(ComprehensiveModule.EntityId.VALIDATION)); - bytes4[] memory selectors = account1.getSelectors(comprehensiveModuleValidation); assertEq(selectors.length, 1); @@ -107,7 +108,7 @@ contract AccountLoupeTest is CustomValidationTestBase { } function test_moduleLoupe_getValidationHooks() public { - ModuleEntity[] memory hooks = account1.getPreValidationHooks(_signerValidation); + ModuleEntity[] memory hooks = account1.getPreValidationHooks(comprehensiveModuleValidation); assertEq(hooks.length, 2); assertEq( @@ -144,12 +145,15 @@ contract AccountLoupeTest is CustomValidationTestBase { address(comprehensiveModule), uint32(ComprehensiveModule.EntityId.PRE_VALIDATION_HOOK_2) ); + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = comprehensiveModule.foo.selector; + bytes[] memory installDatas = new bytes[](2); return ( - _signerValidation, + comprehensiveModuleValidation, true, true, - new bytes4[](0), + selectors, bytes(""), abi.encode(preValidationHooks, installDatas), "" diff --git a/test/account/AccountReturnData.t.sol b/test/account/AccountReturnData.t.sol index 7d51e9d1..d6834af6 100644 --- a/test/account/AccountReturnData.t.sol +++ b/test/account/AccountReturnData.t.sol @@ -1,8 +1,11 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; +import {DIRECT_CALL_VALIDATION_ENTITYID} from "../../src/helpers/Constants.sol"; import {ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; +import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import {Call} from "../../src/interfaces/IStandardExecutor.sol"; +import {IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; import { RegularResultContract, @@ -38,6 +41,16 @@ contract AccountReturnDataTest is AccountTestBase { manifest: resultConsumerModule.moduleManifest(), moduleInstallData: "" }); + // Allow the result consumer module to perform direct calls to the account + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = IStandardExecutor.execute.selector; + account1.installValidation( + ValidationConfigLib.pack(address(resultConsumerModule), DIRECT_CALL_VALIDATION_ENTITYID, false, false), + selectors, + "", + "", + "" + ); vm.stopPrank(); } diff --git a/test/account/SelfCallAuthorization.t.sol b/test/account/SelfCallAuthorization.t.sol index a9f61905..4dd8a595 100644 --- a/test/account/SelfCallAuthorization.t.sol +++ b/test/account/SelfCallAuthorization.t.sol @@ -24,12 +24,18 @@ contract SelfCallAuthorizationTest is AccountTestBase { comprehensiveModule = new ComprehensiveModule(); + comprehensiveModuleValidation = + ModuleEntityLib.pack(address(comprehensiveModule), uint32(ComprehensiveModule.EntityId.VALIDATION)); + + bytes4[] memory validationSelectors = new bytes4[](1); + validationSelectors[0] = ComprehensiveModule.foo.selector; + vm.startPrank(address(entryPoint)); account1.installModule(address(comprehensiveModule), comprehensiveModule.moduleManifest(), ""); + account1.installValidation( + ValidationConfigLib.pack(comprehensiveModuleValidation, false, false), validationSelectors, "", "", "" + ); vm.stopPrank(); - - comprehensiveModuleValidation = - ModuleEntityLib.pack(address(comprehensiveModule), uint32(ComprehensiveModule.EntityId.VALIDATION)); } function test_selfCallFails_userOp() public { diff --git a/test/account/ValidationIntersection.t.sol b/test/account/ValidationIntersection.t.sol index 9662702f..c9c1a882 100644 --- a/test/account/ValidationIntersection.t.sol +++ b/test/account/ValidationIntersection.t.sol @@ -46,19 +46,21 @@ contract ValidationIntersectionTest is AccountTestBase { entityId: uint32(MockBaseUserOpValidationModule.EntityId.USER_OP_VALIDATION) }); + bytes4[] memory validationSelectors = new bytes4[](1); + validationSelectors[0] = MockUserOpValidationModule.foo.selector; + vm.startPrank(address(entryPoint)); - account1.installModule({ - module: address(noHookModule), - manifest: noHookModule.moduleManifest(), - moduleInstallData: "" - }); - account1.installModule({ - module: address(oneHookModule), - manifest: oneHookModule.moduleManifest(), - moduleInstallData: "" - }); - // TODO: change with new install flow - // temporary fix to add the pre-validation hook + // Install noHookValidation + account1.installValidation( + ValidationConfigLib.pack(noHookValidation, true, true), + validationSelectors, + bytes(""), + bytes(""), + bytes("") + ); + + // Install oneHookValidation + validationSelectors[0] = MockUserOpValidation1HookModule.bar.selector; ModuleEntity[] memory preValidationHooks = new ModuleEntity[](1); preValidationHooks[0] = ModuleEntityLib.pack({ addr: address(oneHookModule), @@ -67,17 +69,14 @@ contract ValidationIntersectionTest is AccountTestBase { bytes[] memory installDatas = new bytes[](1); account1.installValidation( ValidationConfigLib.pack(oneHookValidation, true, true), - new bytes4[](0), + validationSelectors, bytes(""), abi.encode(preValidationHooks, installDatas), bytes("") ); - account1.installModule({ - module: address(twoHookModule), - manifest: twoHookModule.moduleManifest(), - moduleInstallData: "" - }); - // temporary fix to add the pre-validation hook + + // Install twoHookValidation + validationSelectors[0] = MockUserOpValidation2HookModule.baz.selector; preValidationHooks = new ModuleEntity[](2); preValidationHooks[0] = ModuleEntityLib.pack({ addr: address(twoHookModule), @@ -90,7 +89,7 @@ contract ValidationIntersectionTest is AccountTestBase { installDatas = new bytes[](2); account1.installValidation( ValidationConfigLib.pack(twoHookValidation, true, true), - new bytes4[](0), + validationSelectors, bytes(""), abi.encode(preValidationHooks, installDatas), bytes("") diff --git a/test/mocks/modules/ComprehensiveModule.sol b/test/mocks/modules/ComprehensiveModule.sol index aca09f92..fe0f6cfe 100644 --- a/test/mocks/modules/ComprehensiveModule.sol +++ b/test/mocks/modules/ComprehensiveModule.sol @@ -7,7 +7,6 @@ import {IExecutionHook} from "../../../src/interfaces/IExecutionHook.sol"; import { ManifestExecutionFunction, ManifestExecutionHook, - ManifestValidation, ModuleManifest, ModuleMetadata } from "../../../src/interfaces/IModule.sol"; @@ -140,17 +139,6 @@ contract ComprehensiveModule is IValidation, IValidationHook, IExecutionHook, Ba allowGlobalValidation: false }); - bytes4[] memory validationSelectors = new bytes4[](1); - validationSelectors[0] = this.foo.selector; - - manifest.validationFunctions = new ManifestValidation[](1); - manifest.validationFunctions[0] = ManifestValidation({ - entityId: uint32(EntityId.VALIDATION), - isGlobal: true, - isSignatureValidation: false, - selectors: validationSelectors - }); - manifest.executionHooks = new ManifestExecutionHook[](3); manifest.executionHooks[0] = ManifestExecutionHook({ executionSelector: this.foo.selector, diff --git a/test/mocks/modules/ReturnDataModuleMocks.sol b/test/mocks/modules/ReturnDataModuleMocks.sol index 1f219e8b..b7baf03e 100644 --- a/test/mocks/modules/ReturnDataModuleMocks.sol +++ b/test/mocks/modules/ReturnDataModuleMocks.sol @@ -3,12 +3,9 @@ pragma solidity ^0.8.19; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; -import { - ManifestExecutionFunction, - ManifestValidation, - ModuleManifest, - ModuleMetadata -} from "../../../src/interfaces/IModule.sol"; +import {ManifestExecutionFunction, ModuleManifest, ModuleMetadata} from "../../../src/interfaces/IModule.sol"; + +import {DIRECT_CALL_VALIDATION_ENTITYID} from "../../../src/helpers/Constants.sol"; import {IStandardExecutor} from "../../../src/interfaces/IStandardExecutor.sol"; import {IValidation} from "../../../src/interfaces/IValidation.sol"; @@ -103,7 +100,8 @@ contract ResultConsumerModule is BaseModule, IValidation { // This result should be allowed based on the manifest permission request bytes memory returnData = IStandardExecutor(msg.sender).executeWithAuthorization( abi.encodeCall(IStandardExecutor.execute, (target, 0, abi.encodeCall(RegularResultContract.foo, ()))), - abi.encodePacked(this, uint32(0), uint8(0), uint32(1), uint8(255)) // Validation function of self, + abi.encodePacked(this, DIRECT_CALL_VALIDATION_ENTITYID, uint8(0), uint32(1), uint8(255)) // Validation + // function of self, // selector-associated, with no auth data ); @@ -119,18 +117,6 @@ contract ResultConsumerModule is BaseModule, IValidation { function moduleManifest() external pure override returns (ModuleManifest memory) { ModuleManifest memory manifest; - // todo: this is the exact workflow that would benefit from a "permiteed call" setup in the manifest. - bytes4[] memory validationSelectors = new bytes4[](1); - validationSelectors[0] = IStandardExecutor.execute.selector; - - manifest.validationFunctions = new ManifestValidation[](1); - manifest.validationFunctions[0] = ManifestValidation({ - entityId: 0, - isGlobal: true, - isSignatureValidation: false, - selectors: validationSelectors - }); - manifest.executionFunctions = new ManifestExecutionFunction[](2); manifest.executionFunctions[0] = ManifestExecutionFunction({ executionSelector: this.checkResultFallback.selector, diff --git a/test/mocks/modules/ValidationModuleMocks.sol b/test/mocks/modules/ValidationModuleMocks.sol index 4c37190f..3186051f 100644 --- a/test/mocks/modules/ValidationModuleMocks.sol +++ b/test/mocks/modules/ValidationModuleMocks.sol @@ -3,12 +3,7 @@ pragma solidity ^0.8.19; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; -import { - ManifestExecutionFunction, - ManifestValidation, - ModuleManifest, - ModuleMetadata -} from "../../../src/interfaces/IModule.sol"; +import {ManifestExecutionFunction, ModuleManifest, ModuleMetadata} from "../../../src/interfaces/IModule.sol"; import {IValidation} from "../../../src/interfaces/IValidation.sol"; import {IValidationHook} from "../../../src/interfaces/IValidationHook.sol"; import {BaseModule} from "../../../src/modules/BaseModule.sol"; @@ -112,17 +107,6 @@ contract MockUserOpValidationModule is MockBaseUserOpValidationModule { allowGlobalValidation: false }); - bytes4[] memory validationSelectors = new bytes4[](1); - validationSelectors[0] = this.foo.selector; - - manifest.validationFunctions = new ManifestValidation[](1); - manifest.validationFunctions[0] = ManifestValidation({ - entityId: uint32(EntityId.USER_OP_VALIDATION), - isGlobal: false, - isSignatureValidation: false, - selectors: validationSelectors - }); - return manifest; } } @@ -155,17 +139,6 @@ contract MockUserOpValidation1HookModule is MockBaseUserOpValidationModule { allowGlobalValidation: false }); - bytes4[] memory validationSelectors = new bytes4[](1); - validationSelectors[0] = this.bar.selector; - - manifest.validationFunctions = new ManifestValidation[](2); - manifest.validationFunctions[0] = ManifestValidation({ - entityId: uint32(EntityId.USER_OP_VALIDATION), - isGlobal: false, - isSignatureValidation: false, - selectors: validationSelectors - }); - return manifest; } } @@ -201,17 +174,6 @@ contract MockUserOpValidation2HookModule is MockBaseUserOpValidationModule { allowGlobalValidation: false }); - bytes4[] memory validationSelectors = new bytes4[](1); - validationSelectors[0] = this.baz.selector; - - manifest.validationFunctions = new ManifestValidation[](1); - manifest.validationFunctions[0] = ManifestValidation({ - entityId: uint32(EntityId.USER_OP_VALIDATION), - isGlobal: false, - isSignatureValidation: false, - selectors: validationSelectors - }); - return manifest; } } From 6d928b6666bd42a32fed131177f992b2ca4cad8a Mon Sep 17 00:00:00 2001 From: Adam Egyed <5456061+adamegyed@users.noreply.github.com> Date: Wed, 24 Jul 2024 18:59:33 -0400 Subject: [PATCH 097/111] feat: [v0.8-develop] HookConfig install parameter & internal structure 4/N (#109) --- foundry.toml | 2 +- src/account/AccountFactory.sol | 3 +- src/account/AccountLoupe.sol | 22 ++- src/account/AccountStorage.sol | 18 +- src/account/ModuleManagerInternals.sol | 198 +++++++++++----------- src/account/UpgradeableModularAccount.sol | 41 ++--- src/helpers/HookConfigLib.sol | 140 +++++++++++++++ src/interfaces/IModuleManager.sol | 21 +-- test/account/AccountLoupe.t.sol | 28 ++- test/account/AccountReturnData.t.sol | 3 +- test/account/DirectCallsFromModule.t.sol | 18 +- test/account/MultiValidation.t.sol | 3 +- test/account/PerHookData.t.sol | 26 ++- test/account/SelfCallAuthorization.t.sol | 12 +- test/account/ValidationIntersection.t.sol | 53 +++--- test/libraries/HookConfigLib.t.sol | 67 ++++++++ test/mocks/SingleSignerFactoryFixture.sol | 3 +- test/module/AllowlistModule.t.sol | 27 ++- test/module/ERC20TokenLimitModule.t.sol | 14 +- test/module/NativeTokenLimitModule.t.sol | 25 ++- test/module/SingleSignerValidation.t.sol | 3 +- test/utils/CustomValidationTestBase.sol | 9 +- 22 files changed, 454 insertions(+), 282 deletions(-) create mode 100644 src/helpers/HookConfigLib.sol create mode 100644 test/libraries/HookConfigLib.t.sol diff --git a/foundry.toml b/foundry.toml index 2aa76ba2..0252d4e5 100644 --- a/foundry.toml +++ b/foundry.toml @@ -38,7 +38,7 @@ runs = 5000 depth = 32 [profile.deep.fuzz] -runs = 10000 +runs = 100000 [profile.deep.invariant] runs = 5000 diff --git a/src/account/AccountFactory.sol b/src/account/AccountFactory.sol index 67fcd8c6..ca15e815 100644 --- a/src/account/AccountFactory.sol +++ b/src/account/AccountFactory.sol @@ -51,8 +51,7 @@ contract AccountFactory is Ownable { ValidationConfigLib.pack(SINGLE_SIGNER_VALIDATION, entityId, true, true), new bytes4[](0), pluginInstallData, - "", - "" + new bytes[](0) ); } diff --git a/src/account/AccountLoupe.sol b/src/account/AccountLoupe.sol index f0afc1b2..bcd6dbcd 100644 --- a/src/account/AccountLoupe.sol +++ b/src/account/AccountLoupe.sol @@ -6,14 +6,16 @@ import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeab import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import {HookConfigLib} from "../helpers/HookConfigLib.sol"; import {ExecutionHook, IAccountLoupe} from "../interfaces/IAccountLoupe.sol"; -import {IModuleManager, ModuleEntity} from "../interfaces/IModuleManager.sol"; +import {HookConfig, IModuleManager, ModuleEntity} from "../interfaces/IModuleManager.sol"; import {IStandardExecutor} from "../interfaces/IStandardExecutor.sol"; -import {getAccountStorage, toExecutionHook, toSelector} from "./AccountStorage.sol"; +import {getAccountStorage, toHookConfig, toSelector} from "./AccountStorage.sol"; abstract contract AccountLoupe is IAccountLoupe { using EnumerableSet for EnumerableSet.Bytes32Set; using EnumerableMap for EnumerableMap.AddressToUintMap; + using HookConfigLib for HookConfig; /// @inheritdoc IAccountLoupe function getExecutionFunctionHandler(bytes4 selector) external view override returns (address module) { @@ -56,8 +58,12 @@ abstract contract AccountLoupe is IAccountLoupe { for (uint256 i = 0; i < executionHooksLength; ++i) { bytes32 key = hooks.at(i); - ExecutionHook memory execHook = execHooks[i]; - (execHook.hookFunction, execHook.isPreHook, execHook.isPostHook) = toExecutionHook(key); + HookConfig hookConfig = toHookConfig(key); + execHooks[i] = ExecutionHook({ + hookFunction: hookConfig.moduleEntity(), + isPreHook: hookConfig.hasPreHook(), + isPostHook: hookConfig.hasPostHook() + }); } } @@ -74,8 +80,12 @@ abstract contract AccountLoupe is IAccountLoupe { permissionHooks = new ExecutionHook[](executionHooksLength); for (uint256 i = 0; i < executionHooksLength; ++i) { bytes32 key = hooks.at(i); - ExecutionHook memory execHook = permissionHooks[i]; - (execHook.hookFunction, execHook.isPreHook, execHook.isPostHook) = toExecutionHook(key); + HookConfig hookConfig = toHookConfig(key); + permissionHooks[i] = ExecutionHook({ + hookFunction: hookConfig.moduleEntity(), + isPreHook: hookConfig.hasPreHook(), + isPostHook: hookConfig.hasPostHook() + }); } } diff --git a/src/account/AccountStorage.sol b/src/account/AccountStorage.sol index c9845a8e..f7d279e4 100644 --- a/src/account/AccountStorage.sol +++ b/src/account/AccountStorage.sol @@ -3,8 +3,7 @@ pragma solidity ^0.8.25; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; -import {ModuleEntity} from "../interfaces/IModuleManager.sol"; +import {HookConfig, ModuleEntity} from "../interfaces/IModuleManager.sol"; // bytes = keccak256("ERC6900.UpgradeableModularAccount.Storage") bytes32 constant _ACCOUNT_STORAGE_SLOT = 0x9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e40; @@ -70,19 +69,12 @@ function toModuleEntity(bytes32 setValue) pure returns (ModuleEntity) { // 0x________________________________________________AA____________________ is pre hook // 0x__________________________________________________BB__________________ is post hook -function toSetValue(ExecutionHook memory executionHook) pure returns (bytes32) { - return bytes32(ModuleEntity.unwrap(executionHook.hookFunction)) - | bytes32(executionHook.isPreHook ? uint256(1) << 56 : 0) - | bytes32(executionHook.isPostHook ? uint256(1) << 48 : 0); +function toSetValue(HookConfig hookConfig) pure returns (bytes32) { + return bytes32(HookConfig.unwrap(hookConfig)); } -function toExecutionHook(bytes32 setValue) - pure - returns (ModuleEntity hookFunction, bool isPreHook, bool isPostHook) -{ - hookFunction = ModuleEntity.wrap(bytes24(setValue)); - isPreHook = (uint256(setValue) >> 56) & 0xFF == 1; - isPostHook = (uint256(setValue) >> 48) & 0xFF == 1; +function toHookConfig(bytes32 setValue) pure returns (HookConfig) { + return HookConfig.wrap(bytes26(setValue)); } function toSetValue(bytes4 selector) pure returns (bytes32) { diff --git a/src/account/ModuleManagerInternals.sol b/src/account/ModuleManagerInternals.sol index 5eeeb77b..1c5dc6db 100644 --- a/src/account/ModuleManagerInternals.sol +++ b/src/account/ModuleManagerInternals.sol @@ -5,19 +5,27 @@ import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165C import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import {DIRECT_CALL_VALIDATION_ENTITYID, MAX_PRE_VALIDATION_HOOKS} from "../helpers/Constants.sol"; +import {MAX_PRE_VALIDATION_HOOKS} from "../helpers/Constants.sol"; +import {HookConfigLib} from "../helpers/HookConfigLib.sol"; import {KnownSelectors} from "../helpers/KnownSelectors.sol"; import {ModuleEntityLib} from "../helpers/ModuleEntityLib.sol"; import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; -import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; import {IModule, ManifestExecutionHook, ModuleManifest} from "../interfaces/IModule.sol"; -import {IModuleManager, ModuleEntity, ValidationConfig} from "../interfaces/IModuleManager.sol"; -import {AccountStorage, SelectorData, ValidationData, getAccountStorage, toSetValue} from "./AccountStorage.sol"; +import {HookConfig, IModuleManager, ModuleEntity, ValidationConfig} from "../interfaces/IModuleManager.sol"; +import { + AccountStorage, + SelectorData, + ValidationData, + getAccountStorage, + toModuleEntity, + toSetValue +} from "./AccountStorage.sol"; abstract contract ModuleManagerInternals is IModuleManager { using EnumerableSet for EnumerableSet.Bytes32Set; using ModuleEntityLib for ModuleEntity; using ValidationConfigLib for ValidationConfig; + using HookConfigLib for HookConfig; error ArrayLengthMismatch(); error Erc4337FunctionNotAllowed(bytes4 selector); @@ -25,7 +33,7 @@ abstract contract ModuleManagerInternals is IModuleManager { error IModuleFunctionNotAllowed(bytes4 selector); error NativeFunctionNotAllowed(bytes4 selector); error NullModule(); - error PermissionAlreadySet(ModuleEntity validationFunction, ExecutionHook hook); + error PermissionAlreadySet(ModuleEntity validationFunction, HookConfig hookConfig); error ModuleInstallCallbackFailed(address module, bytes revertReason); error ModuleInterfaceNotSupported(address module); error ModuleNotInstalled(address module); @@ -108,30 +116,12 @@ abstract contract ModuleManagerInternals is IModuleManager { } } - function _addExecHooks( - EnumerableSet.Bytes32Set storage hooks, - ModuleEntity hookFunction, - bool isPreExecHook, - bool isPostExecHook - ) internal { - hooks.add( - toSetValue( - ExecutionHook({hookFunction: hookFunction, isPreHook: isPreExecHook, isPostHook: isPostExecHook}) - ) - ); + function _addExecHooks(EnumerableSet.Bytes32Set storage hooks, HookConfig hookConfig) internal { + hooks.add(toSetValue(hookConfig)); } - function _removeExecHooks( - EnumerableSet.Bytes32Set storage hooks, - ModuleEntity hookFunction, - bool isPreExecHook, - bool isPostExecHook - ) internal { - hooks.remove( - toSetValue( - ExecutionHook({hookFunction: hookFunction, isPreHook: isPreExecHook, isPostHook: isPostExecHook}) - ) - ); + function _removeExecHooks(EnumerableSet.Bytes32Set storage hooks, HookConfig hookConfig) internal { + hooks.remove(toSetValue(hookConfig)); } function _installModule(address module, ModuleManifest calldata manifest, bytes memory moduleInstallData) @@ -162,8 +152,13 @@ abstract contract ModuleManagerInternals is IModuleManager { for (uint256 i = 0; i < length; ++i) { ManifestExecutionHook memory mh = manifest.executionHooks[i]; EnumerableSet.Bytes32Set storage execHooks = _storage.selectorData[mh.executionSelector].executionHooks; - ModuleEntity hookFunction = ModuleEntityLib.pack(module, mh.entityId); - _addExecHooks(execHooks, hookFunction, mh.isPreHook, mh.isPostHook); + HookConfig hookConfig = HookConfigLib.packExecHook({ + _module: module, + _entityId: mh.entityId, + _hasPre: mh.isPreHook, + _hasPost: mh.isPostHook + }); + _addExecHooks(execHooks, hookConfig); } length = manifest.interfaceIds.length; @@ -191,9 +186,14 @@ abstract contract ModuleManagerInternals is IModuleManager { uint256 length = manifest.executionHooks.length; for (uint256 i = 0; i < length; ++i) { ManifestExecutionHook memory mh = manifest.executionHooks[i]; - ModuleEntity hookFunction = ModuleEntityLib.pack(module, mh.entityId); EnumerableSet.Bytes32Set storage execHooks = _storage.selectorData[mh.executionSelector].executionHooks; - _removeExecHooks(execHooks, hookFunction, mh.isPreHook, mh.isPostHook); + HookConfig hookConfig = HookConfigLib.packExecHook({ + _module: module, + _entityId: mh.entityId, + _hasPre: mh.isPreHook, + _hasPost: mh.isPostHook + }); + _removeExecHooks(execHooks, hookConfig); } length = manifest.executionFunctions.length; @@ -218,53 +218,44 @@ abstract contract ModuleManagerInternals is IModuleManager { emit ModuleUninstalled(module, onUninstallSuccess); } + function _onInstall(address module, bytes calldata data) internal { + if (data.length > 0) { + IModule(module).onInstall(data); + } + } + + function _onUninstall(address module, bytes calldata data) internal { + if (data.length > 0) { + IModule(module).onUninstall(data); + } + } + function _installValidation( ValidationConfig validationConfig, - bytes4[] memory selectors, + bytes4[] calldata selectors, bytes calldata installData, - bytes memory preValidationHooks, - bytes memory permissionHooks + bytes[] calldata hooks ) internal { ValidationData storage _validationData = getAccountStorage().validationData[validationConfig.moduleEntity()]; - if (preValidationHooks.length > 0) { - (ModuleEntity[] memory preValidationFunctions, bytes[] memory initDatas) = - abi.decode(preValidationHooks, (ModuleEntity[], bytes[])); - - for (uint256 i = 0; i < preValidationFunctions.length; ++i) { - ModuleEntity preValidationFunction = preValidationFunctions[i]; + for (uint256 i = 0; i < hooks.length; ++i) { + HookConfig hookConfig = HookConfig.wrap(bytes26(hooks[i][:26])); + bytes calldata hookData = hooks[i][26:]; - _validationData.preValidationHooks.push(preValidationFunction); + if (hookConfig.isValidationHook()) { + _validationData.preValidationHooks.push(hookConfig.moduleEntity()); - if (initDatas[i].length > 0) { - (address preValidationModule,) = ModuleEntityLib.unpack(preValidationFunction); - IModule(preValidationModule).onInstall(initDatas[i]); + // Avoid collision between reserved index and actual indices + if (_validationData.preValidationHooks.length > MAX_PRE_VALIDATION_HOOKS) { + revert PreValidationHookLimitExceeded(); } + } // Hook is an execution hook + else if (!_validationData.permissionHooks.add(toSetValue(hookConfig))) { + revert PermissionAlreadySet(validationConfig.moduleEntity(), hookConfig); } - // Avoid collision between reserved index and actual indices - if (_validationData.preValidationHooks.length > MAX_PRE_VALIDATION_HOOKS) { - revert PreValidationHookLimitExceeded(); - } - } - - if (permissionHooks.length > 0) { - (ExecutionHook[] memory permissionFunctions, bytes[] memory initDatas) = - abi.decode(permissionHooks, (ExecutionHook[], bytes[])); - - for (uint256 i = 0; i < permissionFunctions.length; ++i) { - ExecutionHook memory permissionFunction = permissionFunctions[i]; - - if (!_validationData.permissionHooks.add(toSetValue(permissionFunction))) { - revert PermissionAlreadySet(validationConfig.moduleEntity(), permissionFunction); - } - - if (initDatas[i].length > 0) { - (address executionModule,) = ModuleEntityLib.unpack(permissionFunction.hookFunction); - IModule(executionModule).onInstall(initDatas[i]); - } - } + _onInstall(hookConfig.module(), hookData); } for (uint256 i = 0; i < selectors.length; ++i) { @@ -274,56 +265,59 @@ abstract contract ModuleManagerInternals is IModuleManager { } } - if (validationConfig.entityId() != DIRECT_CALL_VALIDATION_ENTITYID) { - // Only allow global validations and signature validations if they're not direct-call validations. + _validationData.isGlobal = validationConfig.isGlobal(); + _validationData.isSignatureValidation = validationConfig.isSignatureValidation(); - _validationData.isGlobal = validationConfig.isGlobal(); - _validationData.isSignatureValidation = validationConfig.isSignatureValidation(); - if (installData.length > 0) { - IModule(validationConfig.module()).onInstall(installData); - } - } + _onInstall(validationConfig.module(), installData); } function _uninstallValidation( ModuleEntity validationFunction, bytes calldata uninstallData, - bytes calldata preValidationHookUninstallData, - bytes calldata permissionHookUninstallData + bytes[] calldata hookUninstallDatas ) internal { ValidationData storage _validationData = getAccountStorage().validationData[validationFunction]; _removeValidationFunction(validationFunction); - { - bytes[] memory preValidationHookUninstallDatas = abi.decode(preValidationHookUninstallData, (bytes[])); + // Send `onUninstall` to hooks + if (hookUninstallDatas.length > 0) { + // If any uninstall data is provided, assert it is of the correct length. + if ( + hookUninstallDatas.length + != _validationData.preValidationHooks.length + _validationData.permissionHooks.length() + ) { + revert ArrayLengthMismatch(); + } - // Clear pre validation hooks - ModuleEntity[] storage preValidationHooks = _validationData.preValidationHooks; - for (uint256 i = 0; i < preValidationHooks.length; ++i) { - ModuleEntity preValidationFunction = preValidationHooks[i]; - if (preValidationHookUninstallDatas[0].length > 0) { - (address preValidationModule,) = ModuleEntityLib.unpack(preValidationFunction); - IModule(preValidationModule).onUninstall(preValidationHookUninstallDatas[0]); - } + // Hook uninstall data is provided in the order of pre-validation hooks, then permission hooks. + uint256 hookIndex = 0; + for (uint256 i = 0; i < _validationData.preValidationHooks.length; ++i) { + bytes calldata hookData = hookUninstallDatas[hookIndex]; + (address hookModule,) = ModuleEntityLib.unpack(_validationData.preValidationHooks[i]); + _onUninstall(hookModule, hookData); + hookIndex++; } - delete _validationData.preValidationHooks; - } - { - bytes[] memory permissionHookUninstallDatas = abi.decode(permissionHookUninstallData, (bytes[])); - - // Clear permission hooks - EnumerableSet.Bytes32Set storage permissionHooks = _validationData.permissionHooks; - uint256 permissionHookLen = permissionHooks.length(); - for (uint256 i = 0; i < permissionHookLen; ++i) { - bytes32 permissionHook = permissionHooks.at(0); - permissionHooks.remove(permissionHook); - address permissionHookModule = address(uint160(bytes20(permissionHook))); - IModule(permissionHookModule).onUninstall(permissionHookUninstallDatas[i]); + for (uint256 i = 0; i < _validationData.permissionHooks.length(); ++i) { + bytes calldata hookData = hookUninstallDatas[hookIndex]; + (address hookModule,) = + ModuleEntityLib.unpack(toModuleEntity(_validationData.permissionHooks.at(i))); + _onUninstall(hookModule, hookData); + hookIndex++; } } + // Clear all stored hooks + delete _validationData.preValidationHooks; + + EnumerableSet.Bytes32Set storage permissionHooks = _validationData.permissionHooks; + uint256 permissionHookLen = permissionHooks.length(); + for (uint256 i = 0; i < permissionHookLen; ++i) { + bytes32 permissionHook = permissionHooks.at(0); + permissionHooks.remove(permissionHook); + } + // Clear selectors uint256 selectorLen = _validationData.selectors.length(); for (uint256 i = 0; i < selectorLen; ++i) { @@ -331,9 +325,7 @@ abstract contract ModuleManagerInternals is IModuleManager { _validationData.selectors.remove(selectorSetValue); } - if (uninstallData.length > 0) { - (address module,) = ModuleEntityLib.unpack(validationFunction); - IModule(module).onUninstall(uninstallData); - } + (address module,) = ModuleEntityLib.unpack(validationFunction); + _onUninstall(module, uninstallData); } } diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 87379241..f8545051 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -12,6 +12,7 @@ import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeab import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import {HookConfig, HookConfigLib} from "../helpers/HookConfigLib.sol"; import {ModuleEntityLib} from "../helpers/ModuleEntityLib.sol"; import {SparseCalldataSegmentLib} from "../helpers/SparseCalldataSegmentLib.sol"; @@ -27,7 +28,7 @@ import {IValidation} from "../interfaces/IValidation.sol"; import {IValidationHook} from "../interfaces/IValidationHook.sol"; import {AccountExecutor} from "./AccountExecutor.sol"; import {AccountLoupe} from "./AccountLoupe.sol"; -import {AccountStorage, getAccountStorage, toExecutionHook, toSetValue} from "./AccountStorage.sol"; +import {AccountStorage, getAccountStorage, toHookConfig, toSetValue} from "./AccountStorage.sol"; import {AccountStorageInitializable} from "./AccountStorageInitializable.sol"; import {ModuleManagerInternals} from "./ModuleManagerInternals.sol"; @@ -46,6 +47,7 @@ contract UpgradeableModularAccount is using EnumerableSet for EnumerableSet.Bytes32Set; using ModuleEntityLib for ModuleEntity; using ValidationConfigLib for ValidationConfig; + using HookConfigLib for HookConfig; using SparseCalldataSegmentLib for bytes; struct PostExecToRun { @@ -249,12 +251,11 @@ contract UpgradeableModularAccount is /// @dev This function is only callable once, and only by the EntryPoint. function initializeWithValidation( ValidationConfig validationConfig, - bytes4[] memory selectors, + bytes4[] calldata selectors, bytes calldata installData, - bytes calldata preValidationHooks, - bytes calldata permissionHooks + bytes[] calldata hooks ) external initializer { - _installValidation(validationConfig, selectors, installData, preValidationHooks, permissionHooks); + _installValidation(validationConfig, selectors, installData, hooks); emit ModularAccountInitialized(_ENTRY_POINT); } @@ -262,12 +263,11 @@ contract UpgradeableModularAccount is /// @notice May be validated by a global validation. function installValidation( ValidationConfig validationConfig, - bytes4[] memory selectors, + bytes4[] calldata selectors, bytes calldata installData, - bytes calldata preValidationHooks, - bytes calldata permissionHooks + bytes[] calldata hooks ) external wrapNativeFunction { - _installValidation(validationConfig, selectors, installData, preValidationHooks, permissionHooks); + _installValidation(validationConfig, selectors, installData, hooks); } /// @inheritdoc IModuleManager @@ -275,12 +275,9 @@ contract UpgradeableModularAccount is function uninstallValidation( ModuleEntity validationFunction, bytes calldata uninstallData, - bytes calldata preValidationHookUninstallData, - bytes calldata permissionHookUninstallData + bytes[] calldata hookUninstallData ) external wrapNativeFunction { - _uninstallValidation( - validationFunction, uninstallData, preValidationHookUninstallData, permissionHookUninstallData - ); + _uninstallValidation(validationFunction, uninstallData, hookUninstallData); } /// @notice ERC165 introspection @@ -503,26 +500,24 @@ contract UpgradeableModularAccount is // Copy all post hooks to the array. This happens before any pre hooks are run, so we can // be sure that the set of hooks to run will not be affected by state changes mid-execution. for (uint256 i = 0; i < hooksLength; ++i) { - bytes32 key = executionHooks.at(i); - (ModuleEntity hookFunction,, bool isPostHook) = toExecutionHook(key); - if (isPostHook) { - postHooksToRun[i].postExecHook = hookFunction; + HookConfig hookConfig = toHookConfig(executionHooks.at(i)); + if (hookConfig.hasPostHook()) { + postHooksToRun[i].postExecHook = hookConfig.moduleEntity(); } } // Run the pre hooks and copy their return data to the post hooks array, if an associated post-exec hook // exists. for (uint256 i = 0; i < hooksLength; ++i) { - bytes32 key = executionHooks.at(i); - (ModuleEntity hookFunction, bool isPreHook, bool isPostHook) = toExecutionHook(key); + HookConfig hookConfig = toHookConfig(executionHooks.at(i)); - if (isPreHook) { + if (hookConfig.hasPreHook()) { bytes memory preExecHookReturnData; - preExecHookReturnData = _runPreExecHook(hookFunction, data); + preExecHookReturnData = _runPreExecHook(hookConfig.moduleEntity(), data); // If there is an associated post-exec hook, save the return data. - if (isPostHook) { + if (hookConfig.hasPostHook()) { postHooksToRun[i].preExecHookReturnData = preExecHookReturnData; } } diff --git a/src/helpers/HookConfigLib.sol b/src/helpers/HookConfigLib.sol new file mode 100644 index 00000000..9c94e4bd --- /dev/null +++ b/src/helpers/HookConfigLib.sol @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.25; + +import {HookConfig, ModuleEntity} from "../interfaces/IModuleManager.sol"; + +// Hook types: +// Exec hook: bools for hasPre, hasPost +// Validation hook: no bools + +// Hook fields: +// module address +// entity ID +// hook type +// if exec hook: hasPre, hasPost + +// Hook config is a packed representation of a hook function and flags for its configuration. +// Layout: +// 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA________________________ // Address +// 0x________________________________________BBBBBBBB________________ // Entity ID +// 0x________________________________________________CC______________ // Type +// 0x__________________________________________________DD____________ // exec hook flags +// + +// Hook types: +// 0x00 // Exec (selector and validation associated) +// 0x01 // Validation + +// Exec hook flags layout: +// 0b000000__ // unused +// 0b______A_ // hasPre +// 0b_______B // hasPost + +library HookConfigLib { + // Hook type constants + // Exec has no bits set + bytes32 internal constant _HOOK_TYPE_EXEC = bytes32(uint256(0)); + // Validation has 1 in the 25th byte + bytes32 internal constant _HOOK_TYPE_VALIDATION = bytes32(uint256(1) << 56); + + // Exec hook flags constants + // Pre hook has 1 in 2's bit in the 26th byte + bytes32 internal constant _EXEC_HOOK_HAS_PRE = bytes32(uint256(1) << 49); + // Post hook has 1 in 1's bit in the 26th byte + bytes32 internal constant _EXEC_HOOK_HAS_POST = bytes32(uint256(1) << 48); + + function packValidationHook(ModuleEntity _hookFunction) internal pure returns (HookConfig) { + return + HookConfig.wrap(bytes26(bytes26(ModuleEntity.unwrap(_hookFunction)) | bytes26(_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( + bytes26( + bytes26(ModuleEntity.unwrap(_hookFunction)) + // | bytes26(_HOOK_TYPE_EXEC) // Can omit because exec type is 0 + | bytes26(_hasPre ? _EXEC_HOOK_HAS_PRE : bytes32(0)) + | bytes26(_hasPost ? _EXEC_HOOK_HAS_POST : bytes32(0)) + ) + ); + } + + function packExecHook(address _module, uint32 _entityId, bool _hasPre, bool _hasPost) + internal + pure + returns (HookConfig) + { + return HookConfig.wrap( + bytes26( + // module address stored in the first 20 bytes + bytes26(bytes20(_module)) + // entityId stored in the 21st - 24th byte + | bytes26(bytes24(uint192(_entityId))) + // | bytes26(_HOOK_TYPE_EXEC) // Can omit because exec type is 0 + | bytes26(_hasPre ? _EXEC_HOOK_HAS_PRE : bytes32(0)) + | bytes26(_hasPost ? _EXEC_HOOK_HAS_POST : bytes32(0)) + ) + ); + } + + function unpackValidationHook(HookConfig _config) internal pure returns (ModuleEntity _hookFunction) { + bytes26 configBytes = HookConfig.unwrap(_config); + _hookFunction = ModuleEntity.wrap(bytes24(configBytes)); + } + + function unpackExecHook(HookConfig _config) + internal + pure + returns (ModuleEntity _hookFunction, bool _hasPre, bool _hasPost) + { + bytes26 configBytes = HookConfig.unwrap(_config); + _hookFunction = ModuleEntity.wrap(bytes24(configBytes)); + _hasPre = configBytes & _EXEC_HOOK_HAS_PRE != 0; + _hasPost = configBytes & _EXEC_HOOK_HAS_POST != 0; + } + + function module(HookConfig _config) internal pure returns (address) { + return address(bytes20(HookConfig.unwrap(_config))); + } + + function entityId(HookConfig _config) internal pure returns (uint32) { + return uint32(bytes4(HookConfig.unwrap(_config) << 160)); + } + + function moduleEntity(HookConfig _config) internal pure returns (ModuleEntity) { + return ModuleEntity.wrap(bytes24(HookConfig.unwrap(_config))); + } + + // Check if the hook is a validation hook + // If false, it is an exec hook + function isValidationHook(HookConfig _config) internal pure returns (bool) { + return HookConfig.unwrap(_config) & _HOOK_TYPE_VALIDATION != 0; + } + + // 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; + } + + // 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; + } +} diff --git a/src/interfaces/IModuleManager.sol b/src/interfaces/IModuleManager.sol index 9923bd34..d2e63f74 100644 --- a/src/interfaces/IModuleManager.sol +++ b/src/interfaces/IModuleManager.sol @@ -7,6 +7,8 @@ type ModuleEntity is bytes24; type ValidationConfig is bytes26; +type HookConfig is bytes26; + interface IModuleManager { event ModuleInstalled(address indexed module); @@ -29,14 +31,14 @@ interface IModuleManager { /// @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 decoded and used by the module to setup initial module state. - /// @param preValidationHooks Optional pre-validation hooks to install for the validation function. - /// @param permissionHooks Optional permission hooks to install for the validation function. + /// @param hooks Optional hooks to install, associated with the validation function. These may be + /// pre-validation hooks or execution hooks. The expected format is a bytes26 HookConfig, followed by the + /// install data, if any. function installValidation( ValidationConfig validationConfig, - bytes4[] memory selectors, + bytes4[] calldata selectors, bytes calldata installData, - bytes calldata preValidationHooks, - bytes calldata permissionHooks + bytes[] calldata hooks ) external; /// @notice Uninstall a validation function from a set of execution selectors. @@ -44,14 +46,13 @@ interface IModuleManager { /// @param validationFunction The validation function to uninstall. /// @param uninstallData Optional data to be decoded and used by the module to clear module data for the /// account. - /// @param preValidationHookUninstallData Optional data to be decoded and used by the module to clear account - /// data - /// @param permissionHookUninstallData Optional data to be decoded and used by the module to clear account data + /// @param hookUninstallData Optional data to be used by hooks for cleanup. If any are provided, the array must + /// be of a length equal to existing pre-validation hooks plus permission hooks. Hooks are indexed by + /// pre-validation hook order first, then permission hooks. function uninstallValidation( ModuleEntity validationFunction, bytes calldata uninstallData, - bytes calldata preValidationHookUninstallData, - bytes calldata permissionHookUninstallData + bytes[] calldata hookUninstallData ) external; /// @notice Uninstall a module from the modular account. diff --git a/test/account/AccountLoupe.t.sol b/test/account/AccountLoupe.t.sol index 8b03d86f..988e3dd5 100644 --- a/test/account/AccountLoupe.t.sol +++ b/test/account/AccountLoupe.t.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.19; import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; +import {HookConfigLib} from "../../src/helpers/HookConfigLib.sol"; import {ModuleEntity, ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; import {IModuleManager} from "../../src/interfaces/IModuleManager.sol"; @@ -135,28 +136,23 @@ contract AccountLoupeTest is CustomValidationTestBase { internal virtual override - returns (ModuleEntity, bool, bool, bytes4[] memory, bytes memory, bytes memory, bytes memory) + returns (ModuleEntity, bool, bool, bytes4[] memory, bytes memory, bytes[] memory) { - ModuleEntity[] memory preValidationHooks = new ModuleEntity[](2); - preValidationHooks[0] = ModuleEntityLib.pack( - address(comprehensiveModule), uint32(ComprehensiveModule.EntityId.PRE_VALIDATION_HOOK_1) + bytes[] memory hooks = new bytes[](2); + hooks[0] = abi.encodePacked( + HookConfigLib.packValidationHook( + address(comprehensiveModule), uint32(ComprehensiveModule.EntityId.PRE_VALIDATION_HOOK_1) + ) ); - preValidationHooks[1] = ModuleEntityLib.pack( - address(comprehensiveModule), uint32(ComprehensiveModule.EntityId.PRE_VALIDATION_HOOK_2) + hooks[1] = abi.encodePacked( + HookConfigLib.packValidationHook( + address(comprehensiveModule), uint32(ComprehensiveModule.EntityId.PRE_VALIDATION_HOOK_2) + ) ); bytes4[] memory selectors = new bytes4[](1); selectors[0] = comprehensiveModule.foo.selector; - bytes[] memory installDatas = new bytes[](2); - return ( - comprehensiveModuleValidation, - true, - true, - selectors, - bytes(""), - abi.encode(preValidationHooks, installDatas), - "" - ); + return (comprehensiveModuleValidation, true, true, selectors, bytes(""), hooks); } } diff --git a/test/account/AccountReturnData.t.sol b/test/account/AccountReturnData.t.sol index d6834af6..4f6fef9d 100644 --- a/test/account/AccountReturnData.t.sol +++ b/test/account/AccountReturnData.t.sol @@ -48,8 +48,7 @@ contract AccountReturnDataTest is AccountTestBase { ValidationConfigLib.pack(address(resultConsumerModule), DIRECT_CALL_VALIDATION_ENTITYID, false, false), selectors, "", - "", - "" + new bytes[](0) ); vm.stopPrank(); } diff --git a/test/account/DirectCallsFromModule.t.sol b/test/account/DirectCallsFromModule.t.sol index b8d39728..6d108540 100644 --- a/test/account/DirectCallsFromModule.t.sol +++ b/test/account/DirectCallsFromModule.t.sol @@ -1,9 +1,10 @@ pragma solidity ^0.8.19; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; + +import {HookConfigLib} from "../../src/helpers/HookConfigLib.sol"; import {ModuleEntity, ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {ValidationConfig, ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; -import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; import {Call, IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; import {DirectCallModule} from "../mocks/modules/DirectCallModule.sol"; @@ -108,23 +109,22 @@ contract DirectCallsFromModuleTest is AccountTestBase { bytes4[] memory selectors = new bytes4[](1); selectors[0] = IStandardExecutor.execute.selector; - ExecutionHook[] memory permissionHooks = new ExecutionHook[](1); - bytes[] memory permissionHookInitDatas = new bytes[](1); - - permissionHooks[0] = ExecutionHook({hookFunction: _moduleEntity, isPreHook: true, isPostHook: true}); - - bytes memory encodedPermissionHooks = abi.encode(permissionHooks, permissionHookInitDatas); + bytes[] memory hooks = new bytes[](1); + hooks[0] = abi.encodePacked( + HookConfigLib.packExecHook({_hookFunction: _moduleEntity, _hasPre: true, _hasPost: true}), + hex"00" // onInstall data + ); vm.prank(address(entryPoint)); ValidationConfig validationConfig = ValidationConfigLib.pack(_moduleEntity, false, false); - account1.installValidation(validationConfig, selectors, "", "", encodedPermissionHooks); + account1.installValidation(validationConfig, selectors, "", hooks); } function _uninstallModule() internal { vm.prank(address(entryPoint)); - account1.uninstallValidation(_moduleEntity, "", abi.encode(new bytes[](0)), abi.encode(new bytes[](1))); + account1.uninstallValidation(_moduleEntity, "", new bytes[](1)); } function _buildDirectCallDisallowedError(bytes4 selector) internal pure returns (bytes memory) { diff --git a/test/account/MultiValidation.t.sol b/test/account/MultiValidation.t.sol index 772011bf..3e927ee6 100644 --- a/test/account/MultiValidation.t.sol +++ b/test/account/MultiValidation.t.sol @@ -39,8 +39,7 @@ contract MultiValidationTest is AccountTestBase { ValidationConfigLib.pack(address(validator2), TEST_DEFAULT_VALIDATION_ENTITY_ID, true, true), new bytes4[](0), abi.encode(TEST_DEFAULT_VALIDATION_ENTITY_ID, owner2), - "", - "" + new bytes[](0) ); ModuleEntity[] memory validations = new ModuleEntity[](2); diff --git a/test/account/PerHookData.t.sol b/test/account/PerHookData.t.sol index 15cefbd9..cc3f3415 100644 --- a/test/account/PerHookData.t.sol +++ b/test/account/PerHookData.t.sol @@ -6,7 +6,9 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {ModuleEntity, ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; + +import {HookConfigLib} from "../../src/helpers/HookConfigLib.sol"; +import {ModuleEntity} from "../../src/helpers/ModuleEntityLib.sol"; import {Counter} from "../mocks/Counter.sol"; import {MockAccessControlHookModule} from "../mocks/modules/MockAccessControlHookModule.sol"; @@ -330,29 +332,23 @@ contract PerHookDataTest is CustomValidationTestBase { internal virtual override - returns (ModuleEntity, bool, bool, bytes4[] memory, bytes memory, bytes memory, bytes memory) + returns (ModuleEntity, bool, bool, bytes4[] memory, bytes memory, bytes[] memory) { - ModuleEntity accessControlHook = ModuleEntityLib.pack( - address(_accessControlHookModule), uint32(MockAccessControlHookModule.EntityId.PRE_VALIDATION_HOOK) + bytes[] memory hooks = new bytes[](1); + hooks[0] = abi.encodePacked( + HookConfigLib.packValidationHook( + address(_accessControlHookModule), uint32(MockAccessControlHookModule.EntityId.PRE_VALIDATION_HOOK) + ), + abi.encode(_counter) ); - ModuleEntity[] memory preValidationHooks = new ModuleEntity[](1); - preValidationHooks[0] = accessControlHook; - - bytes[] memory preValidationHookData = new bytes[](1); - // Access control is restricted to only the counter - preValidationHookData[0] = abi.encode(_counter); - - bytes memory packedPreValidationHooks = abi.encode(preValidationHooks, preValidationHookData); - return ( _signerValidation, true, true, new bytes4[](0), abi.encode(TEST_DEFAULT_VALIDATION_ENTITY_ID, owner1), - packedPreValidationHooks, - "" + hooks ); } } diff --git a/test/account/SelfCallAuthorization.t.sol b/test/account/SelfCallAuthorization.t.sol index 4dd8a595..de2839c7 100644 --- a/test/account/SelfCallAuthorization.t.sol +++ b/test/account/SelfCallAuthorization.t.sol @@ -33,7 +33,10 @@ contract SelfCallAuthorizationTest is AccountTestBase { vm.startPrank(address(entryPoint)); account1.installModule(address(comprehensiveModule), comprehensiveModule.moduleManifest(), ""); account1.installValidation( - ValidationConfigLib.pack(comprehensiveModuleValidation, false, false), validationSelectors, "", "", "" + ValidationConfigLib.pack(comprehensiveModuleValidation, false, false), + validationSelectors, + "", + new bytes[](0) ); vm.stopPrank(); } @@ -302,7 +305,12 @@ contract SelfCallAuthorizationTest is AccountTestBase { account1.executeWithAuthorization( abi.encodeCall( UpgradeableModularAccount.installValidation, - (ValidationConfigLib.pack(comprehensiveModuleValidation, false, false), selectors, "", "", "") + ( + ValidationConfigLib.pack(comprehensiveModuleValidation, false, false), + selectors, + "", + new bytes[](0) + ) ), _encodeSignature(_signerValidation, GLOBAL_VALIDATION, "") ); diff --git a/test/account/ValidationIntersection.t.sol b/test/account/ValidationIntersection.t.sol index c9c1a882..73deb89f 100644 --- a/test/account/ValidationIntersection.t.sol +++ b/test/account/ValidationIntersection.t.sol @@ -4,6 +4,8 @@ pragma solidity ^0.8.19; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; + +import {HookConfigLib} from "../../src/helpers/HookConfigLib.sol"; import {ModuleEntity, ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; @@ -52,47 +54,36 @@ contract ValidationIntersectionTest is AccountTestBase { vm.startPrank(address(entryPoint)); // Install noHookValidation account1.installValidation( - ValidationConfigLib.pack(noHookValidation, true, true), - validationSelectors, - bytes(""), - bytes(""), - bytes("") + ValidationConfigLib.pack(noHookValidation, true, true), validationSelectors, bytes(""), new bytes[](0) ); // Install oneHookValidation validationSelectors[0] = MockUserOpValidation1HookModule.bar.selector; - ModuleEntity[] memory preValidationHooks = new ModuleEntity[](1); - preValidationHooks[0] = ModuleEntityLib.pack({ - addr: address(oneHookModule), - entityId: uint32(MockBaseUserOpValidationModule.EntityId.PRE_VALIDATION_HOOK_1) - }); - bytes[] memory installDatas = new bytes[](1); + bytes[] memory hooks = new bytes[](1); + hooks[0] = abi.encodePacked( + HookConfigLib.packValidationHook( + address(oneHookModule), uint32(MockBaseUserOpValidationModule.EntityId.PRE_VALIDATION_HOOK_1) + ) + ); account1.installValidation( - ValidationConfigLib.pack(oneHookValidation, true, true), - validationSelectors, - bytes(""), - abi.encode(preValidationHooks, installDatas), - bytes("") + ValidationConfigLib.pack(oneHookValidation, true, true), validationSelectors, bytes(""), hooks ); // Install twoHookValidation validationSelectors[0] = MockUserOpValidation2HookModule.baz.selector; - preValidationHooks = new ModuleEntity[](2); - preValidationHooks[0] = ModuleEntityLib.pack({ - addr: address(twoHookModule), - entityId: uint32(MockBaseUserOpValidationModule.EntityId.PRE_VALIDATION_HOOK_1) - }); - preValidationHooks[1] = ModuleEntityLib.pack({ - addr: address(twoHookModule), - entityId: uint32(MockBaseUserOpValidationModule.EntityId.PRE_VALIDATION_HOOK_2) - }); - installDatas = new bytes[](2); + hooks = new bytes[](2); + hooks[0] = abi.encodePacked( + HookConfigLib.packValidationHook( + address(twoHookModule), uint32(MockBaseUserOpValidationModule.EntityId.PRE_VALIDATION_HOOK_1) + ) + ); + hooks[1] = abi.encodePacked( + HookConfigLib.packValidationHook( + address(twoHookModule), uint32(MockBaseUserOpValidationModule.EntityId.PRE_VALIDATION_HOOK_2) + ) + ); account1.installValidation( - ValidationConfigLib.pack(twoHookValidation, true, true), - validationSelectors, - bytes(""), - abi.encode(preValidationHooks, installDatas), - bytes("") + ValidationConfigLib.pack(twoHookValidation, true, true), validationSelectors, bytes(""), hooks ); vm.stopPrank(); } diff --git a/test/libraries/HookConfigLib.t.sol b/test/libraries/HookConfigLib.t.sol new file mode 100644 index 00000000..7a4671b8 --- /dev/null +++ b/test/libraries/HookConfigLib.t.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.25; + +import {Test} from "forge-std/Test.sol"; + +import {HookConfigLib} from "../../src/helpers/HookConfigLib.sol"; +import {ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; +import {HookConfig, ModuleEntity} from "../../src/interfaces/IModuleManager.sol"; + +contract HookConfigLibTest is Test { + 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.module(), addr, "module mismatch"); + assertEq(hookConfig.entityId(), entityId, "entityId mismatch"); + assertEq(hookConfig.isValidationHook(), isValidation, "isValidation mismatch"); + + if (!isValidation) { + assertEq(hookConfig.hasPreHook(), hasPre, "hasPre mismatch"); + assertEq(hookConfig.hasPostHook(), 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.moduleEntity()), + ModuleEntity.unwrap(hookFunction), + "moduleEntity mismatch" + ); + assertEq(hookConfig.isValidationHook(), isValidation, "isValidation mismatch"); + + if (!isValidation) { + assertEq(hookConfig.hasPreHook(), hasPre, "hasPre mismatch"); + assertEq(hookConfig.hasPostHook(), hasPost, "hasPost mismatch"); + } + } +} diff --git a/test/mocks/SingleSignerFactoryFixture.sol b/test/mocks/SingleSignerFactoryFixture.sol index 98dfc0b8..b4a3a4ff 100644 --- a/test/mocks/SingleSignerFactoryFixture.sol +++ b/test/mocks/SingleSignerFactoryFixture.sol @@ -56,8 +56,7 @@ contract SingleSignerFactoryFixture is OptimizedTest { ), new bytes4[](0), moduleInstallData, - "", - "" + new bytes[](0) ); } diff --git a/test/module/AllowlistModule.t.sol b/test/module/AllowlistModule.t.sol index 136ae211..b0e09c90 100644 --- a/test/module/AllowlistModule.t.sol +++ b/test/module/AllowlistModule.t.sol @@ -4,7 +4,9 @@ pragma solidity ^0.8.25; import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {ModuleEntity, ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; + +import {HookConfigLib} from "../../src/helpers/HookConfigLib.sol"; +import {ModuleEntity} from "../../src/helpers/ModuleEntityLib.sol"; import {Call} from "../../src/interfaces/IStandardExecutor.sol"; import {AllowlistModule} from "../../src/modules/permissionhooks/AllowlistModule.sol"; @@ -291,19 +293,15 @@ contract AllowlistModuleTest is CustomValidationTestBase { internal virtual override - returns (ModuleEntity, bool, bool, bytes4[] memory, bytes memory, bytes memory, bytes memory) + returns (ModuleEntity, bool, bool, bytes4[] memory, bytes memory, bytes[] memory) { - ModuleEntity accessControlHook = - ModuleEntityLib.pack(address(allowlistModule), uint32(AllowlistModule.EntityId.PRE_VALIDATION_HOOK)); - - ModuleEntity[] memory preValidationHooks = new ModuleEntity[](1); - preValidationHooks[0] = accessControlHook; - - bytes[] memory preValidationHookData = new bytes[](1); - // Access control is restricted to only the counter - preValidationHookData[0] = abi.encode(allowlistInit); - - bytes memory packedPreValidationHooks = abi.encode(preValidationHooks, preValidationHookData); + bytes[] memory hooks = new bytes[](1); + hooks[0] = abi.encodePacked( + HookConfigLib.packValidationHook( + address(allowlistModule), uint32(AllowlistModule.EntityId.PRE_VALIDATION_HOOK) + ), + abi.encode(allowlistInit) + ); return ( _signerValidation, @@ -311,8 +309,7 @@ contract AllowlistModuleTest is CustomValidationTestBase { true, new bytes4[](0), abi.encode(TEST_DEFAULT_VALIDATION_ENTITY_ID, owner1), - packedPreValidationHooks, - "" + hooks ); } diff --git a/test/module/ERC20TokenLimitModule.t.sol b/test/module/ERC20TokenLimitModule.t.sol index e88a4527..c0aa8719 100644 --- a/test/module/ERC20TokenLimitModule.t.sol +++ b/test/module/ERC20TokenLimitModule.t.sol @@ -8,6 +8,7 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {ModuleEntity} from "../../src/helpers/ModuleEntityLib.sol"; +import {HookConfigLib} from "../../src/helpers/HookConfigLib.sol"; import {ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; @@ -52,16 +53,15 @@ contract ERC20TokenLimitModuleTest is AccountTestBase { ERC20TokenLimitModule.ERC20SpendLimit[] memory limit = new ERC20TokenLimitModule.ERC20SpendLimit[](1); limit[0] = ERC20TokenLimitModule.ERC20SpendLimit({token: address(erc20), limits: limits}); - bytes[] memory permissionInitDatas = new bytes[](1); - permissionInitDatas[0] = abi.encode(uint8(0), limit); + bytes[] memory hooks = new bytes[](1); + hooks[0] = abi.encodePacked( + HookConfigLib.packExecHook({_module: address(module), _entityId: 0, _hasPre: true, _hasPost: false}), + abi.encode(uint32(0), limit) + ); vm.prank(address(acct)); acct.installValidation( - ValidationConfigLib.pack(address(validationModule), 0, true, true), - new bytes4[](0), - new bytes(0), - new bytes(0), - abi.encode(permissionHooks, permissionInitDatas) + ValidationConfigLib.pack(address(validationModule), 0, true, true), new bytes4[](0), "", hooks ); validationFunction = ModuleEntityLib.pack(address(validationModule), 0); diff --git a/test/module/NativeTokenLimitModule.t.sol b/test/module/NativeTokenLimitModule.t.sol index a5e20389..540dc902 100644 --- a/test/module/NativeTokenLimitModule.t.sol +++ b/test/module/NativeTokenLimitModule.t.sol @@ -8,8 +8,8 @@ import {ModuleEntity} from "../../src/helpers/ModuleEntityLib.sol"; import {ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; +import {HookConfigLib} from "../../src/helpers/HookConfigLib.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; -import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; import {ModuleManifest} from "../../src/interfaces/IModule.sol"; import {Call, IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; import {NativeTokenLimitModule} from "../../src/modules/NativeTokenLimitModule.sol"; @@ -38,29 +38,24 @@ contract NativeTokenLimitModuleTest is AccountTestBase { ModuleEntity[] memory preValidationHooks = new ModuleEntity[](1); preValidationHooks[0] = ModuleEntityLib.pack(address(module), 0); - ExecutionHook[] memory permissionHooks = new ExecutionHook[](1); - permissionHooks[0] = ExecutionHook({ - hookFunction: ModuleEntityLib.pack(address(module), 0), - isPreHook: true, - isPostHook: false - }); - uint256[] memory spendLimits = new uint256[](1); spendLimits[0] = spendLimit; - bytes[] memory preValHooksInitDatas = new bytes[](1); - preValHooksInitDatas[0] = ""; + bytes[] memory hooks = new bytes[](2); + hooks[0] = abi.encodePacked(HookConfigLib.packValidationHook({_module: address(module), _entityId: 0})); + // No init data for pre validation - bytes[] memory permissionInitDatas = new bytes[](1); - permissionInitDatas[0] = abi.encode(0, spendLimits); + hooks[1] = abi.encodePacked( + HookConfigLib.packExecHook({_module: address(module), _entityId: 0, _hasPre: true, _hasPost: false}), + abi.encode(0, spendLimits) + ); vm.prank(address(acct)); acct.installValidation( ValidationConfigLib.pack(address(validationModule), 0, true, true), new bytes4[](0), new bytes(0), - abi.encode(preValidationHooks, preValHooksInitDatas), - abi.encode(permissionHooks, permissionInitDatas) + hooks ); validationFunction = ModuleEntityLib.pack(address(validationModule), 0); @@ -120,7 +115,7 @@ contract NativeTokenLimitModuleTest is AccountTestBase { UpgradeableModularAccount.PreExecHookReverted.selector, abi.encode( address(module), - uint8(0), + uint32(0), abi.encodePacked(NativeTokenLimitModule.ExceededNativeTokenLimit.selector) ) ) diff --git a/test/module/SingleSignerValidation.t.sol b/test/module/SingleSignerValidation.t.sol index ae3be4e5..9358b59f 100644 --- a/test/module/SingleSignerValidation.t.sol +++ b/test/module/SingleSignerValidation.t.sol @@ -83,8 +83,7 @@ contract SingleSignerValidationTest is AccountTestBase { ValidationConfigLib.pack(address(singleSignerValidation), newEntityId, true, false), new bytes4[](0), abi.encode(newEntityId, owner2), - "", - "" + new bytes[](0) ); vm.prank(owner2); diff --git a/test/utils/CustomValidationTestBase.sol b/test/utils/CustomValidationTestBase.sol index 18fd6ae4..3b313039 100644 --- a/test/utils/CustomValidationTestBase.sol +++ b/test/utils/CustomValidationTestBase.sol @@ -21,8 +21,7 @@ abstract contract CustomValidationTestBase is AccountTestBase { bool isSignatureValidation, bytes4[] memory selectors, bytes memory installData, - bytes memory preValidationHooks, - bytes memory permissionHooks + bytes[] memory hooks ) = _initialValidationConfig(); address accountImplementation = address(factory.accountImplementation()); @@ -33,8 +32,7 @@ abstract contract CustomValidationTestBase is AccountTestBase { ValidationConfigLib.pack(validationFunction, isGlobal, isSignatureValidation), selectors, installData, - preValidationHooks, - permissionHooks + hooks ); vm.deal(address(account1), 100 ether); @@ -49,7 +47,6 @@ abstract contract CustomValidationTestBase is AccountTestBase { bool isSignatureValidation, bytes4[] memory selectors, bytes memory installData, - bytes memory preValidationHooks, - bytes memory permissionHooks + bytes[] memory hooks ); } From 193e1569f183230085cf60866d329b3decf4d741 Mon Sep 17 00:00:00 2001 From: Adam Egyed <5456061+adamegyed@users.noreply.github.com> Date: Wed, 24 Jul 2024 19:06:31 -0400 Subject: [PATCH 098/111] test: [v0.8-develop] Add test for ValidationConfigLib 5/N (#110) --- test/libraries/ValidationConfigLib.t.sol | 105 +++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 test/libraries/ValidationConfigLib.t.sol diff --git a/test/libraries/ValidationConfigLib.t.sol b/test/libraries/ValidationConfigLib.t.sol new file mode 100644 index 00000000..4d49c383 --- /dev/null +++ b/test/libraries/ValidationConfigLib.t.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.25; + +import {Test} from "forge-std/Test.sol"; + +import {ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; +import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; +import {ModuleEntity, ValidationConfig} from "../../src/interfaces/IModuleManager.sol"; + +contract ValidationConfigLibTest is Test { + using ModuleEntityLib for ModuleEntity; + using ValidationConfigLib for ValidationConfig; + + // Tests the packing and unpacking of a validation config with a randomized state + + function testFuzz_validationConfig_packingUnderlying( + address module, + uint32 entityId, + bool isGlobal, + bool isSignatureValidation + ) public { + ValidationConfig validationConfig = + ValidationConfigLib.pack(module, entityId, isGlobal, isSignatureValidation); + + // Test unpacking underlying + (address module2, uint32 entityId2, bool isGlobal2, bool isSignatureValidation2) = + validationConfig.unpackUnderlying(); + + assertEq(module, module2, "module mismatch"); + assertEq(entityId, entityId2, "entityId mismatch"); + assertEq(isGlobal, isGlobal2, "isGlobal mismatch"); + assertEq(isSignatureValidation, isSignatureValidation2, "isSignatureValidation mismatch"); + + // Test unpacking to ModuleEntity + + ModuleEntity expectedModuleEntity = ModuleEntityLib.pack(module, entityId); + + (ModuleEntity validationFunction, bool isGlobal3, bool isSignatureValidation3) = validationConfig.unpack(); + + assertEq( + ModuleEntity.unwrap(validationFunction), + ModuleEntity.unwrap(expectedModuleEntity), + "validationFunction mismatch" + ); + assertEq(isGlobal, isGlobal3, "isGlobal mismatch"); + assertEq(isSignatureValidation, isSignatureValidation3, "isSignatureValidation 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"); + } + + function testFuzz_validationConfig_packingModuleEntity( + ModuleEntity validationFunction, + bool isGlobal, + bool isSignatureValidation + ) public { + ValidationConfig validationConfig = + ValidationConfigLib.pack(validationFunction, isGlobal, isSignatureValidation); + + // Test unpacking underlying + + (address expectedModule, uint32 expectedEntityId) = validationFunction.unpack(); + + (address module, uint32 entityId, bool isGlobal2, bool isSignatureValidation2) = + validationConfig.unpackUnderlying(); + + assertEq(expectedModule, module, "module mismatch"); + assertEq(expectedEntityId, entityId, "entityId mismatch"); + assertEq(isGlobal, isGlobal2, "isGlobal mismatch"); + assertEq(isSignatureValidation, isSignatureValidation2, "isSignatureValidation mismatch"); + + // Test unpacking to ModuleEntity + + (ModuleEntity validationFunction2, bool isGlobal3, bool isSignatureValidation3) = validationConfig.unpack(); + + assertEq( + ModuleEntity.unwrap(validationFunction), + ModuleEntity.unwrap(validationFunction2), + "validationFunction mismatch" + ); + assertEq(isGlobal, isGlobal3, "isGlobal mismatch"); + assertEq(isSignatureValidation, isSignatureValidation3, "isSignatureValidation mismatch"); + + // Test individual view functions + + assertEq(validationConfig.module(), expectedModule, "module mismatch"); + assertEq(validationConfig.entityId(), expectedEntityId, "entityId mismatch"); + assertEq( + ModuleEntity.unwrap(validationConfig.moduleEntity()), + ModuleEntity.unwrap(validationFunction), + "validationFunction mismatch" + ); + assertEq(validationConfig.isGlobal(), isGlobal, "isGlobal mismatch"); + assertEq(validationConfig.isSignatureValidation(), isSignatureValidation, "isSignatureValidation mismatch"); + } +} From 7f75b1ee73db4331fd0bc577de45be39131aa614 Mon Sep 17 00:00:00 2001 From: Zer0dot Date: Fri, 26 Jul 2024 06:19:20 +0800 Subject: [PATCH 099/111] fix: Handle fallback hooks & entrypoint/self-call/public selector runtime execution hooks (#112) --- src/account/UpgradeableModularAccount.sol | 45 +++++++++++------------ 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index f8545051..8ef40fab 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -117,12 +117,8 @@ contract UpgradeableModularAccount is if (execModule == address(0)) { revert UnrecognizedFunction(msg.sig); } - - _checkPermittedCallerAndAssociatedHooks(); - - PostExecToRun[] memory postExecHooks; - // Cache post-exec hooks in memory - postExecHooks = _doPreHooks(getAccountStorage().selectorData[msg.sig].executionHooks, msg.data); + (PostExecToRun[] memory postPermissionHooks, PostExecToRun[] memory postExecHooks) = + _checkPermittedCallerAndAssociatedHooks(); // execute the function, bubbling up any reverts (bool execSuccess, bytes memory execReturnData) = execModule.call(msg.data); @@ -135,6 +131,7 @@ contract UpgradeableModularAccount is } _doCachedPostExecHooks(postExecHooks); + _doCachedPostExecHooks(postPermissionHooks); return execReturnData; } @@ -600,33 +597,35 @@ contract UpgradeableModularAccount is returns (PostExecToRun[] memory, PostExecToRun[] memory) { AccountStorage storage _storage = getAccountStorage(); + PostExecToRun[] memory postPermissionHooks; + // We only need to handle permission hooks when the sender is not the entry point or the account itself, + // and the selector isn't public. if ( - msg.sender == address(_ENTRY_POINT) || msg.sender == address(this) - || _storage.selectorData[msg.sig].isPublic + msg.sender != address(_ENTRY_POINT) && msg.sender != address(this) + && !_storage.selectorData[msg.sig].isPublic ) { - return (new PostExecToRun[](0), new PostExecToRun[](0)); - } + ModuleEntity directCallValidationKey = + ModuleEntityLib.pack(msg.sender, DIRECT_CALL_VALIDATION_ENTITYID); - ModuleEntity directCallValidationKey = ModuleEntityLib.pack(msg.sender, DIRECT_CALL_VALIDATION_ENTITYID); + _checkIfValidationAppliesCallData(msg.data, directCallValidationKey, false); - _checkIfValidationAppliesCallData(msg.data, directCallValidationKey, false); + // Direct call is allowed, run associated permission & validation hooks - // Direct call is allowed, run associated permission & validation hooks + // Validation hooks + ModuleEntity[] memory preRuntimeValidationHooks = + _storage.validationData[directCallValidationKey].preValidationHooks; - // Validation hooks - ModuleEntity[] memory preRuntimeValidationHooks = - _storage.validationData[directCallValidationKey].preValidationHooks; + uint256 hookLen = preRuntimeValidationHooks.length; + for (uint256 i = 0; i < hookLen; ++i) { + _doPreRuntimeValidationHook(preRuntimeValidationHooks[i], msg.data, ""); + } - uint256 hookLen = preRuntimeValidationHooks.length; - for (uint256 i = 0; i < hookLen; ++i) { - _doPreRuntimeValidationHook(preRuntimeValidationHooks[i], msg.data, ""); + // Permission hooks + postPermissionHooks = + _doPreHooks(_storage.validationData[directCallValidationKey].permissionHooks, msg.data); } - // Permission hooks - PostExecToRun[] memory postPermissionHooks = - _doPreHooks(_storage.validationData[directCallValidationKey].permissionHooks, msg.data); - // Exec hooks PostExecToRun[] memory postExecutionHooks = _doPreHooks(_storage.selectorData[msg.sig].executionHooks, msg.data); From bfeffa335d91d5de0e12e839ad6c63f887c49718 Mon Sep 17 00:00:00 2001 From: Adam Egyed <5456061+adamegyed@users.noreply.github.com> Date: Fri, 26 Jul 2024 09:45:14 -0400 Subject: [PATCH 100/111] chore: [v0.8-develop] alpha.0 deploy prep (#111) --- .env.example | 20 ++++ .gitignore | 3 + README.md | 11 +++ foundry.toml | 4 + script/Deploy.s.sol | 156 ++++++++++++++++++++++++++++++ src/account/AccountFactory.sol | 9 +- test/account/AccountFactory.t.sol | 2 +- test/script/Deploy.s.t.sol | 79 +++++++++++++++ 8 files changed, 280 insertions(+), 4 deletions(-) create mode 100644 .env.example create mode 100644 script/Deploy.s.sol create mode 100644 test/script/Deploy.s.t.sol diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..4d35e4c1 --- /dev/null +++ b/.env.example @@ -0,0 +1,20 @@ + +# Factory owner capable only of managing stake +OWNER= +# EP 0.7 address +ENTRYPOINT= + +# Create2 expected addresses of the contracts. +# When running for the first time, the error message will contain the expected addresses. +ACCOUNT_IMPL= +FACTORY= +SINGLE_SIGNER_VALIDATION= + +# Optional, defaults to bytes32(0) +ACCOUNT_IMPL_SALT= +FACTORY_SALT= +SINGLE_SIGNER_VALIDATION_SALT= + +# Optional, defaults to 0.1 ether and 1 day, respectively +STAKE_AMOUNT= +UNSTAKE_DELAY= diff --git a/.gitignore b/.gitignore index 3189d156..39dd4f5d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ node_modules/ # Coverage report/ lcov.info + +# env vars +.env diff --git a/README.md b/README.md index b3b625b4..97476b43 100644 --- a/README.md +++ b/README.md @@ -28,3 +28,14 @@ Since IR compilation generates different bytecode, it's useful to test against t FOUNDRY_PROFILE=optimized-build forge build FOUNDRY_PROFILE=optimized-test forge test -vvv ``` + +## Integration testing + +The reference implementation provides a sample factory and deploy script for the factory, account implementation, and the demo validation module `SingleSignerValidation`. This is not auditted, nor intended for production use. Limitations set by the GPL-V3 license apply. + +To run this script, provide appropriate values in a `.env` file based on the `.env.example` template, then run: +```bash +forge script script/Deploy.s.sol -r --broadcast +``` + +Where `` specifies a way to sign the deployment transaction (see [here](https://book.getfoundry.sh/reference/forge/forge-script#wallet-options---raw)) and `` specifies an RPC for the network you are deploying on. diff --git a/foundry.toml b/foundry.toml index 0252d4e5..09a60df9 100644 --- a/foundry.toml +++ b/foundry.toml @@ -7,6 +7,9 @@ libs = ['lib'] out = 'out' optimizer = true optimizer_runs = 200 +auto_detect_solc = false +bytecode_hash = "none" +auto_detect_remappings = false fs_permissions = [ { access = "read", path = "./out-optimized" } ] @@ -22,6 +25,7 @@ depth = 10 [profile.optimized-build] via_ir = true test = 'src' +optimizer_runs = 20000 out = 'out-optimized' [profile.optimized-test] diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol new file mode 100644 index 00000000..988cc6b0 --- /dev/null +++ b/script/Deploy.s.sol @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.25; + +import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; +import {Script} from "forge-std/Script.sol"; +import {console2} from "forge-std/Test.sol"; + +import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; + +import {AccountFactory} from "../src/account/AccountFactory.sol"; +import {UpgradeableModularAccount} from "../src/account/UpgradeableModularAccount.sol"; +import {SingleSignerValidation} from "../src/modules/validation/SingleSignerValidation.sol"; + +contract DeployScript is Script { + IEntryPoint public entryPoint = IEntryPoint(payable(vm.envAddress("ENTRYPOINT"))); + + address public owner = vm.envAddress("OWNER"); + + address public accountImpl = vm.envOr("ACCOUNT_IMPL", address(0)); + address public factory = vm.envOr("FACTORY", address(0)); + address public singleSignerValidation = vm.envOr("SINGLE_SIGNER_VALIDATION", address(0)); + + bytes32 public accountImplSalt = bytes32(vm.envOr("ACCOUNT_IMPL_SALT", uint256(0))); + bytes32 public factorySalt = bytes32(vm.envOr("FACTORY_SALT", uint256(0))); + bytes32 public singleSignerValidationSalt = bytes32(vm.envOr("SINGLE_SIGNER_VALIDATION_SALT", uint256(0))); + + uint256 public requiredStakeAmount = vm.envOr("STAKE_AMOUNT", uint256(0.1 ether)); + uint256 public requiredUnstakeDelay = vm.envOr("UNSTAKE_DELAY", uint256(1 days)); + + function run() public { + console2.log("******** Deploying ERC-6900 Reference Implementation ********"); + console2.log("Chain: ", block.chainid); + console2.log("EP: ", address(entryPoint)); + console2.log("Factory owner: ", owner); + + _deployAccountImpl(accountImplSalt, accountImpl); + _deploySingleSignerValidation(singleSignerValidationSalt, singleSignerValidation); + _deployAccountFactory(factorySalt, factory); + _addStakeForFactory(uint32(requiredUnstakeDelay), requiredStakeAmount); + } + + function _deployAccountImpl(bytes32 salt, address expected) internal { + console2.log(string.concat("Deploying AccountImpl with salt: ", vm.toString(salt))); + + address addr = Create2.computeAddress( + salt, keccak256(abi.encodePacked(type(UpgradeableModularAccount).creationCode, abi.encode(entryPoint))) + ); + if (addr != expected) { + console2.log("Expected address mismatch"); + console2.log("Expected: ", expected); + console2.log("Actual: ", addr); + revert(); + } + + if (addr.code.length == 0) { + console2.log("No code found at expected address, deploying..."); + UpgradeableModularAccount deployed = new UpgradeableModularAccount{salt: salt}(entryPoint); + + if (address(deployed) != expected) { + console2.log("Deployed address mismatch"); + console2.log("Expected: ", expected); + console2.log("Deployed: ", address(deployed)); + revert(); + } + + console2.log("Deployed AccountImpl at: ", address(deployed)); + } else { + console2.log("Code found at expected address, skipping deployment"); + } + } + + function _deploySingleSignerValidation(bytes32 salt, address expected) internal { + console2.log(string.concat("Deploying SingleSignerValidation with salt: ", vm.toString(salt))); + + address addr = + Create2.computeAddress(salt, keccak256(abi.encodePacked(type(SingleSignerValidation).creationCode))); + if (addr != expected) { + console2.log("Expected address mismatch"); + console2.log("Expected: ", expected); + console2.log("Actual: ", addr); + revert(); + } + + if (addr.code.length == 0) { + console2.log("No code found at expected address, deploying..."); + SingleSignerValidation deployed = new SingleSignerValidation{salt: salt}(); + + if (address(deployed) != expected) { + console2.log("Deployed address mismatch"); + console2.log("Expected: ", expected); + console2.log("Deployed: ", address(deployed)); + revert(); + } + + console2.log("Deployed SingleSignerValidation at: ", address(deployed)); + } else { + console2.log("Code found at expected address, skipping deployment"); + } + } + + function _deployAccountFactory(bytes32 salt, address expected) internal { + console2.log(string.concat("Deploying AccountFactory with salt: ", vm.toString(salt))); + + address addr = Create2.computeAddress( + salt, + keccak256( + abi.encodePacked( + type(AccountFactory).creationCode, + abi.encode(entryPoint, accountImpl, singleSignerValidation, owner) + ) + ) + ); + if (addr != expected) { + console2.log("Expected address mismatch"); + console2.log("Expected: ", expected); + console2.log("Actual: ", addr); + revert(); + } + + if (addr.code.length == 0) { + console2.log("No code found at expected address, deploying..."); + AccountFactory deployed = new AccountFactory{salt: salt}( + entryPoint, UpgradeableModularAccount(payable(accountImpl)), singleSignerValidation, owner + ); + + if (address(deployed) != expected) { + console2.log("Deployed address mismatch"); + console2.log("Expected: ", expected); + console2.log("Deployed: ", address(deployed)); + revert(); + } + + console2.log("Deployed AccountFactory at: ", address(deployed)); + } else { + console2.log("Code found at expected address, skipping deployment"); + } + } + + function _addStakeForFactory(uint32 unstakeDelay, uint256 stakeAmount) internal { + console2.log("Adding stake to factory"); + + uint256 currentStake = entryPoint.getDepositInfo(address(factory)).stake; + console2.log("Current stake: ", currentStake); + uint256 stakeToAdd = stakeAmount - currentStake; + + if (stakeToAdd > 0) { + console2.log("Adding stake: ", stakeToAdd); + entryPoint.addStake{value: stakeToAdd}(unstakeDelay); + console2.log("Staked factory: ", address(factory)); + console2.log("Total stake amount: ", entryPoint.getDepositInfo(address(factory)).stake); + console2.log("Unstake delay: ", entryPoint.getDepositInfo(address(factory)).unstakeDelaySec); + } else { + console2.log("No stake to add"); + } + } +} diff --git a/src/account/AccountFactory.sol b/src/account/AccountFactory.sol index ca15e815..07a74d09 100644 --- a/src/account/AccountFactory.sol +++ b/src/account/AccountFactory.sol @@ -17,9 +17,12 @@ contract AccountFactory is Ownable { IEntryPoint public immutable ENTRY_POINT; address public immutable SINGLE_SIGNER_VALIDATION; - constructor(IEntryPoint _entryPoint, UpgradeableModularAccount _accountImpl, address _singleSignerValidation) - Ownable(msg.sender) - { + constructor( + IEntryPoint _entryPoint, + UpgradeableModularAccount _accountImpl, + address _singleSignerValidation, + address owner + ) Ownable(owner) { ENTRY_POINT = _entryPoint; _PROXY_BYTECODE_HASH = keccak256(abi.encodePacked(type(ERC1967Proxy).creationCode, abi.encode(address(_accountImpl), ""))); diff --git a/test/account/AccountFactory.t.sol b/test/account/AccountFactory.t.sol index 6e79b1c3..37ffa74c 100644 --- a/test/account/AccountFactory.t.sol +++ b/test/account/AccountFactory.t.sol @@ -11,7 +11,7 @@ contract AccountFactoryTest is AccountTestBase { function setUp() public { _account = new UpgradeableModularAccount(entryPoint); - _factory = new AccountFactory(entryPoint, _account, address(singleSignerValidation)); + _factory = new AccountFactory(entryPoint, _account, address(singleSignerValidation), address(this)); } function test_createAccount() public { diff --git a/test/script/Deploy.s.t.sol b/test/script/Deploy.s.t.sol new file mode 100644 index 00000000..c0b09c17 --- /dev/null +++ b/test/script/Deploy.s.t.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.25; + +import {Test} from "forge-std/Test.sol"; + +import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.sol"; +import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; + +import {DeployScript} from "../../script/Deploy.s.sol"; + +import {AccountFactory} from "../../src/account/AccountFactory.sol"; +import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; +import {SingleSignerValidation} from "../../src/modules/validation/SingleSignerValidation.sol"; + +contract DeployTest is Test { + DeployScript internal _deployScript; + + EntryPoint internal _entryPoint; + + address internal _owner; + + address internal _accountImpl; + address internal _singleSignerValidation; + address internal _factory; + + function setUp() public { + _entryPoint = new EntryPoint(); + _owner = makeAddr("OWNER"); + + vm.setEnv("ENTRYPOINT", vm.toString(address(_entryPoint))); + vm.setEnv("OWNER", vm.toString(_owner)); + + // Create1 derivation of the 2nd address deployed + address deployScriptAddr = address(0x2e234DAe75C793f67A35089C9d99245E1C58470b); + + _accountImpl = Create2.computeAddress( + bytes32(0), + keccak256( + abi.encodePacked(type(UpgradeableModularAccount).creationCode, abi.encode(address(_entryPoint))) + ), + deployScriptAddr + ); + + _singleSignerValidation = Create2.computeAddress( + bytes32(0), keccak256(abi.encodePacked(type(SingleSignerValidation).creationCode)), deployScriptAddr + ); + + _factory = Create2.computeAddress( + bytes32(0), + keccak256( + abi.encodePacked( + type(AccountFactory).creationCode, + abi.encode(address(_entryPoint), _accountImpl, _singleSignerValidation, _owner) + ) + ), + deployScriptAddr + ); + + vm.setEnv("ACCOUNT_IMPL", vm.toString(address(_accountImpl))); + vm.setEnv("FACTORY", vm.toString(address(_factory))); + vm.setEnv("SINGLE_SIGNER_VALIDATION", vm.toString(address(_singleSignerValidation))); + + vm.setEnv("ACCOUNT_IMPL_SALT", vm.toString(uint256(0))); + vm.setEnv("FACTORY_SALT", vm.toString(uint256(0))); + vm.setEnv("SINGLE_SIGNER_VALIDATION_SALT", vm.toString(uint256(0))); + + _deployScript = new DeployScript(); + + vm.deal(address(_deployScript), 0.1 ether); + } + + function test_deployScript_run() public { + _deployScript.run(); + + assertTrue(_accountImpl.code.length > 0); + assertTrue(_factory.code.length > 0); + assertTrue(_singleSignerValidation.code.length > 0); + } +} From 1dc64e54d12c52401e8f89207ba513ddd32c2e52 Mon Sep 17 00:00:00 2001 From: Adam Egyed <5456061+adamegyed@users.noreply.github.com> Date: Fri, 26 Jul 2024 11:00:18 -0400 Subject: [PATCH 101/111] fix: remove unnecessary foundry.toml config (#114) --- foundry.toml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/foundry.toml b/foundry.toml index 09a60df9..c35ae1b3 100644 --- a/foundry.toml +++ b/foundry.toml @@ -55,12 +55,4 @@ sort_imports = true number_underscore = "thousands" int_types = "long" -[rpc_endpoints] -mainnet = "${RPC_URL_MAINNET}" -goerli = "${RPC_URL_GOERLI}" - -[etherscan] -mainnet = { key = "${ETHERSCAN_API_KEY}" } -goerli = { key = "${ETHERSCAN_API_KEY}" } - # See more config options https://github.com/foundry-rs/foundry/tree/master/config From 2bc08b0aacb6bad20da8537aa8c4c4dd96d5d6a6 Mon Sep 17 00:00:00 2001 From: Adam Egyed <5456061+adamegyed@users.noreply.github.com> Date: Fri, 26 Jul 2024 11:43:10 -0400 Subject: [PATCH 102/111] fix: start broadcast in script (#115) --- script/Deploy.s.sol | 14 ++++++++++---- test/script/Deploy.s.t.sol | 9 +++------ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 988cc6b0..042a0ab8 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -33,17 +33,21 @@ contract DeployScript is Script { console2.log("EP: ", address(entryPoint)); console2.log("Factory owner: ", owner); + vm.startBroadcast(); _deployAccountImpl(accountImplSalt, accountImpl); _deploySingleSignerValidation(singleSignerValidationSalt, singleSignerValidation); _deployAccountFactory(factorySalt, factory); _addStakeForFactory(uint32(requiredUnstakeDelay), requiredStakeAmount); + vm.stopBroadcast(); } function _deployAccountImpl(bytes32 salt, address expected) internal { console2.log(string.concat("Deploying AccountImpl with salt: ", vm.toString(salt))); address addr = Create2.computeAddress( - salt, keccak256(abi.encodePacked(type(UpgradeableModularAccount).creationCode, abi.encode(entryPoint))) + salt, + keccak256(abi.encodePacked(type(UpgradeableModularAccount).creationCode, abi.encode(entryPoint))), + CREATE2_FACTORY ); if (addr != expected) { console2.log("Expected address mismatch"); @@ -72,8 +76,9 @@ contract DeployScript is Script { function _deploySingleSignerValidation(bytes32 salt, address expected) internal { console2.log(string.concat("Deploying SingleSignerValidation with salt: ", vm.toString(salt))); - address addr = - Create2.computeAddress(salt, keccak256(abi.encodePacked(type(SingleSignerValidation).creationCode))); + address addr = Create2.computeAddress( + salt, keccak256(abi.encodePacked(type(SingleSignerValidation).creationCode)), CREATE2_FACTORY + ); if (addr != expected) { console2.log("Expected address mismatch"); console2.log("Expected: ", expected); @@ -108,7 +113,8 @@ contract DeployScript is Script { type(AccountFactory).creationCode, abi.encode(entryPoint, accountImpl, singleSignerValidation, owner) ) - ) + ), + CREATE2_FACTORY ); if (addr != expected) { console2.log("Expected address mismatch"); diff --git a/test/script/Deploy.s.t.sol b/test/script/Deploy.s.t.sol index c0b09c17..105ce9b8 100644 --- a/test/script/Deploy.s.t.sol +++ b/test/script/Deploy.s.t.sol @@ -30,19 +30,16 @@ contract DeployTest is Test { vm.setEnv("ENTRYPOINT", vm.toString(address(_entryPoint))); vm.setEnv("OWNER", vm.toString(_owner)); - // Create1 derivation of the 2nd address deployed - address deployScriptAddr = address(0x2e234DAe75C793f67A35089C9d99245E1C58470b); - _accountImpl = Create2.computeAddress( bytes32(0), keccak256( abi.encodePacked(type(UpgradeableModularAccount).creationCode, abi.encode(address(_entryPoint))) ), - deployScriptAddr + CREATE2_FACTORY ); _singleSignerValidation = Create2.computeAddress( - bytes32(0), keccak256(abi.encodePacked(type(SingleSignerValidation).creationCode)), deployScriptAddr + bytes32(0), keccak256(abi.encodePacked(type(SingleSignerValidation).creationCode)), CREATE2_FACTORY ); _factory = Create2.computeAddress( @@ -53,7 +50,7 @@ contract DeployTest is Test { abi.encode(address(_entryPoint), _accountImpl, _singleSignerValidation, _owner) ) ), - deployScriptAddr + CREATE2_FACTORY ); vm.setEnv("ACCOUNT_IMPL", vm.toString(address(_accountImpl))); From 4daa25ab3c1e769cda686a39b09c10bc7a16f982 Mon Sep 17 00:00:00 2001 From: zer0dot Date: Fri, 26 Jul 2024 12:23:56 -0400 Subject: [PATCH 103/111] chore: formatting --- src/account/AccountFactory.sol | 5 +- src/account/AccountLoupe.sol | 13 ++--- src/account/ModuleManagerInternals.sol | 13 ++--- src/account/UpgradeableModularAccount.sol | 18 +++---- src/helpers/HookConfigLib.sol | 9 +--- src/helpers/KnownSelectors.sol | 9 ++-- src/interfaces/IValidation.sol | 11 ++-- src/modules/ERC20TokenLimitModule.sol | 3 +- .../permissionhooks/AllowlistModule.sol | 3 +- .../validation/SingleSignerValidation.sol | 13 ++--- test/account/AccountExecHooks.t.sol | 11 +--- test/account/AccountLoupe.t.sol | 4 +- test/account/AccountReturnData.t.sol | 3 +- test/account/GlobalValidationTest.t.sol | 3 +- test/account/PerHookData.t.sol | 51 +++++++------------ test/account/SelfCallAuthorization.t.sol | 21 +++----- test/account/UpgradeableModularAccount.t.sol | 8 +-- test/comparison/CompareSimpleAccount.t.sol | 4 +- test/libraries/HookConfigLib.t.sol | 4 +- test/libraries/SparseCalldataSegmentLib.t.sol | 9 +--- test/libraries/ValidationConfigLib.t.sol | 3 +- test/mocks/SingleSignerFactoryFixture.sol | 9 ++-- test/mocks/modules/ReturnDataModuleMocks.sol | 5 +- test/mocks/modules/ValidationModuleMocks.sol | 4 +- test/module/AllowlistModule.t.sol | 16 ++---- test/module/ERC20TokenLimitModule.t.sol | 37 +++++--------- test/module/NativeTokenLimitModule.t.sol | 8 +-- test/module/SingleSignerValidation.t.sol | 4 +- test/module/TokenReceiverModule.t.sol | 3 +- test/script/Deploy.s.t.sol | 4 +- test/utils/AccountTestBase.sol | 14 ++--- test/utils/CustomValidationTestBase.sol | 5 +- test/utils/OptimizedTest.sol | 12 ++--- 33 files changed, 101 insertions(+), 238 deletions(-) diff --git a/src/account/AccountFactory.sol b/src/account/AccountFactory.sol index 07a74d09..da82b3c4 100644 --- a/src/account/AccountFactory.sol +++ b/src/account/AccountFactory.sol @@ -37,10 +37,7 @@ contract AccountFactory is Ownable { * This method returns an existing account address so that entryPoint.getSenderAddress() would work even after * account creation */ - function createAccount(address owner, uint256 salt, uint32 entityId) - external - returns (UpgradeableModularAccount) - { + function createAccount(address owner, uint256 salt, uint32 entityId) external returns (UpgradeableModularAccount) { bytes32 combinedSalt = getSalt(owner, salt, entityId); address addr = Create2.computeAddress(combinedSalt, _PROXY_BYTECODE_HASH); diff --git a/src/account/AccountLoupe.sol b/src/account/AccountLoupe.sol index bcd6dbcd..7c6bfad0 100644 --- a/src/account/AccountLoupe.sol +++ b/src/account/AccountLoupe.sol @@ -22,8 +22,7 @@ abstract contract AccountLoupe is IAccountLoupe { if ( selector == IStandardExecutor.execute.selector || selector == IStandardExecutor.executeBatch.selector || selector == UUPSUpgradeable.upgradeToAndCall.selector - || selector == IModuleManager.installModule.selector - || selector == IModuleManager.uninstallModule.selector + || selector == IModuleManager.installModule.selector || selector == IModuleManager.uninstallModule.selector ) { return address(this); } @@ -45,12 +44,7 @@ abstract contract AccountLoupe is IAccountLoupe { } /// @inheritdoc IAccountLoupe - function getExecutionHooks(bytes4 selector) - external - view - override - returns (ExecutionHook[] memory execHooks) - { + function getExecutionHooks(bytes4 selector) external view override returns (ExecutionHook[] memory execHooks) { EnumerableSet.Bytes32Set storage hooks = getAccountStorage().selectorData[selector].executionHooks; uint256 executionHooksLength = hooks.length(); @@ -74,8 +68,7 @@ abstract contract AccountLoupe is IAccountLoupe { override returns (ExecutionHook[] memory permissionHooks) { - EnumerableSet.Bytes32Set storage hooks = - getAccountStorage().validationData[validationFunction].permissionHooks; + EnumerableSet.Bytes32Set storage hooks = getAccountStorage().validationData[validationFunction].permissionHooks; uint256 executionHooksLength = hooks.length(); permissionHooks = new ExecutionHook[](executionHooksLength); for (uint256 i = 0; i < executionHooksLength; ++i) { diff --git a/src/account/ModuleManagerInternals.sol b/src/account/ModuleManagerInternals.sol index 1c5dc6db..04ed6219 100644 --- a/src/account/ModuleManagerInternals.sol +++ b/src/account/ModuleManagerInternals.sol @@ -85,8 +85,7 @@ abstract contract ModuleManagerInternals is IModuleManager { } function _addValidationFunction(ValidationConfig validationConfig, bytes4[] memory selectors) internal { - ValidationData storage _validationData = - getAccountStorage().validationData[validationConfig.moduleEntity()]; + ValidationData storage _validationData = getAccountStorage().validationData[validationConfig.moduleEntity()]; if (validationConfig.isGlobal()) { _validationData.isGlobal = true; @@ -176,9 +175,7 @@ abstract contract ModuleManagerInternals is IModuleManager { emit ModuleInstalled(module); } - function _uninstallModule(address module, ModuleManifest calldata manifest, bytes memory uninstallData) - internal - { + function _uninstallModule(address module, ModuleManifest calldata manifest, bytes memory uninstallData) internal { AccountStorage storage _storage = getAccountStorage(); // Remove components according to the manifest, in reverse order (by component type) of their installation. @@ -236,8 +233,7 @@ abstract contract ModuleManagerInternals is IModuleManager { bytes calldata installData, bytes[] calldata hooks ) internal { - ValidationData storage _validationData = - getAccountStorage().validationData[validationConfig.moduleEntity()]; + ValidationData storage _validationData = getAccountStorage().validationData[validationConfig.moduleEntity()]; for (uint256 i = 0; i < hooks.length; ++i) { HookConfig hookConfig = HookConfig.wrap(bytes26(hooks[i][:26])); @@ -301,8 +297,7 @@ abstract contract ModuleManagerInternals is IModuleManager { for (uint256 i = 0; i < _validationData.permissionHooks.length(); ++i) { bytes calldata hookData = hookUninstallDatas[hookIndex]; - (address hookModule,) = - ModuleEntityLib.unpack(toModuleEntity(_validationData.permissionHooks.at(i))); + (address hookModule,) = ModuleEntityLib.unpack(toModuleEntity(_validationData.permissionHooks.at(i))); _onUninstall(hookModule, hookData); hookIndex++; } diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 9cd693cb..db0d42bf 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -425,8 +425,7 @@ contract UpgradeableModularAccount is } (address module, uint32 entityId) = preUserOpValidationHooks[i].unpack(); - uint256 currentValidationRes = - IValidationHook(module).preUserOpValidationHook(entityId, userOp, userOpHash); + uint256 currentValidationRes = IValidationHook(module).preUserOpValidationHook(entityId, userOp, userOpHash); if (uint160(currentValidationRes) > 1) { // If the aggregator is not 0 or 1, it is an unexpected value @@ -629,8 +628,7 @@ contract UpgradeableModularAccount is msg.sender != address(_ENTRY_POINT) && msg.sender != address(this) && !_storage.selectorData[msg.sig].isPublic ) { - ModuleEntity directCallValidationKey = - ModuleEntityLib.pack(msg.sender, DIRECT_CALL_VALIDATION_ENTITYID); + ModuleEntity directCallValidationKey = ModuleEntityLib.pack(msg.sender, DIRECT_CALL_VALIDATION_ENTITYID); _checkIfValidationAppliesCallData(msg.data, directCallValidationKey, false); @@ -651,17 +649,15 @@ contract UpgradeableModularAccount is } // Exec hooks - PostExecToRun[] memory postExecutionHooks = - _doPreHooks(_storage.selectorData[msg.sig].executionHooks, msg.data); + PostExecToRun[] memory postExecutionHooks = _doPreHooks(_storage.selectorData[msg.sig].executionHooks, msg.data); return (postPermissionHooks, postExecutionHooks); } - function _checkIfValidationAppliesCallData( - bytes calldata callData, - ModuleEntity validationFunction, - bool isGlobal - ) internal view { + function _checkIfValidationAppliesCallData(bytes calldata callData, ModuleEntity validationFunction, bool isGlobal) + internal + view + { bytes4 outerSelector = bytes4(callData[:4]); if (outerSelector == this.executeUserOp.selector) { // If the selector is executeUserOp, pull the actual selector from the following data, diff --git a/src/helpers/HookConfigLib.sol b/src/helpers/HookConfigLib.sol index 9c94e4bd..dd6f82e7 100644 --- a/src/helpers/HookConfigLib.sol +++ b/src/helpers/HookConfigLib.sol @@ -44,8 +44,7 @@ library HookConfigLib { bytes32 internal constant _EXEC_HOOK_HAS_POST = bytes32(uint256(1) << 48); function packValidationHook(ModuleEntity _hookFunction) internal pure returns (HookConfig) { - return - HookConfig.wrap(bytes26(bytes26(ModuleEntity.unwrap(_hookFunction)) | bytes26(_HOOK_TYPE_VALIDATION))); + return HookConfig.wrap(bytes26(bytes26(ModuleEntity.unwrap(_hookFunction)) | bytes26(_HOOK_TYPE_VALIDATION))); } function packValidationHook(address _module, uint32 _entityId) internal pure returns (HookConfig) { @@ -59,11 +58,7 @@ library HookConfigLib { ); } - function packExecHook(ModuleEntity _hookFunction, bool _hasPre, bool _hasPost) - internal - pure - returns (HookConfig) - { + function packExecHook(ModuleEntity _hookFunction, bool _hasPre, bool _hasPost) internal pure returns (HookConfig) { return HookConfig.wrap( bytes26( bytes26(ModuleEntity.unwrap(_hookFunction)) diff --git a/src/helpers/KnownSelectors.sol b/src/helpers/KnownSelectors.sol index ec3b6e6f..e076ddca 100644 --- a/src/helpers/KnownSelectors.sol +++ b/src/helpers/KnownSelectors.sol @@ -27,8 +27,7 @@ library KnownSelectors { // check against IERC165 methods || selector == IERC165.supportsInterface.selector // check against UUPSUpgradeable methods - || selector == UUPSUpgradeable.proxiableUUID.selector - || selector == UUPSUpgradeable.upgradeToAndCall.selector + || selector == UUPSUpgradeable.proxiableUUID.selector || selector == UUPSUpgradeable.upgradeToAndCall.selector // check against IStandardExecutor methods || selector == IStandardExecutor.execute.selector || selector == IStandardExecutor.executeBatch.selector || selector == IStandardExecutor.executeWithAuthorization.selector @@ -48,9 +47,9 @@ library KnownSelectors { function isIModuleFunction(bytes4 selector) internal pure returns (bool) { return selector == IModule.onInstall.selector || selector == IModule.onUninstall.selector || selector == IModule.moduleManifest.selector || selector == IModule.moduleMetadata.selector - || selector == IExecutionHook.preExecutionHook.selector - || selector == IExecutionHook.postExecutionHook.selector || selector == IValidation.validateUserOp.selector - || selector == IValidation.validateRuntime.selector || selector == IValidation.validateSignature.selector + || selector == IExecutionHook.preExecutionHook.selector || selector == IExecutionHook.postExecutionHook.selector + || selector == IValidation.validateUserOp.selector || selector == IValidation.validateRuntime.selector + || selector == IValidation.validateSignature.selector || selector == IValidationHook.preUserOpValidationHook.selector || selector == IValidationHook.preRuntimeValidationHook.selector; } diff --git a/src/interfaces/IValidation.sol b/src/interfaces/IValidation.sol index 4f8fbbb8..e5fcfefa 100644 --- a/src/interfaces/IValidation.sol +++ b/src/interfaces/IValidation.sol @@ -43,11 +43,8 @@ interface IValidation is IModule { /// @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); + function validateSignature(address account, uint32 entityId, address sender, bytes32 hash, bytes calldata signature) + external + view + returns (bytes4); } diff --git a/src/modules/ERC20TokenLimitModule.sol b/src/modules/ERC20TokenLimitModule.sol index 85d90585..e270c1ee 100644 --- a/src/modules/ERC20TokenLimitModule.sol +++ b/src/modules/ERC20TokenLimitModule.sol @@ -79,8 +79,7 @@ contract ERC20TokenLimitModule is BaseModule, IExecutionHook { /// @inheritdoc IModule function onInstall(bytes calldata data) external override { - (uint32 startEntityId, ERC20SpendLimit[] memory spendLimits) = - abi.decode(data, (uint32, ERC20SpendLimit[])); + (uint32 startEntityId, ERC20SpendLimit[] memory spendLimits) = abi.decode(data, (uint32, ERC20SpendLimit[])); if (startEntityId + spendLimits.length > type(uint32).max) { revert ExceededNumberOfEntities(); diff --git a/src/modules/permissionhooks/AllowlistModule.sol b/src/modules/permissionhooks/AllowlistModule.sol index b90fd6c2..f3ae036e 100644 --- a/src/modules/permissionhooks/AllowlistModule.sol +++ b/src/modules/permissionhooks/AllowlistModule.sol @@ -26,8 +26,7 @@ contract AllowlistModule is IValidationHook, BaseModule { } mapping(address target => mapping(address account => AllowlistEntry)) public targetAllowlist; - mapping(address target => mapping(bytes4 selector => mapping(address account => bool))) public - selectorAllowlist; + mapping(address target => mapping(bytes4 selector => mapping(address account => bool))) public selectorAllowlist; error TargetNotAllowed(); error SelectorNotAllowed(); diff --git a/src/modules/validation/SingleSignerValidation.sol b/src/modules/validation/SingleSignerValidation.sol index 2f2205ee..fd65c8ec 100644 --- a/src/modules/validation/SingleSignerValidation.sol +++ b/src/modules/validation/SingleSignerValidation.sol @@ -86,14 +86,11 @@ contract SingleSignerValidation is ISingleSignerValidation, BaseModule { } /// @inheritdoc IValidation - function validateRuntime( - address account, - uint32 entityId, - address sender, - uint256, - bytes calldata, - bytes calldata - ) external view override { + function validateRuntime(address account, uint32 entityId, address sender, uint256, bytes calldata, bytes calldata) + external + view + override + { if (sender != _getExpectedSigner(entityId, account)) { revert NotAuthorized(); } diff --git a/test/account/AccountExecHooks.t.sol b/test/account/AccountExecHooks.t.sol index 434ad0e6..738bcdd1 100644 --- a/test/account/AccountExecHooks.t.sol +++ b/test/account/AccountExecHooks.t.sol @@ -3,10 +3,7 @@ pragma solidity ^0.8.19; import {IExecutionHook} from "../../src/interfaces/IExecutionHook.sol"; import { - IModule, - ManifestExecutionFunction, - ManifestExecutionHook, - ModuleManifest + IModule, ManifestExecutionFunction, ManifestExecutionHook, ModuleManifest } from "../../src/interfaces/IModule.sol"; import {MockModule} from "../mocks/MockModule.sol"; @@ -31,11 +28,7 @@ contract AccountExecHooksTest is AccountTestBase { _transferOwnershipToTest(); _m1.executionFunctions.push( - ManifestExecutionFunction({ - executionSelector: _EXEC_SELECTOR, - isPublic: true, - allowGlobalValidation: false - }) + ManifestExecutionFunction({executionSelector: _EXEC_SELECTOR, isPublic: true, allowGlobalValidation: false}) ); } diff --git a/test/account/AccountLoupe.t.sol b/test/account/AccountLoupe.t.sol index 988e3dd5..e6d70829 100644 --- a/test/account/AccountLoupe.t.sol +++ b/test/account/AccountLoupe.t.sol @@ -100,9 +100,7 @@ contract AccountLoupeTest is CustomValidationTestBase { assertEq(hooks.length, 3); for (uint256 i = 0; i < hooks.length; i++) { - assertEq( - ModuleEntity.unwrap(hooks[i].hookFunction), ModuleEntity.unwrap(expectedHooks[i].hookFunction) - ); + assertEq(ModuleEntity.unwrap(hooks[i].hookFunction), ModuleEntity.unwrap(expectedHooks[i].hookFunction)); assertEq(hooks[i].isPreHook, expectedHooks[i].isPreHook); assertEq(hooks[i].isPostHook, expectedHooks[i].isPostHook); } diff --git a/test/account/AccountReturnData.t.sol b/test/account/AccountReturnData.t.sol index 4f6fef9d..ab516244 100644 --- a/test/account/AccountReturnData.t.sol +++ b/test/account/AccountReturnData.t.sol @@ -64,8 +64,7 @@ contract AccountReturnDataTest is AccountTestBase { function test_returnData_singular_execute() public { bytes memory returnData = account1.executeWithAuthorization( abi.encodeCall( - account1.execute, - (address(regularResultContract), 0, abi.encodeCall(RegularResultContract.foo, ())) + account1.execute, (address(regularResultContract), 0, abi.encodeCall(RegularResultContract.foo, ())) ), _encodeSignature( ModuleEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), diff --git a/test/account/GlobalValidationTest.t.sol b/test/account/GlobalValidationTest.t.sol index b22d68eb..02e3c3f8 100644 --- a/test/account/GlobalValidationTest.t.sol +++ b/test/account/GlobalValidationTest.t.sol @@ -26,8 +26,7 @@ contract GlobalValidationTest is AccountTestBase { account2 = UpgradeableModularAccount(payable(factory.getAddress(owner2, 0))); vm.deal(address(account2), 100 ether); - _signerValidation = - ModuleEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID); + _signerValidation = ModuleEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID); ethRecipient = makeAddr("ethRecipient"); vm.deal(ethRecipient, 1 wei); diff --git a/test/account/PerHookData.t.sol b/test/account/PerHookData.t.sol index cc3f3415..75d71270 100644 --- a/test/account/PerHookData.t.sol +++ b/test/account/PerHookData.t.sol @@ -39,9 +39,8 @@ contract PerHookDataTest is CustomValidationTestBase { PreValidationHookData[] memory preValidationHookData = new PreValidationHookData[](1); preValidationHookData[0] = PreValidationHookData({index: 0, validationData: abi.encodePacked(_counter)}); - userOp.signature = _encodeSignature( - _signerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v) - ); + userOp.signature = + _encodeSignature(_signerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -62,9 +61,8 @@ contract PerHookDataTest is CustomValidationTestBase { validationData: abi.encodePacked(address(0x1234123412341234123412341234123412341234)) }); - userOp.signature = _encodeSignature( - _signerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v) - ); + userOp.signature = + _encodeSignature(_signerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -108,9 +106,8 @@ contract PerHookDataTest is CustomValidationTestBase { preValidationHookData[0] = PreValidationHookData({index: 0, validationData: abi.encodePacked(_counter)}); preValidationHookData[1] = PreValidationHookData({index: 1, validationData: abi.encodePacked(_counter)}); - userOp.signature = _encodeSignature( - _signerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v) - ); + userOp.signature = + _encodeSignature(_signerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -147,9 +144,8 @@ contract PerHookDataTest is CustomValidationTestBase { PreValidationHookData[] memory preValidationHookData = new PreValidationHookData[](1); preValidationHookData[0] = PreValidationHookData({index: 0, validationData: abi.encodePacked(beneficiary)}); - userOp.signature = _encodeSignature( - _signerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v) - ); + userOp.signature = + _encodeSignature(_signerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -172,9 +168,8 @@ contract PerHookDataTest is CustomValidationTestBase { PreValidationHookData[] memory preValidationHookData = new PreValidationHookData[](1); preValidationHookData[0] = PreValidationHookData({index: 0, validationData: ""}); - userOp.signature = _encodeSignature( - _signerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v) - ); + userOp.signature = + _encodeSignature(_signerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v)); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -199,8 +194,7 @@ contract PerHookDataTest is CustomValidationTestBase { vm.prank(owner1); account1.executeWithAuthorization( abi.encodeCall( - UpgradeableModularAccount.execute, - (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) + UpgradeableModularAccount.execute, (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) ), _encodeSignature(_signerValidation, GLOBAL_VALIDATION, preValidationHookData, "") ); @@ -226,8 +220,7 @@ contract PerHookDataTest is CustomValidationTestBase { ); account1.executeWithAuthorization( abi.encodeCall( - UpgradeableModularAccount.execute, - (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) + UpgradeableModularAccount.execute, (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) ), _encodeSignature(_signerValidation, GLOBAL_VALIDATION, preValidationHookData, "") ); @@ -245,8 +238,7 @@ contract PerHookDataTest is CustomValidationTestBase { ); account1.executeWithAuthorization( abi.encodeCall( - UpgradeableModularAccount.execute, - (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) + UpgradeableModularAccount.execute, (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) ), _encodeSignature(_signerValidation, GLOBAL_VALIDATION, "") ); @@ -258,13 +250,10 @@ contract PerHookDataTest is CustomValidationTestBase { preValidationHookData[1] = PreValidationHookData({index: 1, validationData: abi.encodePacked(_counter)}); vm.prank(owner1); - vm.expectRevert( - abi.encodeWithSelector(UpgradeableModularAccount.ValidationSignatureSegmentMissing.selector) - ); + vm.expectRevert(abi.encodeWithSelector(UpgradeableModularAccount.ValidationSignatureSegmentMissing.selector)); account1.executeWithAuthorization( abi.encodeCall( - UpgradeableModularAccount.execute, - (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) + UpgradeableModularAccount.execute, (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) ), _encodeSignature(_signerValidation, GLOBAL_VALIDATION, preValidationHookData, "") ); @@ -299,8 +288,7 @@ contract PerHookDataTest is CustomValidationTestBase { vm.expectRevert(abi.encodeWithSelector(UpgradeableModularAccount.NonCanonicalEncoding.selector)); account1.executeWithAuthorization( abi.encodeCall( - UpgradeableModularAccount.execute, - (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) + UpgradeableModularAccount.execute, (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) ), _encodeSignature(_signerValidation, GLOBAL_VALIDATION, preValidationHookData, "") ); @@ -343,12 +331,7 @@ contract PerHookDataTest is CustomValidationTestBase { ); return ( - _signerValidation, - true, - true, - new bytes4[](0), - abi.encode(TEST_DEFAULT_VALIDATION_ENTITY_ID, owner1), - hooks + _signerValidation, true, true, new bytes4[](0), abi.encode(TEST_DEFAULT_VALIDATION_ENTITY_ID, owner1), hooks ); } } diff --git a/test/account/SelfCallAuthorization.t.sol b/test/account/SelfCallAuthorization.t.sol index de2839c7..280c236c 100644 --- a/test/account/SelfCallAuthorization.t.sol +++ b/test/account/SelfCallAuthorization.t.sol @@ -85,8 +85,7 @@ contract SelfCallAuthorizationTest is AccountTestBase { // Using global validation, self-call bypasses custom validation needed for ComprehensiveModule.foo _runUserOp( abi.encodeCall( - UpgradeableModularAccount.execute, - (address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())) + UpgradeableModularAccount.execute, (address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())) ), abi.encodeWithSelector( IEntryPoint.FailedOpWithRevert.selector, @@ -152,8 +151,7 @@ contract SelfCallAuthorizationTest is AccountTestBase { // Using global validation, self-call bypasses custom validation needed for ComprehensiveModule.foo _runtimeCall( abi.encodeCall( - UpgradeableModularAccount.execute, - (address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())) + UpgradeableModularAccount.execute, (address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())) ), abi.encodeWithSelector(UpgradeableModularAccount.SelfCallRecursionDepthExceeded.selector) ); @@ -176,9 +174,8 @@ contract SelfCallAuthorizationTest is AccountTestBase { calls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())); calls[1] = Call(address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())); - PackedUserOperation memory userOp = _generateUserOpWithComprehensiveModuleValidation( - abi.encodeCall(IStandardExecutor.executeBatch, (calls)) - ); + PackedUserOperation memory userOp = + _generateUserOpWithComprehensiveModuleValidation(abi.encodeCall(IStandardExecutor.executeBatch, (calls))); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -259,8 +256,7 @@ contract SelfCallAuthorizationTest is AccountTestBase { PackedUserOperation memory userOp = _generateUserOpWithComprehensiveModuleValidation( abi.encodePacked( - IAccountExecute.executeUserOp.selector, - abi.encodeCall(IStandardExecutor.executeBatch, (outerCalls)) + IAccountExecute.executeUserOp.selector, abi.encodeCall(IStandardExecutor.executeBatch, (outerCalls)) ) ); @@ -305,12 +301,7 @@ contract SelfCallAuthorizationTest is AccountTestBase { account1.executeWithAuthorization( abi.encodeCall( UpgradeableModularAccount.installValidation, - ( - ValidationConfigLib.pack(comprehensiveModuleValidation, false, false), - selectors, - "", - new bytes[](0) - ) + (ValidationConfigLib.pack(comprehensiveModuleValidation, false, false), selectors, "", new bytes[](0)) ), _encodeSignature(_signerValidation, GLOBAL_VALIDATION, "") ); diff --git a/test/account/UpgradeableModularAccount.t.sol b/test/account/UpgradeableModularAccount.t.sol index 847ab552..00641f19 100644 --- a/test/account/UpgradeableModularAccount.t.sol +++ b/test/account/UpgradeableModularAccount.t.sol @@ -345,9 +345,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { bytes32 slot = account3.proxiableUUID(); // account has impl from factory - assertEq( - address(factory.accountImplementation()), address(uint160(uint256(vm.load(address(account1), slot)))) - ); + assertEq(address(factory.accountImplementation()), address(uint160(uint256(vm.load(address(account1), slot))))); account1.upgradeToAndCall(address(account3), bytes("")); // account has new impl assertEq(address(account3), address(uint160(uint256(vm.load(address(account1), slot))))); @@ -388,9 +386,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { for (uint256 i = 0; i < accountWrites.length; i++) { bytes32 valWritten = vm.load(addr, accountWrites[i]); // solhint-disable-next-line no-console - console.log( - string.concat("write loc: ", vm.toString(accountWrites[i]), " val: ", vm.toString(valWritten)) - ); + console.log(string.concat("write loc: ", vm.toString(accountWrites[i]), " val: ", vm.toString(valWritten))); } for (uint256 i = 0; i < accountReads.length; i++) { diff --git a/test/comparison/CompareSimpleAccount.t.sol b/test/comparison/CompareSimpleAccount.t.sol index a7f59b69..359e18a8 100644 --- a/test/comparison/CompareSimpleAccount.t.sol +++ b/test/comparison/CompareSimpleAccount.t.sol @@ -137,9 +137,7 @@ contract CompareSimpleAccountTest is Test { sender: account2, nonce: 0, initCode: "", - callData: abi.encodeCall( - SimpleAccount.execute, (address(counter), 0, abi.encodeCall(Counter.increment, ())) - ), + callData: abi.encodeCall(SimpleAccount.execute, (address(counter), 0, abi.encodeCall(Counter.increment, ()))), accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), preVerificationGas: 0, gasFees: _encodeGas(1, 2), diff --git a/test/libraries/HookConfigLib.t.sol b/test/libraries/HookConfigLib.t.sol index 7a4671b8..c8064c67 100644 --- a/test/libraries/HookConfigLib.t.sol +++ b/test/libraries/HookConfigLib.t.sol @@ -53,9 +53,7 @@ contract HookConfigLibTest is Test { } assertEq( - ModuleEntity.unwrap(hookConfig.moduleEntity()), - ModuleEntity.unwrap(hookFunction), - "moduleEntity mismatch" + ModuleEntity.unwrap(hookConfig.moduleEntity()), ModuleEntity.unwrap(hookFunction), "moduleEntity mismatch" ); assertEq(hookConfig.isValidationHook(), isValidation, "isValidation mismatch"); diff --git a/test/libraries/SparseCalldataSegmentLib.t.sol b/test/libraries/SparseCalldataSegmentLib.t.sol index 7edea4e4..374dafd8 100644 --- a/test/libraries/SparseCalldataSegmentLib.t.sol +++ b/test/libraries/SparseCalldataSegmentLib.t.sol @@ -33,8 +33,7 @@ contract SparseCalldataSegmentLibTest is Test { bytes memory encoded = _encodeWithIndex(segments, indices); // Decode - (bytes[] memory decodedBodies, uint8[] memory decodedIndices) = - this.decodeWithIndex(encoded, segments.length); + (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"); @@ -55,11 +54,7 @@ contract SparseCalldataSegmentLibTest is Test { return result; } - function _encodeWithIndex(bytes[] memory segments, uint8[] memory indices) - internal - pure - returns (bytes memory) - { + function _encodeWithIndex(bytes[] memory segments, uint8[] memory indices) internal pure returns (bytes memory) { require(segments.length == indices.length, "segments len != indices len"); bytes memory result = ""; diff --git a/test/libraries/ValidationConfigLib.t.sol b/test/libraries/ValidationConfigLib.t.sol index 4d49c383..f903c413 100644 --- a/test/libraries/ValidationConfigLib.t.sol +++ b/test/libraries/ValidationConfigLib.t.sol @@ -19,8 +19,7 @@ contract ValidationConfigLibTest is Test { bool isGlobal, bool isSignatureValidation ) public { - ValidationConfig validationConfig = - ValidationConfigLib.pack(module, entityId, isGlobal, isSignatureValidation); + ValidationConfig validationConfig = ValidationConfigLib.pack(module, entityId, isGlobal, isSignatureValidation); // Test unpacking underlying (address module2, uint32 entityId2, bool isGlobal2, bool isSignatureValidation2) = diff --git a/test/mocks/SingleSignerFactoryFixture.sol b/test/mocks/SingleSignerFactoryFixture.sol index eec2ab7d..30afdeff 100644 --- a/test/mocks/SingleSignerFactoryFixture.sol +++ b/test/mocks/SingleSignerFactoryFixture.sol @@ -29,9 +29,8 @@ contract SingleSignerFactoryFixture is OptimizedTest { constructor(IEntryPoint _entryPoint, SingleSignerValidation _singleSignerValidation) { entryPoint = _entryPoint; accountImplementation = _deployUpgradeableModularAccount(_entryPoint); - _PROXY_BYTECODE_HASH = keccak256( - abi.encodePacked(type(ERC1967Proxy).creationCode, abi.encode(address(accountImplementation), "")) - ); + _PROXY_BYTECODE_HASH = + keccak256(abi.encodePacked(type(ERC1967Proxy).creationCode, abi.encode(address(accountImplementation), ""))); singleSignerValidation = _singleSignerValidation; self = address(this); } @@ -49,7 +48,6 @@ contract SingleSignerFactoryFixture is OptimizedTest { // short circuit if exists if (addr.code.length == 0) { - LibClone.createDeterministicERC1967( address(accountImplementation), _getImmutableArgs(owner), getSalt(owner, salt) ); @@ -78,8 +76,7 @@ contract SingleSignerFactoryFixture is OptimizedTest { function _getImmutableArgs(address owner) private view returns (bytes memory) { return abi.encodePacked( - ModuleEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), - owner + ModuleEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), owner ); } } diff --git a/test/mocks/modules/ReturnDataModuleMocks.sol b/test/mocks/modules/ReturnDataModuleMocks.sol index b7baf03e..ad552136 100644 --- a/test/mocks/modules/ReturnDataModuleMocks.sol +++ b/test/mocks/modules/ReturnDataModuleMocks.sol @@ -74,10 +74,7 @@ contract ResultConsumerModule is BaseModule, IValidation { revert NotImplemented(); } - function validateRuntime(address, uint32, address sender, uint256, bytes calldata, bytes calldata) - external - view - { + function validateRuntime(address, uint32, address sender, uint256, bytes calldata, bytes calldata) external view { if (sender != address(this)) { revert NotAuthorized(); } diff --git a/test/mocks/modules/ValidationModuleMocks.sol b/test/mocks/modules/ValidationModuleMocks.sol index 3186051f..8704d7e0 100644 --- a/test/mocks/modules/ValidationModuleMocks.sol +++ b/test/mocks/modules/ValidationModuleMocks.sol @@ -112,9 +112,7 @@ contract MockUserOpValidationModule is MockBaseUserOpValidationModule { } contract MockUserOpValidation1HookModule is MockBaseUserOpValidationModule { - function setValidationData(uint256 userOpValidationFunctionData, uint256 preUserOpValidationHook1Data) - external - { + function setValidationData(uint256 userOpValidationFunctionData, uint256 preUserOpValidationHook1Data) external { _userOpValidationFunctionData = userOpValidationFunctionData; _preUserOpValidationHook1Data = preUserOpValidationHook1Data; } diff --git a/test/module/AllowlistModule.t.sol b/test/module/AllowlistModule.t.sol index b0e09c90..0f8dd0e8 100644 --- a/test/module/AllowlistModule.t.sol +++ b/test/module/AllowlistModule.t.sol @@ -146,8 +146,7 @@ contract AllowlistModuleTest is CustomValidationTestBase { for (uint256 i = 0; i < calls.length; i++) { Call memory call = calls[i]; - (bool allowed, bool hasSelectorAllowlist) = - allowlistModule.targetAllowlist(call.target, address(account1)); + (bool allowed, bool hasSelectorAllowlist) = allowlistModule.targetAllowlist(call.target, address(account1)); if (allowed) { if ( hasSelectorAllowlist @@ -177,8 +176,7 @@ contract AllowlistModuleTest is CustomValidationTestBase { for (uint256 i = 0; i < calls.length; i++) { Call memory call = calls[i]; - (bool allowed, bool hasSelectorAllowlist) = - allowlistModule.targetAllowlist(call.target, address(account1)); + (bool allowed, bool hasSelectorAllowlist) = allowlistModule.targetAllowlist(call.target, address(account1)); if (allowed) { if ( hasSelectorAllowlist @@ -235,8 +233,7 @@ contract AllowlistModuleTest is CustomValidationTestBase { bool isCounter = seed % 2 == 0; seed = _next(seed); - address target = - isCounter ? address(counters[seed % counters.length]) : address(uint160(uint256(seed))); + address target = isCounter ? address(counters[seed % counters.length]) : address(uint160(uint256(seed))); bool hasSelectorAllowlist = seed % 2 == 0; seed = _next(seed); @@ -304,12 +301,7 @@ contract AllowlistModuleTest is CustomValidationTestBase { ); return ( - _signerValidation, - true, - true, - new bytes4[](0), - abi.encode(TEST_DEFAULT_VALIDATION_ENTITY_ID, owner1), - hooks + _signerValidation, true, true, new bytes4[](0), abi.encode(TEST_DEFAULT_VALIDATION_ENTITY_ID, owner1), hooks ); } diff --git a/test/module/ERC20TokenLimitModule.t.sol b/test/module/ERC20TokenLimitModule.t.sol index c0aa8719..17c7d864 100644 --- a/test/module/ERC20TokenLimitModule.t.sol +++ b/test/module/ERC20TokenLimitModule.t.sol @@ -40,11 +40,8 @@ contract ERC20TokenLimitModuleTest is AccountTestBase { erc20.mint(address(acct), 10 ether); ExecutionHook[] memory permissionHooks = new ExecutionHook[](1); - permissionHooks[0] = ExecutionHook({ - hookFunction: ModuleEntityLib.pack(address(module), 0), - isPreHook: true, - isPostHook: false - }); + permissionHooks[0] = + ExecutionHook({hookFunction: ModuleEntityLib.pack(address(module), 0), isPreHook: true, isPostHook: false}); // arr idx 0 => functionId of 0 has that spend uint256[] memory limits = new uint256[](1); @@ -83,8 +80,7 @@ contract ERC20TokenLimitModuleTest is AccountTestBase { function _getExecuteWithSpend(uint256 value) internal view returns (bytes memory) { return abi.encodeCall( - UpgradeableModularAccount.execute, - (address(erc20), 0, abi.encodeCall(IERC20.transfer, (recipient, value))) + UpgradeableModularAccount.execute, (address(erc20), 0, abi.encodeCall(IERC20.transfer, (recipient, value))) ); } @@ -97,10 +93,8 @@ contract ERC20TokenLimitModuleTest is AccountTestBase { function test_userOp_executeBatchLimit() public { Call[] memory calls = new Call[](3); - calls[0] = - Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.transfer, (recipient, 1 wei))}); - calls[1] = - Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.transfer, (recipient, 1 ether))}); + calls[0] = Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.transfer, (recipient, 1 wei))}); + calls[1] = Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.transfer, (recipient, 1 ether))}); calls[2] = Call({ target: address(erc20), value: 0, @@ -115,10 +109,8 @@ contract ERC20TokenLimitModuleTest is AccountTestBase { function test_userOp_executeBatch_approveAndTransferLimit() public { Call[] memory calls = new Call[](3); - calls[0] = - Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.approve, (recipient, 1 wei))}); - calls[1] = - Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.transfer, (recipient, 1 ether))}); + calls[0] = Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.approve, (recipient, 1 wei))}); + calls[1] = Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.transfer, (recipient, 1 ether))}); calls[2] = Call({ target: address(erc20), value: 0, @@ -133,10 +125,8 @@ contract ERC20TokenLimitModuleTest is AccountTestBase { function test_userOp_executeBatch_approveAndTransferLimit_fail() public { Call[] memory calls = new Call[](3); - calls[0] = - Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.approve, (recipient, 1 wei))}); - calls[1] = - Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.transfer, (recipient, 1 ether))}); + calls[0] = Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.approve, (recipient, 1 wei))}); + calls[1] = Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.transfer, (recipient, 1 ether))}); calls[2] = Call({ target: address(erc20), value: 0, @@ -155,18 +145,15 @@ contract ERC20TokenLimitModuleTest is AccountTestBase { function test_runtime_executeLimit() public { assertEq(module.limits(0, address(erc20), address(acct)), 10 ether); acct.executeWithAuthorization( - _getExecuteWithSpend(5 ether), - _encodeSignature(ModuleEntityLib.pack(address(validationModule), 0), 1, "") + _getExecuteWithSpend(5 ether), _encodeSignature(ModuleEntityLib.pack(address(validationModule), 0), 1, "") ); assertEq(module.limits(0, address(erc20), address(acct)), 5 ether); } function test_runtime_executeBatchLimit() public { Call[] memory calls = new Call[](3); - calls[0] = - Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.approve, (recipient, 1 wei))}); - calls[1] = - Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.transfer, (recipient, 1 ether))}); + calls[0] = Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.approve, (recipient, 1 wei))}); + calls[1] = Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.transfer, (recipient, 1 ether))}); calls[2] = Call({ target: address(erc20), value: 0, diff --git a/test/module/NativeTokenLimitModule.t.sol b/test/module/NativeTokenLimitModule.t.sol index 540dc902..a36a55a2 100644 --- a/test/module/NativeTokenLimitModule.t.sol +++ b/test/module/NativeTokenLimitModule.t.sol @@ -52,10 +52,7 @@ contract NativeTokenLimitModuleTest is AccountTestBase { vm.prank(address(acct)); acct.installValidation( - ValidationConfigLib.pack(address(validationModule), 0, true, true), - new bytes4[](0), - new bytes(0), - hooks + ValidationConfigLib.pack(address(validationModule), 0, true, true), new bytes4[](0), new bytes(0), hooks ); validationFunction = ModuleEntityLib.pack(address(validationModule), 0); @@ -157,8 +154,7 @@ contract NativeTokenLimitModuleTest is AccountTestBase { vm.startPrank(address(entryPoint)); assertEq(module.limits(0, address(acct)), 10 ether); PackedUserOperation[] memory uos = new PackedUserOperation[](1); - uos[0] = - _getPackedUO(200_000, 200_000, 200_000, 1, abi.encodeCall(IStandardExecutor.executeBatch, (calls))); + uos[0] = _getPackedUO(200_000, 200_000, 200_000, 1, abi.encodeCall(IStandardExecutor.executeBatch, (calls))); entryPoint.handleOps(uos, bundler); assertEq(module.limits(0, address(acct)), 10 ether - 6 ether - 700_001); diff --git a/test/module/SingleSignerValidation.t.sol b/test/module/SingleSignerValidation.t.sol index 9358b59f..3300ce03 100644 --- a/test/module/SingleSignerValidation.t.sol +++ b/test/module/SingleSignerValidation.t.sol @@ -89,9 +89,7 @@ contract SingleSignerValidationTest is AccountTestBase { vm.prank(owner2); account.executeWithAuthorization( abi.encodeCall(UpgradeableModularAccount.execute, (ethRecipient, 1 wei, "")), - _encodeSignature( - ModuleEntityLib.pack(address(singleSignerValidation), newEntityId), GLOBAL_VALIDATION, "" - ) + _encodeSignature(ModuleEntityLib.pack(address(singleSignerValidation), newEntityId), GLOBAL_VALIDATION, "") ); assertEq(ethRecipient.balance, 1 wei); } diff --git a/test/module/TokenReceiverModule.t.sol b/test/module/TokenReceiverModule.t.sol index 3230d221..8697f37e 100644 --- a/test/module/TokenReceiverModule.t.sol +++ b/test/module/TokenReceiverModule.t.sol @@ -35,8 +35,7 @@ contract TokenReceiverModuleTest is OptimizedTest, IERC1155Receiver { function setUp() public { entryPoint = new EntryPoint(); - SingleSignerFactoryFixture factory = - new SingleSignerFactoryFixture(entryPoint, _deploySingleSignerValidation()); + SingleSignerFactoryFixture factory = new SingleSignerFactoryFixture(entryPoint, _deploySingleSignerValidation()); acct = factory.createAccount(address(this), 0); module = _deployTokenReceiverModule(); diff --git a/test/script/Deploy.s.t.sol b/test/script/Deploy.s.t.sol index 105ce9b8..dc13174b 100644 --- a/test/script/Deploy.s.t.sol +++ b/test/script/Deploy.s.t.sol @@ -32,9 +32,7 @@ contract DeployTest is Test { _accountImpl = Create2.computeAddress( bytes32(0), - keccak256( - abi.encodePacked(type(UpgradeableModularAccount).creationCode, abi.encode(address(_entryPoint))) - ), + keccak256(abi.encodePacked(type(UpgradeableModularAccount).creationCode, abi.encode(address(_entryPoint)))), CREATE2_FACTORY ); diff --git a/test/utils/AccountTestBase.sol b/test/utils/AccountTestBase.sol index 733b93e4..30948367 100644 --- a/test/utils/AccountTestBase.sol +++ b/test/utils/AccountTestBase.sol @@ -11,8 +11,7 @@ import {Call, IStandardExecutor} from "../../src/interfaces/IStandardExecutor.so import {SingleSignerValidation} from "../../src/modules/validation/SingleSignerValidation.sol"; import {OptimizedTest} from "./OptimizedTest.sol"; -import {TEST_DEFAULT_VALIDATION_ENTITY_ID as EXT_CONST_TEST_DEFAULT_VALIDATION_ENTITY_ID} from - "./TestConstants.sol"; +import {TEST_DEFAULT_VALIDATION_ENTITY_ID as EXT_CONST_TEST_DEFAULT_VALIDATION_ENTITY_ID} from "./TestConstants.sol"; import {SingleSignerFactoryFixture} from "../mocks/SingleSignerFactoryFixture.sol"; @@ -59,8 +58,7 @@ abstract contract AccountTestBase is OptimizedTest { account1 = factory.createAccount(owner1, 0); vm.deal(address(account1), 100 ether); - _signerValidation = - ModuleEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID); + _signerValidation = ModuleEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID); } function _runExecUserOp(address target, bytes memory callData) internal { @@ -125,9 +123,7 @@ abstract contract AccountTestBase is OptimizedTest { _runtimeCall(abi.encodeCall(IStandardExecutor.execute, (target, 0, callData)), expectedRevertData); } - function _runtimeExecExpFail(address target, bytes memory callData, bytes memory expectedRevertData) - internal - { + function _runtimeExecExpFail(address target, bytes memory callData, bytes memory expectedRevertData) internal { _runtimeCallExpFail(abi.encodeCall(IStandardExecutor.execute, (target, 0, callData)), expectedRevertData); } @@ -217,9 +213,7 @@ abstract contract AccountTestBase is OptimizedTest { for (uint256 i = 0; i < preValidationHookData.length; ++i) { sig = abi.encodePacked( sig, - _packValidationResWithIndex( - preValidationHookData[i].index, preValidationHookData[i].validationData - ) + _packValidationResWithIndex(preValidationHookData[i].index, preValidationHookData[i].validationData) ); } diff --git a/test/utils/CustomValidationTestBase.sol b/test/utils/CustomValidationTestBase.sol index 3b313039..c8a7a52f 100644 --- a/test/utils/CustomValidationTestBase.sol +++ b/test/utils/CustomValidationTestBase.sol @@ -29,10 +29,7 @@ abstract contract CustomValidationTestBase is AccountTestBase { account1 = UpgradeableModularAccount(payable(new ERC1967Proxy{salt: 0}(accountImplementation, ""))); account1.initializeWithValidation( - ValidationConfigLib.pack(validationFunction, isGlobal, isSignatureValidation), - selectors, - installData, - hooks + ValidationConfigLib.pack(validationFunction, isGlobal, isSignatureValidation), selectors, installData, hooks ); vm.deal(address(account1), 100 ether); diff --git a/test/utils/OptimizedTest.sol b/test/utils/OptimizedTest.sol index 870d416a..9194fe39 100644 --- a/test/utils/OptimizedTest.sol +++ b/test/utils/OptimizedTest.sol @@ -29,16 +29,12 @@ abstract contract OptimizedTest is Test { return keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b)); } - function _deployUpgradeableModularAccount(IEntryPoint entryPoint) - internal - returns (UpgradeableModularAccount) - { + function _deployUpgradeableModularAccount(IEntryPoint entryPoint) internal returns (UpgradeableModularAccount) { return _isOptimizedTest() ? UpgradeableModularAccount( payable( deployCode( - "out-optimized/UpgradeableModularAccount.sol/UpgradeableModularAccount.json", - abi.encode(entryPoint) + "out-optimized/UpgradeableModularAccount.sol/UpgradeableModularAccount.json", abi.encode(entryPoint) ) ) ) @@ -53,9 +49,7 @@ abstract contract OptimizedTest is Test { function _deploySingleSignerValidation() internal returns (SingleSignerValidation) { return _isOptimizedTest() - ? SingleSignerValidation( - deployCode("out-optimized/SingleSignerValidation.sol/SingleSignerValidation.json") - ) + ? SingleSignerValidation(deployCode("out-optimized/SingleSignerValidation.sol/SingleSignerValidation.json")) : new SingleSignerValidation(); } } From 3f562f53e13f56a88ba419013e2e2d595aa024f5 Mon Sep 17 00:00:00 2001 From: fangting-alchemy <119372438+fangting-alchemy@users.noreply.github.com> Date: Fri, 26 Jul 2024 13:11:45 -0700 Subject: [PATCH 104/111] Refactor: split IExecution out from IModule and rename vars to reflect new structure (#117) --- src/account/AccountLoupe.sol | 4 +- src/account/ModuleManagerInternals.sol | 7 +-- src/account/UpgradeableModularAccount.sol | 31 ++++++------ src/helpers/KnownSelectors.sol | 7 ++- src/interfaces/IExecution.sol | 39 +++++++++++++++ src/interfaces/IModule.sol | 37 +------------- src/interfaces/IModuleManager.sol | 16 +++++-- src/modules/ERC20TokenLimitModule.sol | 7 +-- src/modules/NativeTokenLimitModule.sol | 7 +-- src/modules/TokenReceiverModule.sol | 12 +++-- .../permissionhooks/AllowlistModule.sol | 5 +- .../validation/SingleSignerValidation.sol | 8 +--- test/account/AccountExecHooks.t.sol | 32 ++++++------- test/account/AccountLoupe.t.sol | 6 +-- test/account/AccountReturnData.t.sol | 8 ++-- test/account/DirectCallsFromModule.t.sol | 18 +++---- test/account/PermittedCallPermissions.t.sol | 8 ++-- test/account/SelfCallAuthorization.t.sol | 2 +- test/account/UpgradeableModularAccount.t.sol | 48 +++++++++---------- test/mocks/MockModule.sol | 17 +++---- test/mocks/modules/ComprehensiveModule.sol | 20 ++++---- test/mocks/modules/DirectCallModule.sol | 4 +- .../modules/MockAccessControlHookModule.sol | 5 +- test/mocks/modules/PermittedCallMocks.sol | 9 ++-- test/mocks/modules/ReturnDataModuleMocks.sol | 15 +++--- test/mocks/modules/ValidationModuleMocks.sol | 17 +++---- test/module/ERC20TokenLimitModule.t.sol | 4 +- test/module/NativeTokenLimitModule.t.sol | 4 +- test/module/TokenReceiverModule.t.sol | 2 +- 29 files changed, 200 insertions(+), 199 deletions(-) create mode 100644 src/interfaces/IExecution.sol diff --git a/src/account/AccountLoupe.sol b/src/account/AccountLoupe.sol index bcd6dbcd..110e7e72 100644 --- a/src/account/AccountLoupe.sol +++ b/src/account/AccountLoupe.sol @@ -22,8 +22,8 @@ abstract contract AccountLoupe is IAccountLoupe { if ( selector == IStandardExecutor.execute.selector || selector == IStandardExecutor.executeBatch.selector || selector == UUPSUpgradeable.upgradeToAndCall.selector - || selector == IModuleManager.installModule.selector - || selector == IModuleManager.uninstallModule.selector + || selector == IModuleManager.installExecution.selector + || selector == IModuleManager.uninstallExecution.selector ) { return address(this); } diff --git a/src/account/ModuleManagerInternals.sol b/src/account/ModuleManagerInternals.sol index 1c5dc6db..c064d1a9 100644 --- a/src/account/ModuleManagerInternals.sol +++ b/src/account/ModuleManagerInternals.sol @@ -10,7 +10,8 @@ import {HookConfigLib} from "../helpers/HookConfigLib.sol"; import {KnownSelectors} from "../helpers/KnownSelectors.sol"; import {ModuleEntityLib} from "../helpers/ModuleEntityLib.sol"; import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; -import {IModule, ManifestExecutionHook, ModuleManifest} from "../interfaces/IModule.sol"; +import {ExecutionManifest, ManifestExecutionHook} from "../interfaces/IExecution.sol"; +import {IModule} from "../interfaces/IModule.sol"; import {HookConfig, IModuleManager, ModuleEntity, ValidationConfig} from "../interfaces/IModuleManager.sol"; import { AccountStorage, @@ -124,7 +125,7 @@ abstract contract ModuleManagerInternals is IModuleManager { hooks.remove(toSetValue(hookConfig)); } - function _installModule(address module, ModuleManifest calldata manifest, bytes memory moduleInstallData) + function _installExecution(address module, ExecutionManifest calldata manifest, bytes memory moduleInstallData) internal { AccountStorage storage _storage = getAccountStorage(); @@ -176,7 +177,7 @@ abstract contract ModuleManagerInternals is IModuleManager { emit ModuleInstalled(module); } - function _uninstallModule(address module, ModuleManifest calldata manifest, bytes memory uninstallData) + function _uninstallExecution(address module, ExecutionManifest calldata manifest, bytes memory uninstallData) internal { AccountStorage storage _storage = getAccountStorage(); diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 8ef40fab..5767f75f 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -20,8 +20,9 @@ import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; import {_coalescePreValidation, _coalesceValidation} from "../helpers/ValidationResHelpers.sol"; import {DIRECT_CALL_VALIDATION_ENTITYID, RESERVED_VALIDATION_DATA_INDEX} from "../helpers/Constants.sol"; + +import {ExecutionManifest} from "../interfaces/IExecution.sol"; import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; -import {ModuleManifest} from "../interfaces/IModule.sol"; import {IModuleManager, ModuleEntity, ValidationConfig} from "../interfaces/IModuleManager.sol"; import {Call, IStandardExecutor} from "../interfaces/IStandardExecutor.sol"; import {IValidation} from "../interfaces/IValidation.sol"; @@ -89,7 +90,7 @@ contract UpgradeableModularAccount is error SignatureSegmentOutOfOrder(); // Wraps execution of a native function with runtime validation and hooks - // Used for upgradeTo, upgradeToAndCall, execute, executeBatch, installModule, uninstallModule + // Used for upgradeTo, upgradeToAndCall, execute, executeBatch, installExecution, uninstallExecution modifier wrapNativeFunction() { (PostExecToRun[] memory postPermissionHooks, PostExecToRun[] memory postExecHooks) = _checkPermittedCallerAndAssociatedHooks(); @@ -224,22 +225,22 @@ contract UpgradeableModularAccount is /// @inheritdoc IModuleManager /// @notice May be validated by a global validation. - function installModule(address module, ModuleManifest calldata manifest, bytes calldata moduleInstallData) - external - override - wrapNativeFunction - { - _installModule(module, manifest, moduleInstallData); + function installExecution( + address module, + ExecutionManifest calldata manifest, + bytes calldata moduleInstallData + ) external override wrapNativeFunction { + _installExecution(module, manifest, moduleInstallData); } /// @inheritdoc IModuleManager /// @notice May be validated by a global validation. - function uninstallModule(address module, ModuleManifest calldata manifest, bytes calldata moduleUninstallData) - external - override - wrapNativeFunction - { - _uninstallModule(module, manifest, moduleUninstallData); + function uninstallExecution( + address module, + ExecutionManifest calldata manifest, + bytes calldata moduleUninstallData + ) external override wrapNativeFunction { + _uninstallExecution(module, manifest, moduleUninstallData); } /// @notice Initializes the account with a validation function added to the global pool. @@ -689,7 +690,7 @@ contract UpgradeableModularAccount is function _globalValidationAllowed(bytes4 selector) internal view returns (bool) { if ( selector == this.execute.selector || selector == this.executeBatch.selector - || selector == this.installModule.selector || selector == this.uninstallModule.selector + || selector == this.installExecution.selector || selector == this.uninstallExecution.selector || selector == this.installValidation.selector || selector == this.uninstallValidation.selector || selector == this.upgradeToAndCall.selector ) { diff --git a/src/helpers/KnownSelectors.sol b/src/helpers/KnownSelectors.sol index ec3b6e6f..6e1cfca0 100644 --- a/src/helpers/KnownSelectors.sol +++ b/src/helpers/KnownSelectors.sol @@ -8,6 +8,8 @@ import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeab import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import {IAccountLoupe} from "../interfaces/IAccountLoupe.sol"; + +import {IExecution} from "../interfaces/IExecution.sol"; import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; import {IModule} from "../interfaces/IModule.sol"; import {IModuleManager} from "../interfaces/IModuleManager.sol"; @@ -23,7 +25,8 @@ library KnownSelectors { // check against IAccount methods selector == IAccount.validateUserOp.selector // check against IModuleManager methods - || selector == IModuleManager.installModule.selector || selector == IModuleManager.uninstallModule.selector + || selector == IModuleManager.installExecution.selector + || selector == IModuleManager.uninstallExecution.selector // check against IERC165 methods || selector == IERC165.supportsInterface.selector // check against UUPSUpgradeable methods @@ -47,7 +50,7 @@ library KnownSelectors { function isIModuleFunction(bytes4 selector) internal pure returns (bool) { return selector == IModule.onInstall.selector || selector == IModule.onUninstall.selector - || selector == IModule.moduleManifest.selector || selector == IModule.moduleMetadata.selector + || selector == IExecution.executionManifest.selector || selector == IModule.moduleMetadata.selector || selector == IExecutionHook.preExecutionHook.selector || selector == IExecutionHook.postExecutionHook.selector || selector == IValidation.validateUserOp.selector || selector == IValidation.validateRuntime.selector || selector == IValidation.validateSignature.selector diff --git a/src/interfaces/IExecution.sol b/src/interfaces/IExecution.sol new file mode 100644 index 00000000..8d21f9e2 --- /dev/null +++ b/src/interfaces/IExecution.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: CC0-1.0 +pragma solidity ^0.8.25; + +import {IModule} from "./IModule.sol"; + +struct ManifestExecutionFunction { + // TODO(erc6900 spec): These fields can be packed into a single word + // The selector to install + bytes4 executionSelector; + // If true, the function won't need runtime validation, and can be called by anyone. + bool isPublic; + // If true, the function can be validated by a global validation function. + bool allowGlobalValidation; +} + +struct ManifestExecutionHook { + // TODO(erc6900 spec): These fields can be packed into a single word + bytes4 executionSelector; + uint32 entityId; + bool isPreHook; + bool isPostHook; +} + +/// @dev A struct describing how the module should be installed on a modular account. +struct ExecutionManifest { + // Execution functions defined in this module to be installed on the MSCA. + ManifestExecutionFunction[] executionFunctions; + ManifestExecutionHook[] executionHooks; + // List of ERC-165 interface IDs to add to account to support introspection checks. This MUST NOT include + // IModule's interface ID. + bytes4[] interfaceIds; +} + +interface IExecution 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/interfaces/IModule.sol b/src/interfaces/IModule.sol index 87272404..10358619 100644 --- a/src/interfaces/IModule.sol +++ b/src/interfaces/IModule.sol @@ -3,24 +3,6 @@ pragma solidity ^0.8.25; import {IERC165} from "@openzeppelin/contracts/interfaces/IERC165.sol"; -struct ManifestExecutionFunction { - // TODO(erc6900 spec): These fields can be packed into a single word - // The selector to install - bytes4 executionSelector; - // If true, the function won't need runtime validation, and can be called by anyone. - bool isPublic; - // If true, the function can be validated by a global validation function. - bool allowGlobalValidation; -} - -struct ManifestExecutionHook { - // TODO(erc6900 spec): These fields can be packed into a single word - bytes4 executionSelector; - uint32 entityId; - bool isPreHook; - bool isPostHook; -} - struct SelectorPermission { bytes4 functionSelector; string permissionDescription; @@ -42,34 +24,19 @@ struct ModuleMetadata { string[] permissionRequest; } -/// @dev A struct describing how the module should be installed on a modular account. -struct ModuleManifest { - // Execution functions defined in this module to be installed on the MSCA. - ManifestExecutionFunction[] executionFunctions; - ManifestExecutionHook[] executionHooks; - // List of ERC-165 interface IDs to add to account to support introspection checks. This MUST NOT include - // IModule's interface ID. - bytes4[] interfaceIds; -} - interface IModule is IERC165 { /// @notice Initialize module data for the modular account. - /// @dev Called by the modular account during `installModule`. + /// @dev Called by the modular account during `installExecution`. /// @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`. + /// @dev Called by the modular account during `uninstallExecution`. /// @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 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 moduleManifest() external pure returns (ModuleManifest memory); - /// @notice Describe the metadata of the module. /// @dev This metadata MUST stay constant over time. /// @return A metadata struct describing the module. diff --git a/src/interfaces/IModuleManager.sol b/src/interfaces/IModuleManager.sol index d2e63f74..74e3a338 100644 --- a/src/interfaces/IModuleManager.sol +++ b/src/interfaces/IModuleManager.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: CC0-1.0 pragma solidity ^0.8.25; -import {ModuleManifest} from "./IModule.sol"; +import {ExecutionManifest} from "./IExecution.sol"; type ModuleEntity is bytes24; @@ -19,8 +19,11 @@ interface IModuleManager { /// @param manifest the manifest describing functions to install /// @param moduleInstallData Optional data to be decoded and used by the module to setup initial module data /// for the modular account. - function installModule(address module, ModuleManifest calldata manifest, bytes calldata moduleInstallData) - external; + function installExecution( + address module, + ExecutionManifest calldata manifest, + bytes calldata moduleInstallData + ) external; /// @notice Temporary install function - pending a different user-supplied install config & manifest validation /// path. @@ -60,6 +63,9 @@ interface IModuleManager { /// @param manifest the manifest describing functions to uninstall. /// @param moduleUninstallData Optional data to be decoded and used by the module to clear module data for the /// modular account. - function uninstallModule(address module, ModuleManifest calldata manifest, bytes calldata moduleUninstallData) - external; + function uninstallExecution( + address module, + ExecutionManifest calldata manifest, + bytes calldata moduleUninstallData + ) external; } diff --git a/src/modules/ERC20TokenLimitModule.sol b/src/modules/ERC20TokenLimitModule.sol index 85d90585..bb13dec3 100644 --- a/src/modules/ERC20TokenLimitModule.sol +++ b/src/modules/ERC20TokenLimitModule.sol @@ -13,8 +13,7 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; -import {ModuleManifest, ModuleMetadata} from "../interfaces/IModule.sol"; -import {IModule} from "../interfaces/IModule.sol"; +import {IModule, ModuleMetadata} from "../interfaces/IModule.sol"; import {Call, IStandardExecutor} from "../interfaces/IStandardExecutor.sol"; import {BaseModule, IERC165} from "./BaseModule.sol"; @@ -114,10 +113,6 @@ contract ERC20TokenLimitModule is BaseModule, IExecutionHook { revert NotImplemented(); } - /// @inheritdoc IModule - // solhint-disable-next-line no-empty-blocks - function moduleManifest() external pure override returns (ModuleManifest memory) {} - /// @inheritdoc IModule function moduleMetadata() external pure virtual override returns (ModuleMetadata memory) { ModuleMetadata memory metadata; diff --git a/src/modules/NativeTokenLimitModule.sol b/src/modules/NativeTokenLimitModule.sol index f09994be..5baed34d 100644 --- a/src/modules/NativeTokenLimitModule.sol +++ b/src/modules/NativeTokenLimitModule.sol @@ -6,8 +6,7 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {IExecutionHook} from "../interfaces/IExecutionHook.sol"; -import {ModuleManifest, ModuleMetadata} from "../interfaces/IModule.sol"; -import {IModule} from "../interfaces/IModule.sol"; +import {IModule, ModuleMetadata} from "../interfaces/IModule.sol"; import {Call, IStandardExecutor} from "../interfaces/IStandardExecutor.sol"; import {IValidationHook} from "../interfaces/IValidationHook.sol"; @@ -117,10 +116,6 @@ contract NativeTokenLimitModule is BaseModule, IExecutionHook, IValidationHook { override {} // solhint-disable-line no-empty-blocks - /// @inheritdoc IModule - // solhint-disable-next-line no-empty-blocks - function moduleManifest() external pure override returns (ModuleManifest memory) {} - /// @inheritdoc IModule function moduleMetadata() external pure virtual override returns (ModuleMetadata memory) { ModuleMetadata memory metadata; diff --git a/src/modules/TokenReceiverModule.sol b/src/modules/TokenReceiverModule.sol index b4cf839b..3991a29a 100644 --- a/src/modules/TokenReceiverModule.sol +++ b/src/modules/TokenReceiverModule.sol @@ -4,14 +4,16 @@ pragma solidity ^0.8.25; import {IERC1155Receiver} from "@openzeppelin/contracts/interfaces/IERC1155Receiver.sol"; import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; -import {IModule, ManifestExecutionFunction, ModuleManifest, ModuleMetadata} from "../interfaces/IModule.sol"; +import {ExecutionManifest, ManifestExecutionFunction} from "../interfaces/IExecution.sol"; +import {ExecutionManifest, IExecution} from "../interfaces/IExecution.sol"; +import {IModule, ModuleMetadata} from "../interfaces/IModule.sol"; import {BaseModule} from "./BaseModule.sol"; /// @title Token Receiver Module /// @author ERC-6900 Authors /// @notice This module allows modular accounts to receive various types of tokens by implementing /// required token receiver interfaces. -contract TokenReceiverModule is BaseModule, IERC721Receiver, IERC1155Receiver { +contract TokenReceiverModule is BaseModule, IExecution, IERC721Receiver, IERC1155Receiver { string internal constant _NAME = "Token Receiver Module"; string internal constant _VERSION = "1.0.0"; string internal constant _AUTHOR = "ERC-6900 Authors"; @@ -54,9 +56,9 @@ contract TokenReceiverModule is BaseModule, IERC721Receiver, IERC1155Receiver { // solhint-disable-next-line no-empty-blocks function onUninstall(bytes calldata) external pure override {} - /// @inheritdoc IModule - function moduleManifest() external pure override returns (ModuleManifest memory) { - ModuleManifest memory manifest; + /// @inheritdoc IExecution + function executionManifest() external pure override returns (ExecutionManifest memory) { + ExecutionManifest memory manifest; manifest.executionFunctions = new ManifestExecutionFunction[](3); manifest.executionFunctions[0] = ManifestExecutionFunction({ diff --git a/src/modules/permissionhooks/AllowlistModule.sol b/src/modules/permissionhooks/AllowlistModule.sol index b90fd6c2..2fdf1362 100644 --- a/src/modules/permissionhooks/AllowlistModule.sol +++ b/src/modules/permissionhooks/AllowlistModule.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.25; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; -import {ModuleManifest, ModuleMetadata} from "../../interfaces/IModule.sol"; +import {ModuleMetadata} from "../../interfaces/IModule.sol"; import {Call, IStandardExecutor} from "../../interfaces/IStandardExecutor.sol"; import {IValidationHook} from "../../interfaces/IValidationHook.sol"; @@ -104,9 +104,6 @@ contract AllowlistModule is IValidationHook, BaseModule { return metadata; } - // solhint-disable-next-line no-empty-blocks - function moduleManifest() external pure override returns (ModuleManifest memory) {} - function _checkAllowlistCalldata(bytes calldata callData) internal view { if (bytes4(callData[:4]) == IStandardExecutor.execute.selector) { (address target,, bytes memory data) = abi.decode(callData[4:], (address, uint256, bytes)); diff --git a/src/modules/validation/SingleSignerValidation.sol b/src/modules/validation/SingleSignerValidation.sol index a384cda7..16ac6225 100644 --- a/src/modules/validation/SingleSignerValidation.sol +++ b/src/modules/validation/SingleSignerValidation.sol @@ -6,7 +6,7 @@ import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; -import {IModule, ModuleManifest, ModuleMetadata} from "../../interfaces/IModule.sol"; +import {IModule, ModuleMetadata} from "../../interfaces/IModule.sol"; import {IValidation} from "../../interfaces/IValidation.sol"; import {BaseModule} from "../BaseModule.sol"; import {ISingleSignerValidation} from "./ISingleSignerValidation.sol"; @@ -118,12 +118,6 @@ contract SingleSignerValidation is ISingleSignerValidation, BaseModule { // ┃ Module interface functions ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - /// @inheritdoc IModule - function moduleManifest() external pure override returns (ModuleManifest memory) { - ModuleManifest memory manifest; - return manifest; - } - /// @inheritdoc IModule function moduleMetadata() external pure virtual override returns (ModuleMetadata memory) { ModuleMetadata memory metadata; diff --git a/test/account/AccountExecHooks.t.sol b/test/account/AccountExecHooks.t.sol index 434ad0e6..2e0a90bc 100644 --- a/test/account/AccountExecHooks.t.sol +++ b/test/account/AccountExecHooks.t.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {IExecutionHook} from "../../src/interfaces/IExecutionHook.sol"; import { + ExecutionManifest, IModule, ManifestExecutionFunction, - ManifestExecutionHook, - ModuleManifest -} from "../../src/interfaces/IModule.sol"; + ManifestExecutionHook +} from "../../src/interfaces/IExecution.sol"; +import {IExecutionHook} from "../../src/interfaces/IExecutionHook.sol"; import {MockModule} from "../mocks/MockModule.sol"; import {AccountTestBase} from "../utils/AccountTestBase.sol"; @@ -20,7 +20,7 @@ contract AccountExecHooksTest is AccountTestBase { uint32 internal constant _POST_HOOK_FUNCTION_ID_2 = 2; uint32 internal constant _BOTH_HOOKS_FUNCTION_ID_3 = 3; - ModuleManifest internal _m1; + ExecutionManifest internal _m1; event ModuleInstalled(address indexed module); event ModuleUninstalled(address indexed module, bool indexed callbacksSucceeded); @@ -40,7 +40,7 @@ contract AccountExecHooksTest is AccountTestBase { } function test_preExecHook_install() public { - _installModule1WithHooks( + _installExecution1WithHooks( ManifestExecutionHook({ executionSelector: _EXEC_SELECTOR, entityId: _PRE_HOOK_FUNCTION_ID_1, @@ -74,11 +74,11 @@ contract AccountExecHooksTest is AccountTestBase { function test_preExecHook_uninstall() public { test_preExecHook_install(); - _uninstallModule(mockModule1); + _uninstallExecution(mockModule1); } function test_execHookPair_install() public { - _installModule1WithHooks( + _installExecution1WithHooks( ManifestExecutionHook({ executionSelector: _EXEC_SELECTOR, entityId: _BOTH_HOOKS_FUNCTION_ID_3, @@ -122,11 +122,11 @@ contract AccountExecHooksTest is AccountTestBase { function test_execHookPair_uninstall() public { test_execHookPair_install(); - _uninstallModule(mockModule1); + _uninstallExecution(mockModule1); } function test_postOnlyExecHook_install() public { - _installModule1WithHooks( + _installExecution1WithHooks( ManifestExecutionHook({ executionSelector: _EXEC_SELECTOR, entityId: _POST_HOOK_FUNCTION_ID_2, @@ -154,10 +154,10 @@ contract AccountExecHooksTest is AccountTestBase { function test_postOnlyExecHook_uninstall() public { test_postOnlyExecHook_install(); - _uninstallModule(mockModule1); + _uninstallExecution(mockModule1); } - function _installModule1WithHooks(ManifestExecutionHook memory execHooks) internal { + function _installExecution1WithHooks(ManifestExecutionHook memory execHooks) internal { _m1.executionHooks.push(execHooks); mockModule1 = new MockModule(_m1); @@ -167,22 +167,22 @@ contract AccountExecHooksTest is AccountTestBase { emit ModuleInstalled(address(mockModule1)); vm.startPrank(address(entryPoint)); - account1.installModule({ + account1.installExecution({ module: address(mockModule1), - manifest: mockModule1.moduleManifest(), + manifest: mockModule1.executionManifest(), moduleInstallData: bytes("") }); vm.stopPrank(); } - function _uninstallModule(MockModule module) internal { + function _uninstallExecution(MockModule module) internal { vm.expectEmit(true, true, true, true); emit ReceivedCall(abi.encodeCall(IModule.onUninstall, (bytes(""))), 0); vm.expectEmit(true, true, true, true); emit ModuleUninstalled(address(module), true); vm.startPrank(address(entryPoint)); - account1.uninstallModule(address(module), module.moduleManifest(), bytes("")); + account1.uninstallExecution(address(module), module.executionManifest(), bytes("")); vm.stopPrank(); } } diff --git a/test/account/AccountLoupe.t.sol b/test/account/AccountLoupe.t.sol index 988e3dd5..bc75d356 100644 --- a/test/account/AccountLoupe.t.sol +++ b/test/account/AccountLoupe.t.sol @@ -27,7 +27,7 @@ contract AccountLoupeTest is CustomValidationTestBase { _customValidationSetup(); vm.startPrank(address(entryPoint)); - account1.installModule(address(comprehensiveModule), comprehensiveModule.moduleManifest(), ""); + account1.installExecution(address(comprehensiveModule), comprehensiveModule.executionManifest(), ""); vm.stopPrank(); } @@ -40,9 +40,9 @@ contract AccountLoupeTest is CustomValidationTestBase { selectorsToCheck[2] = UUPSUpgradeable.upgradeToAndCall.selector; - selectorsToCheck[3] = IModuleManager.installModule.selector; + selectorsToCheck[3] = IModuleManager.installExecution.selector; - selectorsToCheck[4] = IModuleManager.uninstallModule.selector; + selectorsToCheck[4] = IModuleManager.uninstallExecution.selector; for (uint256 i = 0; i < selectorsToCheck.length; i++) { address module = account1.getExecutionFunctionHandler(selectorsToCheck[i]); diff --git a/test/account/AccountReturnData.t.sol b/test/account/AccountReturnData.t.sol index 4f6fef9d..56da489e 100644 --- a/test/account/AccountReturnData.t.sol +++ b/test/account/AccountReturnData.t.sol @@ -30,15 +30,15 @@ contract AccountReturnDataTest is AccountTestBase { // Add the result creator module to the account vm.startPrank(address(entryPoint)); - account1.installModule({ + account1.installExecution({ module: address(resultCreatorModule), - manifest: resultCreatorModule.moduleManifest(), + manifest: resultCreatorModule.executionManifest(), moduleInstallData: "" }); // Add the result consumer module to the account - account1.installModule({ + account1.installExecution({ module: address(resultConsumerModule), - manifest: resultConsumerModule.moduleManifest(), + manifest: resultConsumerModule.executionManifest(), moduleInstallData: "" }); // Allow the result consumer module to perform direct calls to the account diff --git a/test/account/DirectCallsFromModule.t.sol b/test/account/DirectCallsFromModule.t.sol index 6d108540..889b65b9 100644 --- a/test/account/DirectCallsFromModule.t.sol +++ b/test/account/DirectCallsFromModule.t.sol @@ -34,9 +34,9 @@ contract DirectCallsFromModuleTest is AccountTestBase { } function test_Fail_DirectCallModuleUninstalled() external { - _installModule(); + _installExecution(); - _uninstallModule(); + _uninstallExecution(); vm.prank(address(_module)); vm.expectRevert(_buildDirectCallDisallowedError(IStandardExecutor.execute.selector)); @@ -44,7 +44,7 @@ contract DirectCallsFromModuleTest is AccountTestBase { } function test_Fail_DirectCallModuleCallOtherSelector() external { - _installModule(); + _installExecution(); Call[] memory calls = new Call[](0); @@ -58,7 +58,7 @@ contract DirectCallsFromModuleTest is AccountTestBase { /* -------------------------------------------------------------------------- */ function test_Pass_DirectCallFromModulePrank() external { - _installModule(); + _installExecution(); vm.prank(address(_module)); account1.execute(address(0), 0, ""); @@ -68,7 +68,7 @@ contract DirectCallsFromModuleTest is AccountTestBase { } function test_Pass_DirectCallFromModuleCallback() external { - _installModule(); + _installExecution(); bytes memory encodedCall = abi.encodeCall(DirectCallModule.directCall, ()); @@ -86,7 +86,7 @@ contract DirectCallsFromModuleTest is AccountTestBase { function test_Flow_DirectCallFromModuleSequence() external { // Install => Succeesfully call => uninstall => fail to call - _installModule(); + _installExecution(); vm.prank(address(_module)); account1.execute(address(0), 0, ""); @@ -94,7 +94,7 @@ contract DirectCallsFromModuleTest is AccountTestBase { assertTrue(_module.preHookRan()); assertTrue(_module.postHookRan()); - _uninstallModule(); + _uninstallExecution(); vm.prank(address(_module)); vm.expectRevert(_buildDirectCallDisallowedError(IStandardExecutor.execute.selector)); @@ -105,7 +105,7 @@ contract DirectCallsFromModuleTest is AccountTestBase { /* Internals */ /* -------------------------------------------------------------------------- */ - function _installModule() internal { + function _installExecution() internal { bytes4[] memory selectors = new bytes4[](1); selectors[0] = IStandardExecutor.execute.selector; @@ -122,7 +122,7 @@ contract DirectCallsFromModuleTest is AccountTestBase { account1.installValidation(validationConfig, selectors, "", hooks); } - function _uninstallModule() internal { + function _uninstallExecution() internal { vm.prank(address(entryPoint)); account1.uninstallValidation(_moduleEntity, "", new bytes[](1)); } diff --git a/test/account/PermittedCallPermissions.t.sol b/test/account/PermittedCallPermissions.t.sol index 0b1007f7..f66382da 100644 --- a/test/account/PermittedCallPermissions.t.sol +++ b/test/account/PermittedCallPermissions.t.sol @@ -22,15 +22,15 @@ contract PermittedCallPermissionsTest is AccountTestBase { // Add the result creator module to the account vm.startPrank(address(entryPoint)); - account1.installModule({ + account1.installExecution({ module: address(resultCreatorModule), - manifest: resultCreatorModule.moduleManifest(), + manifest: resultCreatorModule.executionManifest(), moduleInstallData: "" }); // Add the permitted caller module to the account - account1.installModule({ + account1.installExecution({ module: address(permittedCallerModule), - manifest: permittedCallerModule.moduleManifest(), + manifest: permittedCallerModule.executionManifest(), moduleInstallData: "" }); vm.stopPrank(); diff --git a/test/account/SelfCallAuthorization.t.sol b/test/account/SelfCallAuthorization.t.sol index de2839c7..ed158a30 100644 --- a/test/account/SelfCallAuthorization.t.sol +++ b/test/account/SelfCallAuthorization.t.sol @@ -31,7 +31,7 @@ contract SelfCallAuthorizationTest is AccountTestBase { validationSelectors[0] = ComprehensiveModule.foo.selector; vm.startPrank(address(entryPoint)); - account1.installModule(address(comprehensiveModule), comprehensiveModule.moduleManifest(), ""); + account1.installExecution(address(comprehensiveModule), comprehensiveModule.executionManifest(), ""); account1.installValidation( ValidationConfigLib.pack(comprehensiveModuleValidation, false, false), validationSelectors, diff --git a/test/account/UpgradeableModularAccount.t.sol b/test/account/UpgradeableModularAccount.t.sol index 847ab552..0bb281a2 100644 --- a/test/account/UpgradeableModularAccount.t.sol +++ b/test/account/UpgradeableModularAccount.t.sol @@ -13,7 +13,7 @@ import {ModuleManagerInternals} from "../../src/account/ModuleManagerInternals.s import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; import {IAccountLoupe} from "../../src/interfaces/IAccountLoupe.sol"; -import {ModuleManifest} from "../../src/interfaces/IModule.sol"; +import {ExecutionManifest} from "../../src/interfaces/IExecution.sol"; import {IModuleManager} from "../../src/interfaces/IModuleManager.sol"; import {Call} from "../../src/interfaces/IStandardExecutor.sol"; @@ -40,7 +40,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { address public ethRecipient; Counter public counter; - ModuleManifest internal _manifest; + ExecutionManifest internal _manifest; event ModuleInstalled(address indexed module); event ModuleUninstalled(address indexed module, bool indexed callbacksSucceeded); @@ -236,14 +236,14 @@ contract UpgradeableModularAccountTest is AccountTestBase { assertEq(ethRecipient.balance, 2 wei); } - function test_installModule() public { + function test_installExecution() public { vm.startPrank(address(entryPoint)); vm.expectEmit(true, true, true, true); emit ModuleInstalled(address(tokenReceiverModule)); - IModuleManager(account1).installModule({ + IModuleManager(account1).installExecution({ module: address(tokenReceiverModule), - manifest: tokenReceiverModule.moduleManifest(), + manifest: tokenReceiverModule.executionManifest(), moduleInstallData: abi.encode(uint48(1 days)) }); @@ -252,21 +252,21 @@ contract UpgradeableModularAccountTest is AccountTestBase { assertEq(handler, address(tokenReceiverModule)); } - function test_installModule_PermittedCallSelectorNotInstalled() public { + function test_installExecution_PermittedCallSelectorNotInstalled() public { vm.startPrank(address(entryPoint)); - ModuleManifest memory m; + ExecutionManifest memory m; MockModule mockModuleWithBadPermittedExec = new MockModule(m); - IModuleManager(account1).installModule({ + IModuleManager(account1).installExecution({ module: address(mockModuleWithBadPermittedExec), - manifest: mockModuleWithBadPermittedExec.moduleManifest(), + manifest: mockModuleWithBadPermittedExec.executionManifest(), moduleInstallData: "" }); } - function test_installModule_interfaceNotSupported() public { + function test_installExecution_interfaceNotSupported() public { vm.startPrank(address(entryPoint)); address badModule = address(1); @@ -274,16 +274,16 @@ contract UpgradeableModularAccountTest is AccountTestBase { abi.encodeWithSelector(ModuleManagerInternals.ModuleInterfaceNotSupported.selector, address(badModule)) ); - ModuleManifest memory m; + ExecutionManifest memory m; - IModuleManager(account1).installModule({module: address(badModule), manifest: m, moduleInstallData: ""}); + IModuleManager(account1).installExecution({module: address(badModule), manifest: m, moduleInstallData: ""}); } - function test_installModule_alreadyInstalled() public { - ModuleManifest memory m = tokenReceiverModule.moduleManifest(); + function test_installExecution_alreadyInstalled() public { + ExecutionManifest memory m = tokenReceiverModule.executionManifest(); vm.prank(address(entryPoint)); - IModuleManager(account1).installModule({ + IModuleManager(account1).installExecution({ module: address(tokenReceiverModule), manifest: m, moduleInstallData: abi.encode(uint48(1 days)) @@ -296,28 +296,28 @@ contract UpgradeableModularAccountTest is AccountTestBase { TokenReceiverModule.onERC721Received.selector ) ); - IModuleManager(account1).installModule({ + IModuleManager(account1).installExecution({ module: address(tokenReceiverModule), manifest: m, moduleInstallData: abi.encode(uint48(1 days)) }); } - function test_uninstallModule_default() public { + function test_uninstallExecution_default() public { vm.startPrank(address(entryPoint)); ComprehensiveModule module = new ComprehensiveModule(); - IModuleManager(account1).installModule({ + IModuleManager(account1).installExecution({ module: address(module), - manifest: module.moduleManifest(), + manifest: module.executionManifest(), moduleInstallData: "" }); vm.expectEmit(true, true, true, true); emit ModuleUninstalled(address(module), true); - IModuleManager(account1).uninstallModule({ + IModuleManager(account1).uninstallExecution({ module: address(module), - manifest: module.moduleManifest(), + manifest: module.executionManifest(), moduleUninstallData: "" }); @@ -325,14 +325,14 @@ contract UpgradeableModularAccountTest is AccountTestBase { assertEq(handler, address(0)); } - function _installModuleWithExecHooks() internal returns (MockModule module) { + function _installExecutionWithExecHooks() internal returns (MockModule module) { vm.startPrank(address(entryPoint)); module = new MockModule(_manifest); - IModuleManager(account1).installModule({ + IModuleManager(account1).installExecution({ module: address(module), - manifest: module.moduleManifest(), + manifest: module.executionManifest(), moduleInstallData: "" }); diff --git a/test/mocks/MockModule.sol b/test/mocks/MockModule.sol index c6866bd4..0ada610f 100644 --- a/test/mocks/MockModule.sol +++ b/test/mocks/MockModule.sol @@ -3,8 +3,9 @@ pragma solidity ^0.8.19; import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +import {ExecutionManifest} from "../../src/interfaces/IExecution.sol"; import {IExecutionHook} from "../../src/interfaces/IExecutionHook.sol"; -import {IModule, ModuleManifest, ModuleMetadata} from "../../src/interfaces/IModule.sol"; +import {IModule, ModuleMetadata} from "../../src/interfaces/IModule.sol"; import {IValidation} from "../../src/interfaces/IValidation.sol"; contract MockModule is ERC165 { @@ -27,26 +28,26 @@ contract MockModule is ERC165 { // ┃ Module interface functions ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - constructor(ModuleManifest memory _moduleManifest) { - _manifest = abi.encode(_moduleManifest); + constructor(ExecutionManifest memory _executionManifest) { + _manifest = abi.encode(_executionManifest); } - function _getManifest() internal view returns (ModuleManifest memory) { - ModuleManifest memory m = abi.decode(_manifest, (ModuleManifest)); + function _getManifest() internal view returns (ExecutionManifest memory) { + ExecutionManifest memory m = abi.decode(_manifest, (ExecutionManifest)); return m; } - function _castToPure(function() internal view returns (ModuleManifest memory) fnIn) + function _castToPure(function() internal view returns (ExecutionManifest memory) fnIn) internal pure - returns (function() internal pure returns (ModuleManifest memory) fnOut) + returns (function() internal pure returns (ExecutionManifest memory) fnOut) { assembly ("memory-safe") { fnOut := fnIn } } - function moduleManifest() external pure returns (ModuleManifest memory) { + function executionManifest() external pure returns (ExecutionManifest memory) { return _castToPure(_getManifest)(); } diff --git a/test/mocks/modules/ComprehensiveModule.sol b/test/mocks/modules/ComprehensiveModule.sol index fe0f6cfe..420e2a82 100644 --- a/test/mocks/modules/ComprehensiveModule.sol +++ b/test/mocks/modules/ComprehensiveModule.sol @@ -3,20 +3,22 @@ pragma solidity ^0.8.19; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; -import {IExecutionHook} from "../../../src/interfaces/IExecutionHook.sol"; import { + ExecutionManifest, + IExecution, ManifestExecutionFunction, - ManifestExecutionHook, - ModuleManifest, - ModuleMetadata -} from "../../../src/interfaces/IModule.sol"; -import {ModuleManifest} from "../../../src/interfaces/IModule.sol"; + ManifestExecutionHook +} from "../../../src/interfaces/IExecution.sol"; + +import {IExecution} from "../../../src/interfaces/IExecution.sol"; +import {IExecutionHook} from "../../../src/interfaces/IExecutionHook.sol"; +import {ModuleMetadata} from "../../../src/interfaces/IModule.sol"; import {IValidation} from "../../../src/interfaces/IValidation.sol"; import {IValidationHook} from "../../../src/interfaces/IValidationHook.sol"; import {BaseModule} from "../../../src/modules/BaseModule.sol"; -contract ComprehensiveModule is IValidation, IValidationHook, IExecutionHook, BaseModule { +contract ComprehensiveModule is IExecution, IValidation, IValidationHook, IExecutionHook, BaseModule { enum EntityId { PRE_VALIDATION_HOOK_1, PRE_VALIDATION_HOOK_2, @@ -129,8 +131,8 @@ contract ComprehensiveModule is IValidation, IValidationHook, IExecutionHook, Ba revert NotImplemented(); } - function moduleManifest() external pure override returns (ModuleManifest memory) { - ModuleManifest memory manifest; + function executionManifest() external pure override returns (ExecutionManifest memory) { + ExecutionManifest memory manifest; manifest.executionFunctions = new ManifestExecutionFunction[](1); manifest.executionFunctions[0] = ManifestExecutionFunction({ diff --git a/test/mocks/modules/DirectCallModule.sol b/test/mocks/modules/DirectCallModule.sol index 9e0f611d..6cd053f7 100644 --- a/test/mocks/modules/DirectCallModule.sol +++ b/test/mocks/modules/DirectCallModule.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; import {IExecutionHook} from "../../../src/interfaces/IExecutionHook.sol"; -import {ModuleManifest, ModuleMetadata} from "../../../src/interfaces/IModule.sol"; +import {ModuleMetadata} from "../../../src/interfaces/IModule.sol"; import {IStandardExecutor} from "../../../src/interfaces/IStandardExecutor.sol"; import {BaseModule} from "../../../src/modules/BaseModule.sol"; @@ -15,8 +15,6 @@ contract DirectCallModule is BaseModule, IExecutionHook { function onUninstall(bytes calldata) external override {} - function moduleManifest() external pure override returns (ModuleManifest memory) {} - function directCall() external returns (bytes memory) { return IStandardExecutor(msg.sender).execute(address(this), 0, abi.encodeCall(this.getData, ())); } diff --git a/test/mocks/modules/MockAccessControlHookModule.sol b/test/mocks/modules/MockAccessControlHookModule.sol index e3f47b86..2c076d3f 100644 --- a/test/mocks/modules/MockAccessControlHookModule.sol +++ b/test/mocks/modules/MockAccessControlHookModule.sol @@ -3,8 +3,7 @@ pragma solidity ^0.8.25; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; -import {ModuleManifest, ModuleMetadata} from "../../../src/interfaces/IModule.sol"; - +import {ModuleMetadata} from "../../../src/interfaces/IModule.sol"; import {IStandardExecutor} from "../../../src/interfaces/IStandardExecutor.sol"; import {IValidationHook} from "../../../src/interfaces/IValidationHook.sol"; import {BaseModule} from "../../../src/modules/BaseModule.sol"; @@ -74,6 +73,4 @@ contract MockAccessControlHookModule is IValidationHook, BaseModule { } function moduleMetadata() external pure override returns (ModuleMetadata memory) {} - - function moduleManifest() external pure override returns (ModuleManifest memory) {} } diff --git a/test/mocks/modules/PermittedCallMocks.sol b/test/mocks/modules/PermittedCallMocks.sol index bea02e78..167279e5 100644 --- a/test/mocks/modules/PermittedCallMocks.sol +++ b/test/mocks/modules/PermittedCallMocks.sol @@ -1,18 +1,19 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {ManifestExecutionFunction, ModuleManifest, ModuleMetadata} from "../../../src/interfaces/IModule.sol"; +import {ExecutionManifest, IExecution, ManifestExecutionFunction} from "../../../src/interfaces/IExecution.sol"; +import {ModuleMetadata} from "../../../src/interfaces/IModule.sol"; import {BaseModule} from "../../../src/modules/BaseModule.sol"; import {ResultCreatorModule} from "./ReturnDataModuleMocks.sol"; -contract PermittedCallerModule is BaseModule { +contract PermittedCallerModule is IExecution, BaseModule { function onInstall(bytes calldata) external override {} function onUninstall(bytes calldata) external override {} - function moduleManifest() external pure override returns (ModuleManifest memory) { - ModuleManifest memory manifest; + function executionManifest() external pure override returns (ExecutionManifest memory) { + ExecutionManifest memory manifest; manifest.executionFunctions = new ManifestExecutionFunction[](2); manifest.executionFunctions[0].executionSelector = this.usePermittedCallAllowed.selector; diff --git a/test/mocks/modules/ReturnDataModuleMocks.sol b/test/mocks/modules/ReturnDataModuleMocks.sol index b7baf03e..d0e8180e 100644 --- a/test/mocks/modules/ReturnDataModuleMocks.sol +++ b/test/mocks/modules/ReturnDataModuleMocks.sol @@ -3,7 +3,8 @@ pragma solidity ^0.8.19; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; -import {ManifestExecutionFunction, ModuleManifest, ModuleMetadata} from "../../../src/interfaces/IModule.sol"; +import {ExecutionManifest, IExecution, ManifestExecutionFunction} from "../../../src/interfaces/IExecution.sol"; +import {ModuleMetadata} from "../../../src/interfaces/IModule.sol"; import {DIRECT_CALL_VALIDATION_ENTITYID} from "../../../src/helpers/Constants.sol"; @@ -22,7 +23,7 @@ contract RegularResultContract { } } -contract ResultCreatorModule is BaseModule { +contract ResultCreatorModule is IExecution, BaseModule { function onInstall(bytes calldata) external override {} function onUninstall(bytes calldata) external override {} @@ -35,8 +36,8 @@ contract ResultCreatorModule is BaseModule { return keccak256("foo"); } - function moduleManifest() external pure override returns (ModuleManifest memory) { - ModuleManifest memory manifest; + function executionManifest() external pure override returns (ExecutionManifest memory) { + ExecutionManifest memory manifest; manifest.executionFunctions = new ManifestExecutionFunction[](2); manifest.executionFunctions[0] = ManifestExecutionFunction({ @@ -56,7 +57,7 @@ contract ResultCreatorModule is BaseModule { function moduleMetadata() external pure override returns (ModuleMetadata memory) {} } -contract ResultConsumerModule is BaseModule, IValidation { +contract ResultConsumerModule is IExecution, BaseModule, IValidation { ResultCreatorModule public immutable RESULT_CREATOR; RegularResultContract public immutable REGULAR_RESULT_CONTRACT; @@ -114,8 +115,8 @@ contract ResultConsumerModule is BaseModule, IValidation { function onUninstall(bytes calldata) external override {} - function moduleManifest() external pure override returns (ModuleManifest memory) { - ModuleManifest memory manifest; + function executionManifest() external pure override returns (ExecutionManifest memory) { + ExecutionManifest memory manifest; manifest.executionFunctions = new ManifestExecutionFunction[](2); manifest.executionFunctions[0] = ManifestExecutionFunction({ diff --git a/test/mocks/modules/ValidationModuleMocks.sol b/test/mocks/modules/ValidationModuleMocks.sol index 3186051f..3470495f 100644 --- a/test/mocks/modules/ValidationModuleMocks.sol +++ b/test/mocks/modules/ValidationModuleMocks.sol @@ -3,12 +3,13 @@ pragma solidity ^0.8.19; import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; -import {ManifestExecutionFunction, ModuleManifest, ModuleMetadata} from "../../../src/interfaces/IModule.sol"; +import {ExecutionManifest, IExecution, ManifestExecutionFunction} from "../../../src/interfaces/IExecution.sol"; +import {ModuleMetadata} from "../../../src/interfaces/IModule.sol"; import {IValidation} from "../../../src/interfaces/IValidation.sol"; import {IValidationHook} from "../../../src/interfaces/IValidationHook.sol"; import {BaseModule} from "../../../src/modules/BaseModule.sol"; -abstract contract MockBaseUserOpValidationModule is IValidation, IValidationHook, BaseModule { +abstract contract MockBaseUserOpValidationModule is IExecution, IValidation, IValidationHook, BaseModule { enum EntityId { USER_OP_VALIDATION, PRE_VALIDATION_HOOK_1, @@ -97,8 +98,8 @@ contract MockUserOpValidationModule is MockBaseUserOpValidationModule { // ┃ Module interface functions ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - function moduleManifest() external pure override returns (ModuleManifest memory) { - ModuleManifest memory manifest; + function executionManifest() external pure override returns (ExecutionManifest memory) { + ExecutionManifest memory manifest; manifest.executionFunctions = new ManifestExecutionFunction[](1); manifest.executionFunctions[0] = ManifestExecutionFunction({ @@ -129,8 +130,8 @@ contract MockUserOpValidation1HookModule is MockBaseUserOpValidationModule { // ┃ Module interface functions ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - function moduleManifest() external pure override returns (ModuleManifest memory) { - ModuleManifest memory manifest; + function executionManifest() external pure override returns (ExecutionManifest memory) { + ExecutionManifest memory manifest; manifest.executionFunctions = new ManifestExecutionFunction[](1); manifest.executionFunctions[0] = ManifestExecutionFunction({ @@ -164,8 +165,8 @@ contract MockUserOpValidation2HookModule is MockBaseUserOpValidationModule { // ┃ Module interface functions ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - function moduleManifest() external pure override returns (ModuleManifest memory) { - ModuleManifest memory manifest; + function executionManifest() external pure override returns (ExecutionManifest memory) { + ExecutionManifest memory manifest; manifest.executionFunctions = new ManifestExecutionFunction[](1); manifest.executionFunctions[0] = ManifestExecutionFunction({ diff --git a/test/module/ERC20TokenLimitModule.t.sol b/test/module/ERC20TokenLimitModule.t.sol index c0aa8719..96c846e4 100644 --- a/test/module/ERC20TokenLimitModule.t.sol +++ b/test/module/ERC20TokenLimitModule.t.sol @@ -13,7 +13,7 @@ import {ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; -import {ModuleManifest} from "../../src/interfaces/IModule.sol"; +import {ExecutionManifest} from "../../src/interfaces/IExecution.sol"; import {Call, IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; import {ERC20TokenLimitModule} from "../../src/modules/ERC20TokenLimitModule.sol"; import {MockModule} from "../mocks/MockModule.sol"; @@ -24,7 +24,7 @@ contract ERC20TokenLimitModuleTest is AccountTestBase { address public recipient = address(1); MockERC20 public erc20; address payable public bundler = payable(address(2)); - ModuleManifest internal _m; + ExecutionManifest internal _m; MockModule public validationModule = new MockModule(_m); ModuleEntity public validationFunction; diff --git a/test/module/NativeTokenLimitModule.t.sol b/test/module/NativeTokenLimitModule.t.sol index 540dc902..895cd6da 100644 --- a/test/module/NativeTokenLimitModule.t.sol +++ b/test/module/NativeTokenLimitModule.t.sol @@ -10,7 +10,7 @@ import {ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {HookConfigLib} from "../../src/helpers/HookConfigLib.sol"; import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; -import {ModuleManifest} from "../../src/interfaces/IModule.sol"; +import {ExecutionManifest} from "../../src/interfaces/IExecution.sol"; import {Call, IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol"; import {NativeTokenLimitModule} from "../../src/modules/NativeTokenLimitModule.sol"; import {MockModule} from "../mocks/MockModule.sol"; @@ -20,7 +20,7 @@ import {AccountTestBase} from "../utils/AccountTestBase.sol"; contract NativeTokenLimitModuleTest is AccountTestBase { address public recipient = address(1); address payable public bundler = payable(address(2)); - ModuleManifest internal _m; + ExecutionManifest internal _m; MockModule public validationModule = new MockModule(_m); ModuleEntity public validationFunction; diff --git a/test/module/TokenReceiverModule.t.sol b/test/module/TokenReceiverModule.t.sol index 3230d221..d5df2a97 100644 --- a/test/module/TokenReceiverModule.t.sol +++ b/test/module/TokenReceiverModule.t.sol @@ -56,7 +56,7 @@ contract TokenReceiverModuleTest is OptimizedTest, IERC1155Receiver { function _initModule() internal { vm.startPrank(address(entryPoint)); - acct.installModule(address(module), module.moduleManifest(), ""); + acct.installExecution(address(module), module.executionManifest(), ""); vm.stopPrank(); } From e26f0d581f9b290fa018f89466f6bd4a7f51cdd4 Mon Sep 17 00:00:00 2001 From: zer0dot Date: Fri, 26 Jul 2024 17:03:09 -0400 Subject: [PATCH 105/111] fix: adapt new factory to bytecode-appended SMA --- src/account/AccountFactory.sol | 28 +++++++++++------------ test/mocks/SingleSignerFactoryFixture.sol | 3 --- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/src/account/AccountFactory.sol b/src/account/AccountFactory.sol index da82b3c4..ae7db475 100644 --- a/src/account/AccountFactory.sol +++ b/src/account/AccountFactory.sol @@ -9,6 +9,8 @@ import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; import {UpgradeableModularAccount} from "../account/UpgradeableModularAccount.sol"; import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; +import {ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; +import {LibClone} from "solady/utils/LibClone.sol"; contract AccountFactory is Ownable { UpgradeableModularAccount public immutable ACCOUNT_IMPL; @@ -38,20 +40,12 @@ contract AccountFactory is Ownable { * account creation */ function createAccount(address owner, uint256 salt, uint32 entityId) external returns (UpgradeableModularAccount) { - bytes32 combinedSalt = getSalt(owner, salt, entityId); - address addr = Create2.computeAddress(combinedSalt, _PROXY_BYTECODE_HASH); + address addr = getAddress(owner, salt, entityId); // short circuit if exists if (addr.code.length == 0) { - bytes memory pluginInstallData = abi.encode(entityId, owner); - // not necessary to check return addr since next call will fail if so - new ERC1967Proxy{salt: combinedSalt}(address(ACCOUNT_IMPL), ""); - // point proxy to actual implementation and init plugins - UpgradeableModularAccount(payable(addr)).initializeWithValidation( - ValidationConfigLib.pack(SINGLE_SIGNER_VALIDATION, entityId, true, true), - new bytes4[](0), - pluginInstallData, - new bytes[](0) + LibClone.createDeterministicERC1967( + address(ACCOUNT_IMPL), _getImmutableArgs(owner, entityId), getSalt(owner, salt, entityId) ); } @@ -71,13 +65,19 @@ contract AccountFactory is Ownable { } /** - * Calculate the counterfactual address of this account as it would be returned by createAccount() + * calculate the counterfactual address of this account as it would be returned by createAccount() */ - function getAddress(address owner, uint256 salt, uint32 entityId) external view returns (address) { - return Create2.computeAddress(getSalt(owner, salt, entityId), _PROXY_BYTECODE_HASH); + function getAddress(address owner, uint256 salt, uint32 entityId) public view returns (address) { + return LibClone.predictDeterministicAddressERC1967( + address(ACCOUNT_IMPL), _getImmutableArgs(owner, entityId), getSalt(owner, salt, entityId), address(this) + ); } function getSalt(address owner, uint256 salt, uint32 entityId) public pure returns (bytes32) { return keccak256(abi.encodePacked(owner, salt, entityId)); } + + function _getImmutableArgs(address owner, uint32 entityId) private view returns (bytes memory) { + return abi.encodePacked(ModuleEntityLib.pack(address(SINGLE_SIGNER_VALIDATION), entityId), owner); + } } diff --git a/test/mocks/SingleSignerFactoryFixture.sol b/test/mocks/SingleSignerFactoryFixture.sol index 30afdeff..ac044b63 100644 --- a/test/mocks/SingleSignerFactoryFixture.sol +++ b/test/mocks/SingleSignerFactoryFixture.sol @@ -24,15 +24,12 @@ contract SingleSignerFactoryFixture is OptimizedTest { IEntryPoint public entryPoint; - address public self; - constructor(IEntryPoint _entryPoint, SingleSignerValidation _singleSignerValidation) { entryPoint = _entryPoint; accountImplementation = _deployUpgradeableModularAccount(_entryPoint); _PROXY_BYTECODE_HASH = keccak256(abi.encodePacked(type(ERC1967Proxy).creationCode, abi.encode(address(accountImplementation), ""))); singleSignerValidation = _singleSignerValidation; - self = address(this); } /** From 2e9878f6830cf2682593adeb038b42d49b2fb4b3 Mon Sep 17 00:00:00 2001 From: Adam Egyed <5456061+adamegyed@users.noreply.github.com> Date: Fri, 26 Jul 2024 17:38:34 -0400 Subject: [PATCH 106/111] feat: add alpha.0 deployments (#116) --- .gitignore | 8 + .../Deploy.s.sol/421614/run-1722008916.json | 214 ++++++++++++++++++ deployments/arb-sepolia.md | 21 ++ 3 files changed, 243 insertions(+) create mode 100644 broadcast/Deploy.s.sol/421614/run-1722008916.json create mode 100644 deployments/arb-sepolia.md diff --git a/.gitignore b/.gitignore index 39dd4f5d..386aebb6 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,11 @@ lcov.info # env vars .env + +# deployments +broadcast/**/run-latest.json +broadcast/**/dry-run/**/* + +# misc +.DS_Store +**/.DS_Store diff --git a/broadcast/Deploy.s.sol/421614/run-1722008916.json b/broadcast/Deploy.s.sol/421614/run-1722008916.json new file mode 100644 index 00000000..21b808df --- /dev/null +++ b/broadcast/Deploy.s.sol/421614/run-1722008916.json @@ -0,0 +1,214 @@ +{ + "transactions": [ + { + "hash": "0xf77f7595a83b6517b0bca017c6fe29990665132ba5bc0fc2e5f7e760e15f3a9e", + "transactionType": "CREATE2", + "contractName": "UpgradeableModularAccount", + "contractAddress": "0x0809bf385117a43a322a4e31d459c0ecaa3b1a08", + "function": null, + "arguments": [ + "0x0000000071727De22E5E9d8BAf0edAc6f37da032" + ], + "transaction": { + "from": "0x7f89ed1f3f0d52d303904101305471bca3cde710", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x4438e33", + "value": "0x0", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060c03461012057601f615f8938819003918201601f19168301916001600160401b038311848410176101255780849260209460405283398101031261012057516001600160a01b0381168103610120573060805260a052600080516020615f698339815191525460ff8160081c1661010f5760ff808216036100c7575b604051615e2d908161013c82396080518181816116180152613543015260a05181818161057b01528181611a9601528181611bfa01528181611f480152818161278201526129ad0152f35b60ff9081191617600080516020615f69833981519152557fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2602060405160ff8152a13861007c565b63f92ee8a960e01b60005260046000fd5b600080fd5b634e487b7160e01b600052604160045260246000fdfe60806040526004361015610026575b36156100245761001c612887565b602081519101f35b005b60003560e01c806214490e1461018557806301ffc9a714610180578063104179a81461017b5780631626ba7e1461017657806319822f7c146101715780632de0f9341461016c57806334fcd5be146101675780633fa3f96914610162578063443f5daa1461015d5780634f1ef2861461015857806351fd4e1e1461015357806352d1902d1461014e5780635bf8fca214610149578063642f9dd414610144578063756e30981461013f578063808f49bc1461013a5780638dd7712f14610135578063ad3cb1cc14610130578063b0d691fe1461012b578063b61d27f614610126578063b6b1ccfe146101215763d087d2880361000e57611ee7565b611c98565b611c1e565b611bcd565b611b6e565b611a4e565b6119d7565b611892565b61179e565b6116a3565b6115f0565b611325565b6111a2565b610d5f565b610a7c565b610951565b610828565b61052b565b6104b2565b610380565b6102d6565b610278565b9181601f840112156101bb5782359167ffffffffffffffff83116101bb576020808501948460051b0101116101bb57565b600080fd5b9181601f840112156101bb5782359167ffffffffffffffff83116101bb57602083818601950101116101bb57565b9060806003198301126101bb5760043565ffffffffffff19811681036101bb579160243567ffffffffffffffff81116101bb578161022e9160040161018a565b9290929160443567ffffffffffffffff81116101bb5781610251916004016101c0565b929092916064359067ffffffffffffffff82116101bb576102749160040161018a565b9091565b346101bb576100246102a76102a761028f366101ee565b9361029f98959698939193612993565b979099612ea5565b612caa565b7fffffffff000000000000000000000000000000000000000000000000000000008116036101bb57565b346101bb5760206003193601126101bb5760206102fd6004356102f8816102ac565b611fb1565b6040519015158152f35b6004359067ffffffffffffffff19821682036101bb57565b602060408183019282815284518094520192019060005b8181106103435750505090565b909192602060606001926040875167ffffffffffffffff198151168352848101511515858401520151151560408201520194019101919091610336565b346101bb5760206003193601126101bb5760026103d861039e610307565b67ffffffffffffffff19166000527f9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e42602052604060002090565b0180546103e4816120a1565b9160005b82811061040157604051806103fd868261031f565b0390f35b8065ffffffffffff1961041660019385612e21565b90549060031b1c166104966104456104376104378465ffffffffffff191690565b67ffffffffffffffff191690565b9161048d61046766020000000000008316151592660100000000000016151590565b916104846104736110bf565b67ffffffffffffffff199096168652565b15156020850152565b15156040830152565b6104a08287612152565b526104ab8186612152565b50016103e8565b346101bb5760406003193601126101bb5760043560243567ffffffffffffffff81116101bb576020916104ec6104f29236906004016101c0565b91612342565b7fffffffff0000000000000000000000000000000000000000000000000000000060405191168152f35b90816101209103126101bb5790565b346101bb5760606003193601126101bb5760043567ffffffffffffffff81116101bb5761055c90369060040161051c565b602435906044359073ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001633036107ca576060810160046105b1828461270b565b905010610768576101008201936105dd6104376105d76105d1888761270b565b906121a3565b9061216b565b9161063d600160ff61062a6106246105fe6105f88c8b61270b565b906127c9565b357fff000000000000000000000000000000000000000000000000000000000000001690565b60f81c90565b161484610637848861270b565b90613a3c565b600261067d8467ffffffffffffffff19166000527f9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e42602052604060002090565b0154151590816106ff575b506106d5576106b9836106bf936106af6106a86103fd996106c59861270b565b80916121bf565b9390923690613262565b906143f2565b91613342565b6040519081529081906020820190565b7f5f49f00a0000000000000000000000000000000000000000000000000000000060005260046000fd5b7f8dd7712f00000000000000000000000000000000000000000000000000000000915061075f6107596107537fffffffff00000000000000000000000000000000000000000000000000000000938861270b565b906121b1565b906131fc565b16141538610688565b610759610778916107c69361270b565b7ffcfc5aad000000000000000000000000000000000000000000000000000000006000527fffffffff0000000000000000000000000000000000000000000000000000000016600452602490565b6000fd5b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f6163636f756e743a206e6f742066726f6d20456e747279506f696e74000000006044820152fd5b346101bb5760206003193601126101bb57602061084f60043561084a816102ac565b61253d565b73ffffffffffffffffffffffffffffffffffffffff60405191168152f35b60005b8381106108805750506000910152565b8181015183820152602001610870565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f6020936108cc8151809281875287808801910161086d565b0116010190565b602081016020825282518091526040820191602060408360051b8301019401926000915b83831061090657505050505090565b9091929394602080610942837fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc086600196030187528951610890565b970193019301919392906108f7565b60206003193601126101bb5760043567ffffffffffffffff81116101bb5761097d90369060040161018a565b610985612993565b909261099083612089565b9261099e604051948561107e565b8084527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06109cb82612089565b0160005b818110610a6b57505060005b8181106109ff576103fd856109f3886102a788612caa565b604051918291826108d3565b80610a4f610a18610a1360019486886126c1565b612701565b6020610a258487896126c1565b0135610a49610a42610a3886898b6126c1565b604081019061270b565b369161114d565b91613361565b610a598288612152565b52610a648187612152565b50016109db565b8060606020809389010152016109cf565b346101bb57610a8a366101ee565b7f9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e4054969590949193919067ffffffffffffffff610ae2610adc60ff60088c901c1615610ad5565b1590565b9a60ff1690565b60ff1690565b1680159081610cd0575b6001149081610cc6575b159081610cbd575b50610c9357610b7f9688610b7660017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff007f9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e405416177f9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e4055565b610c1f5761275c565b610b8557005b610bf07fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff7f9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e4054167f9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e4055565b604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d290602090a1005b610c8e6101007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff7f9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e405416177f9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e4055565b61275c565b7ff92ee8a90000000000000000000000000000000000000000000000000000000060005260046000fd5b90501538610afe565b303b159150610af6565b899150610aec565b73ffffffffffffffffffffffffffffffffffffffff8116036101bb57565b3590610d0182610cd8565b565b60606003198201126101bb57600435610d1b81610cd8565b9160243567ffffffffffffffff81116101bb57606060031982850301126101bb57600401916044359067ffffffffffffffff82116101bb57610274916004016101c0565b346101bb57610d6d36610d03565b9190610d84610d7a612993565b929094369161114d565b9160208101610d938183613381565b91905060005b828110610f4a57505050610dad8180613465565b905060005b818110610f2257505060408101610dc981836134c9565b92905060005b838110610e9d575050505073ffffffffffffffffffffffffffffffffffffffff60019416803b156101bb5761002494610e3a60006102a795604051809381927f8a91b0e300000000000000000000000000000000000000000000000000000000835260048301611692565b038183875af19081610e82575b50610e7d575060005b1515907fd7b1465298613b13a57ba2ee63b7b364d3a086a9ffcf7f591a9fa01166ed5097600080a3612caa565b610e50565b80610e916000610e979361107e565b806115e5565b38610e47565b80610f10610ebf610eba600194610eb488886134c9565b90612e8b565b612e9b565b7fffffffff00000000000000000000000000000000000000000000000000000000166000527f9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e43602052604060002090565b610f1a815461351d565b905501610dcf565b80610f44610f3f610eba600194610f398880613465565b906134b9565b6147bf565b01610db2565b80611028610f6c610f67600194610f61878a613381565b906133d5565b6133ef565b6110228b85610fef610f9e85517fffffffff000000000000000000000000000000000000000000000000000000001690565b7fffffffff00000000000000000000000000000000000000000000000000000000166000527f9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e41602052604060002090565b0192611002602082015163ffffffff1690565b61101c60606110146040850151151590565b930151151590565b92614700565b906147ac565b01610d99565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6060810190811067ffffffffffffffff82111761107957604052565b61102e565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff82111761107957604052565b60405190610d0160608361107e565b60405190610d016101208361107e565b67ffffffffffffffff811161107957601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b919091611124816110de565b611131604051918261107e565b809382825282116101bb57818160009384602080950137010152565b929192611159826110de565b91611167604051938461107e565b8294818452818301116101bb578281602093846000960137010152565b9080601f830112156101bb5781602061119f9335910161114d565b90565b60406003193601126101bb576004356111ba81610cd8565b60243567ffffffffffffffff81116101bb576111da903690600401611184565b906111e361352c565b6111eb612993565b926111f461352c565b6040517f52d1902d00000000000000000000000000000000000000000000000000000000815260208160048173ffffffffffffffffffffffffffffffffffffffff88165afa600091816112f4575b5061128b577f4c9c8ce30000000000000000000000000000000000000000000000000000000060005273ffffffffffffffffffffffffffffffffffffffff841660045260246000fd5b92837f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8695036112c75750926102a76102a79261002495615592565b7faa1d49a40000000000000000000000000000000000000000000000000000000060005260045260246000fd5b61131791925060203d60201161131e575b61130f818361107e565b810190612878565b9038611242565b503d611305565b346101bb5761133336610d03565b9161134961133f612993565b939094369161114d565b73ffffffffffffffffffffffffffffffffffffffff85169182156115bb57611373610ad18761483c565b611577576113818180613465565b905060005b8181106115275750506020810161139d8183613381565b91905060005b8281106114d257505050604081016113bb81836134c9565b92905060005b8381106114a95750505050813b156101bb57600061140c91604051809381927f6d61fe7000000000000000000000000000000000000000000000000000000000835260048301611692565b038183865af19081611494575b5061146157836114276127ef565b9061145d6040519283927f1672bd93000000000000000000000000000000000000000000000000000000008452600484016135dc565b0390fd5b61002492916102a7917fb4a437488482177b2d124ce7c50e57d8f8d42a9896b525c9c497ee0d533a95de600080a2612caa565b80610e9160006114a39361107e565b38611419565b806114c0610ebf610eba600194610eb488886134c9565b6114ca81546135c1565b9055016113c1565b806115216114e9610f67600194610f61878a613381565b61151b8c85610fef610f9e85517fffffffff000000000000000000000000000000000000000000000000000000001690565b90614ee6565b016113a3565b806115718961153f610eba600195610f398980613465565b611558602061155286610f398b80613465565b016135b7565b61156b604061155287610f398c80613465565b916149a6565b01611386565b7f2e06ed7c0000000000000000000000000000000000000000000000000000000060005273ffffffffffffffffffffffffffffffffffffffff861660045260246000fd5b7f21507ad00000000000000000000000000000000000000000000000000000000060005260046000fd5b60009103126101bb57565b346101bb5760006003193601126101bb5773ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001630036116685760206040517f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8152f35b7fe07c8dba0000000000000000000000000000000000000000000000000000000060005260046000fd5b90602061119f928181520190610890565b60406003193601126101bb5760043567ffffffffffffffff81116101bb576116cf9036906004016101c0565b60243567ffffffffffffffff81116101bb57600091611757600261174561173c61039e611701889736906004016101c0565b806117146104376105d7838698966121a3565b946117378d878c600160ff61172f6106246105fe8a8a6127c9565b161492613a3c565b6121bf565b90878b85613cbd565b0161175136848861114d565b90613fca565b93611767604051809381936127e1565b039082305af1906117766127ef565b911561179557906117896103fd92612caa565b60405191829182611692565b50602081519101fd5b346101bb5760206003193601126101bb5760016117c0600435610f9e816102ac565b0180546117cc816120a1565b9160005b8281106117e557604051806103fd868261031f565b8065ffffffffffff196117fa60019385612e21565b90549060031b1c1661181a6104456104378367ffffffffffffffff191690565b6118248287612152565b5261182f8186612152565b50016117d0565b602060408183019282815284518094520192019060005b81811061185a5750505090565b82517fffffffff000000000000000000000000000000000000000000000000000000001684526020938401939092019160010161184d565b346101bb5760206003193601126101bb576118ab610307565b60046118eb8267ffffffffffffffff19166000527f9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e42602052604060002090565b01546118f681612089565b91611904604051938461107e565b8183527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe061193183612089565b0136602085013760005b82811061195057604051806103fd8682611836565b807fffffffff000000000000000000000000000000000000000000000000000000006119be60019360046119b88767ffffffffffffffff19166000527f9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e42602052604060002090565b01612e21565b90549060031b1c166119d08287612152565b520161193b565b346101bb5760206003193601126101bb576119fe60016119f861039e610307565b0161281f565b60405180916020820160208352815180915260206040840192019060005b818110611a2a575050500390f35b825167ffffffffffffffff1916845285945060209384019390920191600101611a1c565b346101bb5760406003193601126101bb5760043567ffffffffffffffff81116101bb57611a7f90369060040161051c565b73ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000163303611b2f57600080611b01611afa611aef6002611ae461039e6104376105d76105d16101008c018c61270b565b016117513636611118565b94606081019061270b565b80916121fa565b90611b11604051809381936127e1565b039082305af190611b206127ef565b91156117955761002490612caa565b7fd663742a0000000000000000000000000000000000000000000000000000000060005260046000fd5b60405190611b6860208361107e565b60008252565b346101bb5760006003193601126101bb576103fd6040805190611b91818361107e565b600582527f352e302e30000000000000000000000000000000000000000000000000000000602083015251918291602083526020830190610890565b346101bb5760006003193601126101bb57602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b60606003193601126101bb57600435611c3681610cd8565b60243560443567ffffffffffffffff81116101bb576103fd926102a7611c7e611c66611c849436906004016101c0565b939095610a49611c74612993565b989096369161114d565b93612caa565b604051918291602083526020830190610890565b346101bb5760606003193601126101bb57611cb1610307565b60243567ffffffffffffffff81116101bb57611cd19036906004016101c0565b9060443567ffffffffffffffff81116101bb57611cf290369060040161018a565b929091611cfd612993565b939094611d3e8767ffffffffffffffff19166000527f9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e42602052604060002090565b91611d4888615229565b81611dda575b5050611d5c600182016140de565b6002810180549060005b828110611dbe5750505060040194855460005b818110611da257610024876102a78881898960608a901c60408b901c63ffffffff165b506152c5565b600190611db7611db18a6142a3565b8a615cb9565b5001611d79565b600190611dd3611dcd846142a3565b84615cb9565b5001611d66565b9295949093966001830195865492611df860028601948554906135cf565b8703611ebd576000998a5b89548c1015611e6457611e5c81611e578e8d611d9c611e428f8f600199611e3592611e2d92612dca565b959094612e21565b90549060031b1c60401b90565b606081901c9160409190911c63ffffffff1690565b6140b1565b9b019a611e03565b9a50919397509193976000995b88548b1015611eab57611ea381611e578d8c611d9c611e426104378f8f99611e9b9160019b612dca565b9590946142b6565b9a0199611e71565b50945094509450949095503880611d4e565b7fa24a13a60000000000000000000000000000000000000000000000000000000060005260046000fd5b346101bb5760006003193601126101bb576040517f35567e1a0000000000000000000000000000000000000000000000000000000081523060048201526000602482015260208160448173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000165afa8015611fac576103fd91600091611f8d575b506040519081529081906020820190565b611fa6915060203d60201161131e5761130f818361107e565b38611f7c565b612336565b7fffffffff0000000000000000000000000000000000000000000000000000000081167fffffffff000000000000000000000000000000000000000000000000000000008114612082577f01ffc9a7000000000000000000000000000000000000000000000000000000001461207c57612076907fffffffff00000000000000000000000000000000000000000000000000000000166000527f9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e43602052604060002090565b54151590565b50600190565b5050600090565b67ffffffffffffffff81116110795760051b60200190565b906120ab82612089565b6120b8604051918261107e565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06120e68294612089565b019060005b8281106120f757505050565b6020906040516121068161105d565b6000815260008382015260006040820152828285010152016120eb565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b80518210156121665760209160051b010190565b612123565b9190913567ffffffffffffffff1981169260188110612188575050565b67ffffffffffffffff19929350829060180360031b1b161690565b906018116101bb5790601890565b906004116101bb5790600490565b90929192836019116101bb5783116101bb57601901917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe70190565b90929192836004116101bb5783116101bb57600401916003190190565b90929192836018116101bb5783116101bb57601801917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe80190565b90601a116101bb5790601a90565b9092919283601a116101bb5783116101bb57601a01917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe60190565b908160209103126101bb575161119f816102ac565b601f82602094937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0938186528686013760008582860101520116010190565b9160a09363ffffffff73ffffffffffffffffffffffffffffffffffffffff928361119f9a9895168652166020850152166040830152606082015281608082015201916122b0565b6040513d6000823e3d90fd5b6123a792612353610437828561216b565b926123b1610ad1606086901c604087901c63ffffffff1697909667ffffffffffffffff19166000527f9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e42602052604060002090565b5460081c60ff1690565b6124ef57916123da826020959373ffffffffffffffffffffffffffffffffffffffff9795612217565b959093612419604051978896879586947fe7db7f7e000000000000000000000000000000000000000000000000000000008652339030600488016122ef565b0392165afa908115611fac577f1626ba7e00000000000000000000000000000000000000000000000000000000917fffffffff00000000000000000000000000000000000000000000000000000000916000916124c0575b50161461249c577fffffffff0000000000000000000000000000000000000000000000000000000090565b7f1626ba7e0000000000000000000000000000000000000000000000000000000090565b6124e2915060203d6020116124e8575b6124da818361107e565b81019061229b565b38612471565b503d6124d0565b7f5f5943f70000000000000000000000000000000000000000000000000000000060005273ffffffffffffffffffffffffffffffffffffffff841660045263ffffffff851660245260446000fd5b7fffffffff0000000000000000000000000000000000000000000000000000000081167fb61d27f6000000000000000000000000000000000000000000000000000000008114908115612697575b811561266d575b8115612643575b8115612619575b506126145761260f73ffffffffffffffffffffffffffffffffffffffff917fffffffff00000000000000000000000000000000000000000000000000000000166000527f9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e41602052604060002090565b541690565b503090565b7f443f5daa00000000000000000000000000000000000000000000000000000000915014386125a0565b7f51fd4e1e0000000000000000000000000000000000000000000000000000000081149150612599565b7f4f1ef2860000000000000000000000000000000000000000000000000000000081149150612592565b7f34fcd5be000000000000000000000000000000000000000000000000000000008114915061258b565b91908110156121665760051b810135907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa1813603018212156101bb570190565b3561119f81610cd8565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1813603018212156101bb570180359067ffffffffffffffff82116101bb576020019181360383136101bb57565b9061276b969594939291612ea5565b73ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000167f9f08b8dca66d3393166c297eebdbe382963a15cce40f3a2f4bf32378553fe65a600080a2565b90601810156121665760180190565b90156121665790565b908092918237016000815290565b3d1561281a573d90612800826110de565b9161280e604051938461107e565b82523d6000602084013e565b606090565b906040519182815491828252602082019060005260206000209260005b818110612851575050610d019250038361107e565b845460401b67ffffffffffffffff191683526001948501948794506020909301920161283c565b908160209103126101bb575190565b7fffffffff000000000000000000000000000000000000000000000000000000006000351673ffffffffffffffffffffffffffffffffffffffff612916827fffffffff00000000000000000000000000000000000000000000000000000000166000527f9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e41602052604060002090565b541690811561296657506000908161292c612993565b93909260405136838237828136810182815203925af19161294b6127ef565b921561295e57906102a761119f92612caa565b825160208401fd5b7ffcfc5aad0000000000000000000000000000000000000000000000000000000060005260045260246000fd5b60609073ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001633141580612c0a575b80612b7b575b612a5f575b61119f6001611ae47fffffffff00000000000000000000000000000000000000000000000000000000600035167fffffffff00000000000000000000000000000000000000000000000000000000166000527f9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e41602052604060002090565b90503360601b7fffffffffffffffffffffffffffffffffffffffff000000000000000000000000166bffffffff000000000000000017612a9f8136613789565b612ae260016119f88367ffffffffffffffff19166000527f9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e42602052604060002090565b80519060005b828110612b3b575050506002611ae4612b359267ffffffffffffffff19166000527f9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e42602052604060002090565b906129e1565b80612b75612b5d612b4e60019486612152565b5167ffffffffffffffff191690565b612b673636611118565b612b6f611b59565b91614112565b01612ae8565b50612c05610ad1612bfb7fffffffff00000000000000000000000000000000000000000000000000000000600035167fffffffff00000000000000000000000000000000000000000000000000000000166000527f9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e41602052604060002090565b5460a01c60ff1690565b6129dc565b50303314156129d6565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8015612c50576000190190565b612c14565b60409063ffffffff61119f94931681528160208201520190610890565b63ffffffff61119f949373ffffffffffffffffffffffffffffffffffffffff6060941683521660208201528160408201520190610890565b8051805b612cb6575050565b612cbf90612c43565b91612cca8383612152565b5191612ce2602084015167ffffffffffffffff191690565b67ffffffffffffffff19811615612dc057925163ffffffff604085901c169460609490941c939084803b156101bb57612d506000929183926040519485809481937f5d413a810000000000000000000000000000000000000000000000000000000083528d60048401612c55565b03925af19081612dab575b50612da05750505061145d612d6e6127ef565b6040519384937f12c4c3e400000000000000000000000000000000000000000000000000000000855260048501612c72565b919350915080612cae565b80610e916000612dba9361107e565b38612d5b565b5091509180612cae565b90821015612166576102749160051b81019061270b565b9190913565ffffffffffff19811692601a8110612dfc575050565b65ffffffffffff199293508290601a0360031b1b161690565b65ffffffffffff191690565b80548210156121665760005260206000200190600090565b80546801000000000000000081101561107957612e5b91600182018155612e21565b77ffffffffffffffffffffffffffffffffffffffffffffffff829392549160031b9260401c831b921b1916179055565b91908110156121665760051b0190565b3561119f816102ac565b90969592939491612ebc8265ffffffffffff191690565b93612ed167ffffffffffffffff198616610437565b90612f108267ffffffffffffffff19166000527f9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e42602052604060002090565b9460005b8281106130ad575050506004840160005b8381106130055750505050610d0195965091612fd5612ffa92612fa285612f90612f5f6130009865ffffffffffff196001911660181a1490565b849060ff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0083541691151516179055565b65ffffffffffff191660191a60011490565b81547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff1690151560081b61ff0016179055565b7fffffffffffffffffffffffffffffffffffffffff0000000000000000000000001690565b60601c90565b614207565b613013610eba82868e612e8b565b613042610ad17fffffffff00000000000000000000000000000000000000000000000000000000831685615336565b61304f5750600101612f25565b7fd5d4419e000000000000000000000000000000000000000000000000000000006000527fffffffff000000000000000000000000000000000000000000000000000000001660045267ffffffffffffffff19831660245260446000fd5b6130cd612e156130c76130c1848787612dca565b90612252565b90612de1565b906130e36130dc828686612dca565b8091612260565b6130f884929467010000000000000016151590565b156131715760ff60018b0161311f61311961043765ffffffffffff19871681565b82612e39565b541161314757600193613000612ffa612fd56131419565ffffffffffff191690565b01612f14565b7f78f240550000000000000000000000000000000000000000000000000000000060005260046000fd5b919290613195610ad161318c65ffffffffffff198416612e15565b60028d01615336565b6131b7579161314191613000612ffa612fd56001979665ffffffffffff191690565b7fc349aaaa0000000000000000000000000000000000000000000000000000000060005267ffffffffffffffff19871660045265ffffffffffff191660245260446000fd5b919091357fffffffff0000000000000000000000000000000000000000000000000000000081169260048110613230575050565b7fffffffff00000000000000000000000000000000000000000000000000000000929350829060040360031b1b161690565b919091610120818403126101bb576132786110ce565b9261328282610cf6565b845260208201356020850152604082013567ffffffffffffffff81116101bb57816132ae918401611184565b6040850152606082013567ffffffffffffffff81116101bb57816132d3918401611184565b60608501526080820135608085015260a082013560a085015260c082013560c085015260e082013567ffffffffffffffff81116101bb5781613316918401611184565b60e085015261010082013567ffffffffffffffff81116101bb5761333a9201611184565b610100830152565b8061334a5750565b60008080809333600019f15061335e6127ef565b50565b916000928392602083519301915af1906133796127ef565b911561179557565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1813603018212156101bb570180359067ffffffffffffffff82116101bb57602001918160071b360383136101bb57565b91908110156121665760071b0190565b801515036101bb57565b6080813603126101bb57604051906080820182811067ffffffffffffffff821117611079576040528035613422816102ac565b825260208101359063ffffffff821682036101bb576060916020840152604081013561344d816133e5565b6040840152013561345d816133e5565b606082015290565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1813603018212156101bb570180359067ffffffffffffffff82116101bb576020019160608202360383136101bb57565b9190811015612166576060020190565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1813603018212156101bb570180359067ffffffffffffffff82116101bb57602001918160051b360383136101bb57565b906000198201918211612c5057565b73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016803014908115613575575b5061166857565b905073ffffffffffffffffffffffffffffffffffffffff7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc541614153861356e565b3561119f816133e5565b9060018201809211612c5057565b91908201809211612c5057565b60409073ffffffffffffffffffffffffffffffffffffffff61119f94931681528160208201520190610890565b6020818303126101bb5780359067ffffffffffffffff82116101bb57019080601f830112156101bb5781359161363e83612089565b9261364c604051948561107e565b80845260208085019160051b830101918383116101bb5760208101915b83831061367857505050505090565b823567ffffffffffffffff81116101bb5782019060607fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe083880301126101bb57604051906136c58261105d565b60208301356136d381610cd8565b82526040830135602083015260608301359167ffffffffffffffff83116101bb5761370688602080969581960101611184565b6040820152815201920191613669565b90602082519201517fffffffff0000000000000000000000000000000000000000000000000000000081169260048110613230575050565b916060838303126101bb57823561376481610cd8565b92602081013592604082013567ffffffffffffffff81116101bb5761119f9201611184565b919091600061379c6107598360006121b1565b7f8dd7712f000000000000000000000000000000000000000000000000000000007fffffffff000000000000000000000000000000000000000000000000000000008216146139f8575b806138146000877fffffffff0000000000000000000000000000000000000000000000000000000094614ef9565b167fb61d27f60000000000000000000000000000000000000000000000000000000081036138a05750613856929350908061384e926121fa565b81019061374e565b505073ffffffffffffffffffffffffffffffffffffffff16301461387657565b7f54ff929d0000000000000000000000000000000000000000000000000000000060005260046000fd5b7f34fcd5be00000000000000000000000000000000000000000000000000000000146138cd575b50509050565b816138e3926138db926121fa565b810190613609565b60005b81518110156139ec573061393461391b6139008486612152565b515173ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff1690565b14613942575b6001016138e6565b61395960406139518385612152565b510151613716565b907fffffffff0000000000000000000000000000000000000000000000000000000082167fb61d27f60000000000000000000000000000000000000000000000000000000081149081156139c2575b50613876576139bb600086600194614ef9565b905061393a565b7f34fcd5be00000000000000000000000000000000000000000000000000000000915014386139a8565b505090508038806138c7565b505080613a069160006121fa565b907fffffffff00000000000000000000000000000000000000000000000000000000613a3561075984846121b1565b90506137e6565b9392919091613a4e61075984876121b1565b7f8dd7712f000000000000000000000000000000000000000000000000000000007fffffffff00000000000000000000000000000000000000000000000000000000821614613c1a575b80613ac583857fffffffff0000000000000000000000000000000000000000000000000000000094614ef9565b167fb61d27f6000000000000000000000000000000000000000000000000000000008103613aff575050508061384e9161385693946121fa565b91949092917f34fcd5be0000000000000000000000000000000000000000000000000000000014613b32575b5050509050565b81613b40926138db926121fa565b60005b8151811015613c0c5730613b5d61391b6139008486612152565b14613b6b575b600101613b43565b613b7a60406139518385612152565b907fffffffff0000000000000000000000000000000000000000000000000000000082167fb61d27f6000000000000000000000000000000000000000000000000000000008114908115613be2575b5061387657613bdb8487600194614ef9565b9050613b63565b7f34fcd5be0000000000000000000000000000000000000000000000000000000091501438613bc9565b505050905080388080613b2b565b509180613c2792956121fa565b909390917fffffffff00000000000000000000000000000000000000000000000000000000613c5961075985886121b1565b9050613a98565b96949273ffffffffffffffffffffffffffffffffffffffff9063ffffffff61119f9a989483613caf9895168b521660208a0152166040880152606087015260c0608087015260c08601916122b0565b9260a08185039101526122b0565b919493613cca9193615067565b96929096909196613d1460016119f88767ffffffffffffffff19166000527f9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e42602052604060002090565b976000935b8951851015613e1f5784613d36610adc6106246105fe86866127d8565b03613dfe57613d4891610a42916150b3565b91825115613dd457613d5991615067565b909690949193909180613d75610adc6106246105fe89896127d8565b1115613daa57613d9f600192613d8e612b4e848f612152565b613d99368b8e61114d565b90614112565b019392909591613d19565b7fd8ddc84c0000000000000000000000000000000000000000000000000000000060005260046000fd5b7fb91b669d0000000000000000000000000000000000000000000000000000000060005260046000fd5b919590929380613d9f8b613d8e612b4e600195613e19611b59565b93612152565b9493509695949750505060ff80613e3c6106246105fe89876127d8565b1603613f135763ffffffff604087901c169560601c948592613e5e91906150b3565b929093813b156101bb57600088613eab8296604051988997889687957f465d33e0000000000000000000000000000000000000000000000000000000008752349033903060048a01613c60565b03925af19081613efe575b50613ef9575061145d613ec76127ef565b6040519384937f92a47d6b00000000000000000000000000000000000000000000000000000000855260048501612c72565b915050565b80610e916000613f0d9361107e565b38613eb6565b7f151d90fe0000000000000000000000000000000000000000000000000000000060005260046000fd5b90613f4782612089565b613f54604051918261107e565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0613f828294612089565b0160005b818110613f9257505050565b60405190604082019180831067ffffffffffffffff841117611079576020926040526060815260008382015282828601015201613f86565b9190825490613fd882613f3d565b9360005b838110614058575060005b838110613ff45750505050565b80614004612e15600193856142b6565b6602000000000000811661401a575b5001613fe7565b6140416140328667ffffffffffffffff198416615128565b91660100000000000016151590565b15614013576140508289612152565b515238614013565b80614068612e15600193856142b6565b6601000000000000811661407e575b5001613fdc565b67ffffffffffffffff196140ab91166020614099848b612152565b51019067ffffffffffffffff19169052565b38614077565b6000198114612c505760010190565b916140da918354906000199060031b92831b921b19161790565b9055565b80549060008155816140ee575050565b6000526020600020908101905b818110614106575050565b600081556001016140fb565b63ffffffff604082901c169360609190911c929091839190823b156101bb576141929263ffffffff6000886141a48296604051988997889687957f3d6bda3200000000000000000000000000000000000000000000000000000000875216600486015233602486015234604486015260a0606486015260a4850190610890565b90600319848303016084850152610890565b03925af190816141f2575b50613ef9575061145d6141c06127ef565b6040519384937fa53ac97500000000000000000000000000000000000000000000000000000000855260048501612c72565b80610e9160006142019361107e565b386141af565b9160009281614217575b50505050565b73ffffffffffffffffffffffffffffffffffffffff1690813b1561429f5791839161427993836040518096819582947f6d61fe7000000000000000000000000000000000000000000000000000000000845260206004850181815201916122b0565b03925af18015611fac5761428f575b8080614211565b816142999161107e565b38614288565b8380fd5b8054156121665760005260206000205490565b906142c091612e21565b90549060031b1c90565b9392916143ed9063ffffffff6040931686526060602087015261430660608701825173ffffffffffffffffffffffffffffffffffffffff169052565b602081015160808701526101006143ba6143676143338685015161012060a08c01526101808b0190610890565b60608501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa08b83030160c08c0152610890565b608084015160e08a015260a0840151838a015260c08401516101208a015260e08401517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa08a8303016101408b0152610890565b9101517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa087830301610160880152610890565b930152565b9394926143ff9192615067565b96929194909691949660009261444e60016119f88a67ffffffffffffffff19166000527f9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e42602052604060002090565b986000975b8a5189101561461157888b81614472610adc6106246105fe88886127d8565b036145e857505061448691610a42916150b3565b806101008801525115613dd45761449c91615067565b93909394886144b6610adc6106246105fe889780996127d8565b1115613daa57614509906144d0611e42612b4e8c8f612152565b60208a8c60405196879283927f2a3d428c00000000000000000000000000000000000000000000000000000000845286600485016142ca565b0381600073ffffffffffffffffffffffffffffffffffffffff87165af1938415611fac576000946145c8575b5073ffffffffffffffffffffffffffffffffffffffff8416916001831161457357505050600191614565916153a2565b980197949193929093614453565b7f093431410000000000000000000000000000000000000000000000000000000060005273ffffffffffffffffffffffffffffffffffffffff90811660045263ffffffff919091166024521660445260646000fd5b6145e191945060203d811161131e5761130f818361107e565b9238614535565b611e42612b4e614509949699936144d0938d6101006146099c999b9c611b59565b910152612152565b959497509792989591505060ff8061462f6106246105fe8b896127d8565b1603613f1357600061467d73ffffffffffffffffffffffffffffffffffffffff93614662610a426146b69b6020996150b3565b610100850152606081901c9160409190911c63ffffffff1690565b604094919451998a96879586937f0ab8785f000000000000000000000000000000000000000000000000000000008552600485016142ca565b0393165af1928315611fac576000936146df575b5051156146db579061119f91615483565b5090565b6146f991935060203d60201161131e5761130f818361107e565b91386146ca565b909265ffffffffffff199290918391156147a4576602000000000000925b1561476e577fffffffffffffffffffffffffffffffffffffffff0000000000000000000000006bffffffff00000000000000006601000000000000965b60401b169160601b161791161791161790565b7fffffffffffffffffffffffffffffffffffffffff0000000000000000000000006bffffffff000000000000000060009661475b565b60009261471e565b65ffffffffffff1961335e921690615cb9565b614814907fffffffff00000000000000000000000000000000000000000000000000000000166000527f9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e41602052604060002090565b80547fffffffffffffffffffff00000000000000000000000000000000000000000000169055565b60206000604051828101907f01ffc9a70000000000000000000000000000000000000000000000000000000082527f01ffc9a70000000000000000000000000000000000000000000000000000000060248201526024815261489f60448261107e565b519084617530fa903d600051908361499a575b5082614990575b50816148f5575b816148c9575090565b61119f91507fd2db51a800000000000000000000000000000000000000000000000000000000906156c5565b905060206000604051828101907f01ffc9a70000000000000000000000000000000000000000000000000000000082527fffffffff0000000000000000000000000000000000000000000000000000000060248201526024815261495a60448261107e565b519084617530fa6000513d82614984575b508161497a575b5015906148c0565b9050151538614972565b6020111591503861496b565b15159150386148b9565b602011159250386148b2565b9093926149fe827fffffffff00000000000000000000000000000000000000000000000000000000166000527f9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e41602052604060002090565b9173ffffffffffffffffffffffffffffffffffffffff835416614e97577fffffffff0000000000000000000000000000000000000000000000000000000081167f19822f7c000000000000000000000000000000000000000000000000000000008114908115614e6d575b8115614e43575b8115614e19575b8115614def575b8115614dc5575b8115614d9b575b8115614d71575b8115614d47575b8115614d1d575b8115614cf3575b8115614cc9575b8115614c9f575b50614c5057614ac481615758565b614c0157614ad181615988565b614bb25750610d019394614b23614b6992849073ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffff0000000000000000000000000000000000000000825416179055565b82547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff1690151560a01b74ff000000000000000000000000000000000000000016178255565b907fffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffff75ff000000000000000000000000000000000000000000835492151560a81b169116179055565b7f3cecfc37000000000000000000000000000000000000000000000000000000006000527fffffffff000000000000000000000000000000000000000000000000000000001660045260246000fd5b7fd133f366000000000000000000000000000000000000000000000000000000006000527fffffffff000000000000000000000000000000000000000000000000000000001660045260246000fd5b7fe171c779000000000000000000000000000000000000000000000000000000006000527fffffffff000000000000000000000000000000000000000000000000000000001660045260246000fd5b7f808f49bc0000000000000000000000000000000000000000000000000000000091501438614ab6565b7f642f9dd40000000000000000000000000000000000000000000000000000000081149150614aaf565b7f756e30980000000000000000000000000000000000000000000000000000000081149150614aa8565b7f2de0f9340000000000000000000000000000000000000000000000000000000081149150614aa1565b7f5bf8fca20000000000000000000000000000000000000000000000000000000081149150614a9a565b7f34fcd5be0000000000000000000000000000000000000000000000000000000081149150614a93565b7fb61d27f60000000000000000000000000000000000000000000000000000000081149150614a8c565b7f4f1ef2860000000000000000000000000000000000000000000000000000000081149150614a85565b7f52d1902d0000000000000000000000000000000000000000000000000000000081149150614a7e565b7f01ffc9a70000000000000000000000000000000000000000000000000000000081149150614a77565b7f443f5daa0000000000000000000000000000000000000000000000000000000081149150614a70565b7f51fd4e1e0000000000000000000000000000000000000000000000000000000081149150614a69565b7fec9cbcb3000000000000000000000000000000000000000000000000000000006000527fffffffff000000000000000000000000000000000000000000000000000000001660045260246000fd5b65ffffffffffff1961335e921690615336565b9115614fb457614f0882615a92565b15908115614f69575b50614f195750565b7fffffffff00000000000000000000000000000000000000000000000000000000907fcf7b49f6000000000000000000000000000000000000000000000000000000006000521660045260246000fd5b60ff9150614fab9067ffffffffffffffff19166000527f9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e42602052604060002090565b54161538614f11565b7fffffffff0000000000000000000000000000000000000000000000000000000060046150186150329367ffffffffffffffff19166000527f9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e42602052604060002090565b019216809260019160005201602052604060002054151590565b1561503a5750565b7fcf7b49f60000000000000000000000000000000000000000000000000000000060005260045260246000fd5b9190600091816004116150af576004843560e01c0163ffffffff8111612c505763ffffffff168060041161429f5782811161429f578060048601956003198201955001920390565b8280fd5b919091826001116101bb57600101916000190190565b6020818303126101bb5780519067ffffffffffffffff82116101bb570181601f820112156101bb5780516150fc816110de565b9261510a604051948561107e565b818452602082840101116101bb5761119f916020808501910161086d565b6000929061519590606081901c9060401c63ffffffff16949093604051809381927fed6dfb1300000000000000000000000000000000000000000000000000000000835263ffffffff89166004840152336024840152346044840152608060648401526084830190610890565b03818373ffffffffffffffffffffffffffffffffffffffff88165af160009181615204575b506151fe57505061145d6151cc6127ef565b6040519384937f7a7515d000000000000000000000000000000000000000000000000000000000855260048501612c72565b92509050565b6152229192503d806000833e61521a818361107e565b8101906150c9565b90386151ba565b61526960049167ffffffffffffffff19166000527f9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e42602052604060002090565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000815416815501805460005b8181106152a157505050565b600090835415612166576152be6020838660019552205485615cb9565b5001615295565b91600092816152d45750505050565b73ffffffffffffffffffffffffffffffffffffffff1690813b1561429f5791839161427993836040518096819582947f8a91b0e300000000000000000000000000000000000000000000000000000000845260206004850181815201916122b0565b60008281526001820160205260409020546120825780549068010000000000000000821015611079578261538b615374846001809601855584612e21565b81939154906000199060031b92831b921b19161790565b905580549260005201602052604060002055600190565b906153bb6153b08360a01c90565b65ffffffffffff1690565b65ffffffffffff811615615476575b6153d76153b08360a01c90565b65ffffffffffff811615615469575b65ffffffffffff90811691168181111561545e575060a01b915b73ffffffffffffffffffffffffffffffffffffffff806154236153b08460d01c90565b65ffffffffffff806154386153b08860d01c90565b16911681811015615453575060d01b935b1691161791171790565b60d01b905093615449565b60a01b905091615400565b5065ffffffffffff6153e6565b5065ffffffffffff6153ca565b6154906153b08260a01c90565b65ffffffffffff811615615585575b6154ac6153b08460a01c90565b65ffffffffffff811615615578575b65ffffffffffff90811691168181111561556d575060a01b915b600173ffffffffffffffffffffffffffffffffffffffff6154f96153b08560d01c90565b65ffffffffffff8061550e6153b08760d01c90565b16911681811015615562575060d01b935b1603615545575073ffffffffffffffffffffffffffffffffffffffff60015b1691171790565b73ffffffffffffffffffffffffffffffffffffffff80911661553e565b60d01b90509361551f565b60a01b9050916154d5565b5065ffffffffffff6154bb565b5065ffffffffffff61549f565b90813b156156815773ffffffffffffffffffffffffffffffffffffffff8216807fffffffffffffffffffffffff00000000000000000000000000000000000000007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5416177f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b600080a280511561564e5761335e91615d6a565b50503461565757565b7fb398979f0000000000000000000000000000000000000000000000000000000060005260046000fd5b73ffffffffffffffffffffffffffffffffffffffff827f4c9c8ce3000000000000000000000000000000000000000000000000000000006000521660045260246000fd5b6000906020926040517fffffffff00000000000000000000000000000000000000000000000000000000858201927f01ffc9a70000000000000000000000000000000000000000000000000000000084521660248201526024815261572b60448261107e565b5191617530fa6000513d8261574c575b5081615745575090565b9050151590565b6020111591503861573b565b7fffffffff00000000000000000000000000000000000000000000000000000000167f6d61fe7000000000000000000000000000000000000000000000000000000000811490811561595e575b8115615934575b811561590a575b81156158e0575b81156158b6575b811561588c575b8115615862575b8115615838575b811561580e575b81156157e7575090565b7f3d6bda320000000000000000000000000000000000000000000000000000000091501490565b7f2a3d428c00000000000000000000000000000000000000000000000000000000811491506157dd565b7fe7db7f7e00000000000000000000000000000000000000000000000000000000811491506157d6565b7f465d33e000000000000000000000000000000000000000000000000000000000811491506157cf565b7f0ab8785f00000000000000000000000000000000000000000000000000000000811491506157c8565b7f5d413a8100000000000000000000000000000000000000000000000000000000811491506157c1565b7fed6dfb1300000000000000000000000000000000000000000000000000000000811491506157ba565b7f01b2bdc600000000000000000000000000000000000000000000000000000000811491506157b3565b7f3499a2fd00000000000000000000000000000000000000000000000000000000811491506157ac565b7f8a91b0e300000000000000000000000000000000000000000000000000000000811491506157a5565b7fffffffff00000000000000000000000000000000000000000000000000000000167f2dd81133000000000000000000000000000000000000000000000000000000008114908115615a68575b8115615a3e575b8115615a14575b81156159ed575090565b7f7c627b210000000000000000000000000000000000000000000000000000000091501490565b7f52b7512c00000000000000000000000000000000000000000000000000000000811491506159e3565b7fae574a4300000000000000000000000000000000000000000000000000000000811491506159dc565b7f062a422b00000000000000000000000000000000000000000000000000000000811491506159d5565b7fffffffff0000000000000000000000000000000000000000000000000000000081167fb61d27f6000000000000000000000000000000000000000000000000000000008114908115615c38575b8115615c0e575b8115615be4575b8115615bbb575b8115615b91575b8115615b67575b5061207c57615b5f60ff917fffffffff00000000000000000000000000000000000000000000000000000000166000527f9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e41602052604060002090565b5460a81c1690565b7f4f1ef2860000000000000000000000000000000000000000000000000000000091501438615b03565b7fb6b1ccfe0000000000000000000000000000000000000000000000000000000081149150615afc565b7e14490e0000000000000000000000000000000000000000000000000000000081149150615af5565b7f443f5daa0000000000000000000000000000000000000000000000000000000081149150615aee565b7f51fd4e1e0000000000000000000000000000000000000000000000000000000081149150615ae7565b7f34fcd5be0000000000000000000000000000000000000000000000000000000081149150615ae0565b80548015615c8a576000190190615c798282612e21565b60001982549160031b1b1916905555565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fd5b6001810191806000528260205260406000205492831515600014615d61576000198401848111612c50578354936000198501948511612c50576000958583615d1c97615d0d9503615d22575b505050615c62565b90600052602052604060002090565b55600190565b615d48615d4291615d396142c0615d589588612e21565b92839187612e21565b906140c0565b8590600052602052604060002090565b55388080615d05565b50505050600090565b60008061119f93602081519101845af4615d826127ef565b9190615dc25750805115615d9857805190602001fd5b7f1425ea420000000000000000000000000000000000000000000000000000000060005260046000fd5b81511580615e17575b615dd3575090565b73ffffffffffffffffffffffffffffffffffffffff907f9996b315000000000000000000000000000000000000000000000000000000006000521660045260246000fd5b50803b15615dcb56fea164736f6c634300081a000a9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e400000000000000000000000000000000071727de22e5e9d8baf0edac6f37da032", + "nonce": "0x7", + "chainId": "0x66eee" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xff7f87461cff12e51429198b1ba0790cf79df5386c3b0be587446dfb766b4b84", + "transactionType": "CREATE2", + "contractName": "SingleSignerValidation", + "contractAddress": "0x9da8c098a483e257dd96022831df308cb24fcbe6", + "function": null, + "arguments": null, + "transaction": { + "from": "0x7f89ed1f3f0d52d303904101305471bca3cde710", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xe64afa", + "value": "0x0", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601557610fc5908161001b8239f35b600080fdfe6080604052600436101561001257600080fd5b60003560e01c806301b2bdc6146106a457806301ffc9a7146106035780630ab8785f146105ac5780630df0a05e146100955780633499a2fd14610401578063465d33e0146103115780634aeb3b03146102e75780636d61fe70146102715780638a91b0e31461019a578063e7db7f7e146101125763f1a7073e1461009557600080fd5b3461010d57604060031936011261010d576100ae61090f565b63ffffffff6100bb610946565b9116600052600060205273ffffffffffffffffffffffffffffffffffffffff60406000209116600052602052602073ffffffffffffffffffffffffffffffffffffffff60406000205416604051908152f35b600080fd5b3461010d5760a060031936011261010d5761012b610969565b610133610922565b9061013c61098c565b506084359067ffffffffffffffff821161010d576020926101646101709336906004016109af565b92909160643591610c2f565b7fffffffff0000000000000000000000000000000000000000000000000000000060405191168152f35b3461010d57602060031936011261010d5760043567ffffffffffffffff811161010d576101cd60209136906004016109af565b908092918101031261010d576101e763ffffffff91610935565b166000818152602081815260408083203380855290835281842080547fffffffffffffffffffffffff00000000000000000000000000000000000000008116909155825173ffffffffffffffffffffffffffffffffffffffff9091168152928301939093527f3819bcb8ad82b4cf05cf5cce33bd55aad1a15eb389cdd1cc832a99e7438c4a0791a3005b3461010d57602060031936011261010d5760043567ffffffffffffffff811161010d576102a460409136906004016109af565b908092918101031261010d5760206102bb82610935565b91013573ffffffffffffffffffffffffffffffffffffffff811680910361010d576102e591610e7e565b005b3461010d57604060031936011261010d576102e561030361090f565b61030b610946565b90610e7e565b3461010d5760c060031936011261010d5761032a610969565b610332610922565b9061033b61098c565b9160843567ffffffffffffffff811161010d5761035c9036906004016109af565b505060a4359067ffffffffffffffff821161010d5761038463ffffffff9236906004016109af565b505016600052600060205273ffffffffffffffffffffffffffffffffffffffff6040600020911660005260205273ffffffffffffffffffffffffffffffffffffffff80604060002054169116036103d757005b7fea8e4eb50000000000000000000000000000000000000000000000000000000060005260046000fd5b3461010d57600060031936011261010d5761041a610bff565b50610423610bff565b6040518091602082526080820190805191606060208501528251809152602060a0850193019060005b81811061055457505050602081015191601f198482030160408501526020808451928381520193019060005b8181106104ec575050506040015190601f198382030160608401526020808351928381520192019060005b8181106104b1575050500390f35b82517fffffffff00000000000000000000000000000000000000000000000000000000168452859450602093840193909201916001016104a3565b919450919260206080600192606088517fffffffff00000000000000000000000000000000000000000000000000000000815116835263ffffffff85820151168584015260408101511515604084015201511515606082015201950191019185949392610478565b919450919260206060600192604088517fffffffff000000000000000000000000000000000000000000000000000000008151168352848101511515858401520151151560408201520195019101918594939261044c565b3461010d57606060031936011261010d576105c561090f565b6024359067ffffffffffffffff821161010d57610120600319833603011261010d576020916105fb916044359160040190610abd565b604051908152f35b3461010d57602060031936011261010d576004357fffffffff00000000000000000000000000000000000000000000000000000000811680910361010d57807fd2db51a8000000000000000000000000000000000000000000000000000000006020921490811561067a575b506040519015158152f35b7f01ffc9a7000000000000000000000000000000000000000000000000000000009150148261066f565b3461010d57600060031936011261010d576106bd610a2f565b506107b16106c9610a2f565b604080516106d782826109dd565b601781527f53696e676c655369676e65722056616c69646174696f6e0000000000000000006020820152825280519161071082846109dd565b600583527f312e302e300000000000000000000000000000000000000000000000000000006020840152602081019283526107c4825161075084826109dd565b601081527f4552432d3639303020417574686f727300000000000000000000000000000000602082015283830190815283519586956020875261079f855160a060208a015260c08901906108ce565b9051601f1988830301878901526108ce565b9051601f198683030160608701526108ce565b91606082015192601f19858203016080860152835190818152602081016020808460051b8401019601936000925b8484106108675788808960808a015190601f198382030160a0840152815180825260208201916020808360051b8301019401926000915b8383106108365786860387f35b91939550919360208061085583601f19866001960301875289516108ce565b97019301930190928695949293610829565b919395966001919395985060206108bb82601f198684950301885286838d517fffffffff000000000000000000000000000000000000000000000000000000008151168452015191818582015201906108ce565b99019401940191889796959394916107f2565b919082519283825260005b8481106108fa575050601f19601f8460006020809697860101520116010190565b806020809284010151828286010152016108d9565b6004359063ffffffff8216820361010d57565b6024359063ffffffff8216820361010d57565b359063ffffffff8216820361010d57565b6024359073ffffffffffffffffffffffffffffffffffffffff8216820361010d57565b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361010d57565b6044359073ffffffffffffffffffffffffffffffffffffffff8216820361010d57565b9181601f8401121561010d5782359167ffffffffffffffff831161010d576020838186019501011161010d57565b90601f601f19910116810190811067ffffffffffffffff821117610a0057604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040519060a0820182811067ffffffffffffffff821117610a0057604052606060808382815282602082015282604082015282808201520152565b67ffffffffffffffff8111610a0057601f01601f191660200190565b929192610a9282610a6a565b91610aa060405193846109dd565b82948184528183011161010d578281602093846000960137010152565b917f19457468657265756d205369676e6564204d6573736167653a0a333200000000600052601c52603c6000206101008201357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18336030181121561010d5782019081359167ffffffffffffffff831161010d5760200190823603821361010d57610b62610b689273ffffffffffffffffffffffffffffffffffffffff943691610a86565b90610e42565b505016908115928315610b87575b505050610b8257600090565b600190565b63ffffffff919293501660005260006020526040600020903573ffffffffffffffffffffffffffffffffffffffff8116810361010d5773ffffffffffffffffffffffffffffffffffffffff1660005260205273ffffffffffffffffffffffffffffffffffffffff604060002054161415388080610b76565b604051906060820182811067ffffffffffffffff821117610a005760405260606040838281528260208201520152565b63ffffffff610c8a9395949216600052600060205273ffffffffffffffffffffffffffffffffffffffff6040600020911660005260205273ffffffffffffffffffffffffffffffffffffffff60406000205416923691610a86565b91610c958382610e42565b506004819592951015610e1357159384610df3575b508315610d01575b505050610cdd577fffffffff0000000000000000000000000000000000000000000000000000000090565b7f1626ba7e0000000000000000000000000000000000000000000000000000000090565b6000935090610d53610d6185949360405192839160208301957f1626ba7e00000000000000000000000000000000000000000000000000000000875260248401526040604484015260648301906108ce565b03601f1981018352826109dd565b51915afa3d15610dec573d610d7581610a6a565b90610d8360405192836109dd565b81523d6000602083013e5b81610dde575b81610da3575b50388080610cb2565b905060208180518101031261010d57602001517f1626ba7e000000000000000000000000000000000000000000000000000000001438610d9a565b905060208151101590610d94565b6060610d8e565b73ffffffffffffffffffffffffffffffffffffffff168314935038610caa565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b8151919060418303610e7357610e6c92506020820151906060604084015193015160001a90610f16565b9192909190565b505060009160029190565b63ffffffff166000818152602081815260408083203380855290835292819020805473ffffffffffffffffffffffffffffffffffffffff9687167fffffffffffffffffffffffff00000000000000000000000000000000000000008216811790925582519616865291850191909152919290917f3819bcb8ad82b4cf05cf5cce33bd55aad1a15eb389cdd1cc832a99e7438c4a0791a3565b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411610fac579160209360809260ff60009560405194855216868401526040830152606082015282805260015afa15610fa05760005173ffffffffffffffffffffffffffffffffffffffff811615610f945790600090600090565b50600090600190600090565b6040513d6000823e3d90fd5b5050506000916003919056fea164736f6c634300081a000a", + "nonce": "0x8", + "chainId": "0x66eee" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x38a57d3b2c174b63b3aadd6f59536a8bca1f9837acc542a8d4aa5b16837a9eaf", + "transactionType": "CREATE2", + "contractName": "AccountFactory", + "contractAddress": "0x1c7ef41aa9896b74223a3956c7dde28f206e8b24", + "function": null, + "arguments": [ + "0x0000000071727De22E5E9d8BAf0edAc6f37da032", + "0x0809BF385117a43A322A4E31d459c0EcaA3B1A08", + "0x9DA8c098A483E257dd96022831DF308cB24fCBE6", + "0x7f89Ed1F3F0d52d303904101305471bca3cde710" + ], + "transaction": { + "from": "0x7f89ed1f3f0d52d303904101305471bca3cde710", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xf4c552", + "value": "0x0", + "input": "0x000000000000000000000000000000000000000000000000000000000000000061010080604052346101b85760808161131c803803809161002082856101bd565b8339810103126101b85780516001600160a01b03811681036101b8576020820151906001600160a01b0382168083036101b85761005f604085016101f6565b936001600160a01b0390610075906060016101f6565b169182156101a257600080546001600160a01b03198116851782556040519461012b9461013994909390926001600160a01b0316907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09080a360c0526102a86100e160208201866101bd565b808552611074602086013960405190602082015260408082015260006060820152606081526101116080826101bd565b60405192839161012560208401809761020a565b9061020a565b03601f1981018352826101bd565b51902060a05260805260e052604051610e3e90816102368239608051818181610406015261094c015260a05181818161033f01526108dc015260c0518181816101f6015281816102b7015281816106df0152610830015260e05181818161052101526107e10152f35b631e4fbdf760e01b600052600060045260246000fd5b600080fd5b601f909101601f19168101906001600160401b038211908210176101e057604052565b634e487b7160e01b600052604160045260246000fd5b51906001600160a01b03821682036101b857565b9081519160005b838110610222575050016000815290565b806020809284010151818501520161021156fe6080604052600436101561001257600080fd5b6000803560e01c806303c7e13114610970578063121ee5411461091f578063421a21e0146108bb5780635a627dbc1461080557806366ea05d0146107b4578063715018a6146107365780638da5cb5b1461070357806394430fa5146106b2578063afa63a101461031c578063bb9fe6bf14610286578063c23a5cea146101a3578063e35e5d84146101855763f2fde38b146100ac57600080fd5b346101825760206003193601126101825760043573ffffffffffffffffffffffffffffffffffffffff811680910361017e576100e6610b1a565b80156101525773ffffffffffffffffffffffffffffffffffffffff8254827fffffffffffffffffffffffff00000000000000000000000000000000000000008216178455167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08380a380f35b6024827f1e4fbdf700000000000000000000000000000000000000000000000000000000815280600452fd5b5080fd5b80fd5b5034610182578060031936011261018257602060405162093a808152f35b5034610182576020600319360112610182578060043573ffffffffffffffffffffffffffffffffffffffff8116809103610283576101df610b1a565b73ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001690813b1561027f5782916024839260405194859384927fc23a5cea00000000000000000000000000000000000000000000000000000000845260048401525af18015610274576102635750f35b8161026d916109d5565b6101825780f35b6040513d84823e3d90fd5b5050fd5b50fd5b503461018257806003193601126101825761029f610b1a565b8073ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016803b15610283578180916004604051809481937fbb9fe6bf0000000000000000000000000000000000000000000000000000000083525af18015610274576102635750f35b50346101825761033861032e3661098c565b8293918193610a45565b91610364307f000000000000000000000000000000000000000000000000000000000000000085610b69565b92833b1561038e575b60208473ffffffffffffffffffffffffffffffffffffffff60405191168152f35b73ffffffffffffffffffffffffffffffffffffffff6040519263ffffffff85166020850152166040830152604082526103c86060836109d5565b6040516102a88082019082821067ffffffffffffffff831117610685576060918391610b8a833973ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001681526040602082015288604082015203019086f5156105e55773ffffffffffffffffffffffffffffffffffffffff831690602060405161046682826109d5565b86815260003681376040519261047c83856109d5565b878452843b1561068157949290918794926040519687957f3fa3f96900000000000000000000000000000000000000000000000000000000875266010000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000006701000000000000006bffffffff000000000000000060848b019460401b167fffffffffffffffffffffffffffffffffffffffff0000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000060601b161717161760048801526080602488015282518091528360a48801930190885b818110610645575050509061058391600319878303016044880152610abb565b906003198583030160648601528251908183528083019281808460051b83010195019388915b8483106105f0575050505050508383809203925af180156105e5576105d0575b808061036d565b6105db8380926109d5565b61017e57816105c9565b6040513d85823e3d90fd5b928096819295989a50600193969950807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08661063193030187528a51610abb565b9801930193018a97959293899795926105a9565b82517fffffffff000000000000000000000000000000000000000000000000000000001685528c99508a98509385019391850191600101610563565b8780fd5b6024887f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b5034610182578060031936011261018257602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b503461018257806003193601126101825773ffffffffffffffffffffffffffffffffffffffff6020915416604051908152f35b503461018257806003193601126101825761074f610b1a565b8073ffffffffffffffffffffffffffffffffffffffff81547fffffffffffffffffffffffff000000000000000000000000000000000000000081168355167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a380f35b5034610182578060031936011261018257602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b508060031936011261018257610819610b1a565b73ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001681813b156101825780602492604051938480927f0396cb6000000000000000000000000000000000000000000000000000000000825262093a80600483015234905af180156108ae576108a05780f35b6108a9916109d5565b388180f35b50604051903d90823e3d90fd5b50346101825760206109016108d86108d23661098c565b91610a45565b30907f000000000000000000000000000000000000000000000000000000000000000090610b69565b73ffffffffffffffffffffffffffffffffffffffff60405191168152f35b5034610182578060031936011261018257602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b50346101825760206109846108d23661098c565b604051908152f35b60031960609101126109d05760043573ffffffffffffffffffffffffffffffffffffffff811681036109d057906024359060443563ffffffff811681036109d05790565b600080fd5b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff821117610a1657604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b917fffffffff0000000000000000000000000000000000000000000000000000000090604051927fffffffffffffffffffffffffffffffffffffffff000000000000000000000000602085019560601b168552603484015260e01b16605482015260388152610ab56058826109d5565b51902090565b919082519283825260005b848110610b055750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8460006020809697860101520116010190565b80602080928401015182828601015201610ac6565b73ffffffffffffffffffffffffffffffffffffffff600054163303610b3b57565b7f118cdaa7000000000000000000000000000000000000000000000000000000006000523360045260246000fd5b90605592600b92604051926040840152602083015281520160ff8153209056fe60806040526102a88038038061001481610188565b928339810190604081830312610183578051906001600160a01b03821690818303610183576020810151906001600160401b038211610183570183601f820112156101835780519061006d610068836101c3565b610188565b94828652602083830101116101835760005b82811061016e575050602060009185010152813b1561015a577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b031916821790557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b600080a28151156101415760008083602061012995519101845af43d15610139573d91610119610068846101c3565b9283523d6000602085013e6101de565b505b604051606890816102408239f35b6060916101de565b5050341561012b5763b398979f60e01b60005260046000fd5b634c9c8ce360e01b60005260045260246000fd5b8060208092840101518282890101520161007f565b600080fd5b6040519190601f01601f191682016001600160401b038111838210176101ad57604052565b634e487b7160e01b600052604160045260246000fd5b6001600160401b0381116101ad57601f01601f191660200190565b9061020457508051156101f357805190602001fd5b630a12f52160e11b60005260046000fd5b81511580610236575b610215575090565b639996b31560e01b60009081526001600160a01b0391909116600452602490fd5b50803b1561020d56fe608060405260008073ffffffffffffffffffffffffffffffffffffffff7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5416368280378136915af43d6000803e156056573d6000f35b3d6000fdfea164736f6c634300081a000aa164736f6c634300081a000a60806040526102a88038038061001481610188565b928339810190604081830312610183578051906001600160a01b03821690818303610183576020810151906001600160401b038211610183570183601f820112156101835780519061006d610068836101c3565b610188565b94828652602083830101116101835760005b82811061016e575050602060009185010152813b1561015a577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b031916821790557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b600080a28151156101415760008083602061012995519101845af43d15610139573d91610119610068846101c3565b9283523d6000602085013e6101de565b505b604051606890816102408239f35b6060916101de565b5050341561012b5763b398979f60e01b60005260046000fd5b634c9c8ce360e01b60005260045260246000fd5b8060208092840101518282890101520161007f565b600080fd5b6040519190601f01601f191682016001600160401b038111838210176101ad57604052565b634e487b7160e01b600052604160045260246000fd5b6001600160401b0381116101ad57601f01601f191660200190565b9061020457508051156101f357805190602001fd5b630a12f52160e11b60005260046000fd5b81511580610236575b610215575090565b639996b31560e01b60009081526001600160a01b0391909116600452602490fd5b50803b1561020d56fe608060405260008073ffffffffffffffffffffffffffffffffffffffff7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5416368280378136915af43d6000803e156056573d6000f35b3d6000fdfea164736f6c634300081a000a0000000000000000000000000000000071727de22e5e9d8baf0edac6f37da0320000000000000000000000000809bf385117a43a322a4e31d459c0ecaa3b1a080000000000000000000000009da8c098a483e257dd96022831df308cb24fcbe60000000000000000000000007f89ed1f3f0d52d303904101305471bca3cde710", + "nonce": "0x9", + "chainId": "0x66eee" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xeac9bb033b7ea49e5da3fa029a92f4299e5c70ded305d08b91fc801712073120", + "transactionType": "CALL", + "contractName": null, + "contractAddress": "0x0000000071727de22e5e9d8baf0edac6f37da032", + "function": "addStake(uint32)", + "arguments": [ + "86400" + ], + "transaction": { + "from": "0x7f89ed1f3f0d52d303904101305471bca3cde710", + "to": "0x0000000071727de22e5e9d8baf0edac6f37da032", + "gas": "0x126022", + "value": "0x16345785d8a0000", + "input": "0x0396cb600000000000000000000000000000000000000000000000000000000000015180", + "nonce": "0xa", + "chainId": "0x66eee" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "status": "0x1", + "cumulativeGasUsed": "0x33d13fe", + "logs": [ + { + "address": "0x0809bf385117a43a322a4e31d459c0ecaa3b1a08", + "topics": [ + "0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000ff", + "blockHash": "0x2ce888a823b87bb6e33693e4018200e187c8c6fd0b33c758b3d279bc5fab37d5", + "blockNumber": "0x3fad9b9", + "transactionHash": "0xf77f7595a83b6517b0bca017c6fe29990665132ba5bc0fc2e5f7e760e15f3a9e", + "transactionIndex": "0x3", + "logIndex": "0xd", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0xf77f7595a83b6517b0bca017c6fe29990665132ba5bc0fc2e5f7e760e15f3a9e", + "transactionIndex": "0x3", + "blockHash": "0x2ce888a823b87bb6e33693e4018200e187c8c6fd0b33c758b3d279bc5fab37d5", + "blockNumber": "0x3fad9b9", + "gasUsed": "0x2f430a1", + "effectiveGasPrice": "0x5f5e100", + "from": "0x7f89ed1f3f0d52d303904101305471bca3cde710", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null, + "gasUsedForL1": "0x2a432c0", + "l1BlockNumber": "0x61599b" + }, + { + "status": "0x1", + "cumulativeGasUsed": "0x9d8792", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0xff7f87461cff12e51429198b1ba0790cf79df5386c3b0be587446dfb766b4b84", + "transactionIndex": "0x1", + "blockHash": "0x9ca68eaf1308f1dcdb8b066cf3b499a04ee1a3b87012f72c3648df2b6555d1df", + "blockNumber": "0x3fad9ba", + "gasUsed": "0x9d8792", + "effectiveGasPrice": "0x5f5e100", + "from": "0x7f89ed1f3f0d52d303904101305471bca3cde710", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null, + "gasUsedForL1": "0x8f7914", + "l1BlockNumber": "0x61599b" + }, + { + "status": "0x1", + "cumulativeGasUsed": "0xa7cd6c", + "logs": [ + { + "address": "0x1c7ef41aa9896b74223a3956c7dde28f206e8b24", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000007f89ed1f3f0d52d303904101305471bca3cde710" + ], + "data": "0x", + "blockHash": "0x32ae4caa6c30977dcf97fbb69e2fb51e533cf257f9ac418b7d3d6d94b963db70", + "blockNumber": "0x3fad9bc", + "transactionHash": "0x38a57d3b2c174b63b3aadd6f59536a8bca1f9837acc542a8d4aa5b16837a9eaf", + "transactionIndex": "0x1", + "logIndex": "0x0", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000001000000000000000000000000000000000100020000000000000000000800000000000000000000000000000000400000000000000000000000020400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000001000000000000000", + "type": "0x2", + "transactionHash": "0x38a57d3b2c174b63b3aadd6f59536a8bca1f9837acc542a8d4aa5b16837a9eaf", + "transactionIndex": "0x1", + "blockHash": "0x32ae4caa6c30977dcf97fbb69e2fb51e533cf257f9ac418b7d3d6d94b963db70", + "blockNumber": "0x3fad9bc", + "gasUsed": "0xa7cd6c", + "effectiveGasPrice": "0x5f5e100", + "from": "0x7f89ed1f3f0d52d303904101305471bca3cde710", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null, + "gasUsedForL1": "0x9a5399", + "l1BlockNumber": "0x61599b" + }, + { + "status": "0x1", + "cumulativeGasUsed": "0xad22f", + "logs": [ + { + "address": "0x0000000071727de22e5e9d8baf0edac6f37da032", + "topics": [ + "0xa5ae833d0bb1dcd632d98a8b70973e8516812898e19bf27b70071ebc8dc52c01", + "0x0000000000000000000000007f89ed1f3f0d52d303904101305471bca3cde710" + ], + "data": "0x000000000000000000000000000000000000000000000000016345785d8a00000000000000000000000000000000000000000000000000000000000000015180", + "blockHash": "0x797cc9992c915b72e8934162a75584a41c09ae99d5a969f95247231c9b62265d", + "blockNumber": "0x3fad9bd", + "transactionHash": "0xeac9bb033b7ea49e5da3fa029a92f4299e5c70ded305d08b91fc801712073120", + "transactionIndex": "0x1", + "logIndex": "0x0", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000100000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000400000000000000000000040000000000000000000000000000100000000000000000000000000000000000200000000001000000000000000", + "type": "0x2", + "transactionHash": "0xeac9bb033b7ea49e5da3fa029a92f4299e5c70ded305d08b91fc801712073120", + "transactionIndex": "0x1", + "blockHash": "0x797cc9992c915b72e8934162a75584a41c09ae99d5a969f95247231c9b62265d", + "blockNumber": "0x3fad9bd", + "gasUsed": "0xad22f", + "effectiveGasPrice": "0x5f5e100", + "from": "0x7f89ed1f3f0d52d303904101305471bca3cde710", + "to": "0x0000000071727de22e5e9d8baf0edac6f37da032", + "contractAddress": null, + "gasUsedForL1": "0xa153a", + "l1BlockNumber": "0x61599b" + } + ], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1722008916, + "chain": 421614, + "commit": "2bc08b0" +} \ No newline at end of file diff --git a/deployments/arb-sepolia.md b/deployments/arb-sepolia.md new file mode 100644 index 00000000..95d01b26 --- /dev/null +++ b/deployments/arb-sepolia.md @@ -0,0 +1,21 @@ +# Arbitrum Sepolia + +Chain ID: 421614 + +## AccountFactory + +| Version | Address | Explorer | Salt | +| -------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------ | ---- | +| v0.8.0-alpha.0 | `0x1c7EF41AA9896b74223a3956c7dDE28F206E8b24` | [explorer](https://sepolia.arbiscan.io/address/0x1c7EF41AA9896b74223a3956c7dDE28F206E8b24) | `0` | + +## UpgradeableModularAccount Implementation + +| Version | Address | Explorer | Salt | +| -------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------ | ---- | +| v0.8.0-alpha.0 | `0x0809BF385117a43A322A4E31d459c0EcaA3B1A08` | [explorer](https://sepolia.arbiscan.io/address/0x0809BF385117a43A322A4E31d459c0EcaA3B1A08) | `0` | + +## SingleSignerValidation + +| Version | Address | Explorer | Salt | +| -------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------ | ---- | +| v0.8.0-alpha.0 | `0x9DA8c098A483E257dd96022831DF308cB24fCBE6` | [explorer](https://sepolia.arbiscan.io/address/0x9DA8c098A483E257dd96022831DF308cB24fCBE6) | `0` | From 99e8dedc070beddd710414da34cc5653a1e02e64 Mon Sep 17 00:00:00 2001 From: zer0dot Date: Fri, 26 Jul 2024 18:04:57 -0400 Subject: [PATCH 107/111] chore: formatting --- src/account/UpgradeableModularAccount.sol | 20 ++++++++++---------- src/helpers/KnownSelectors.sol | 6 +++--- src/interfaces/IModuleManager.sol | 14 ++++---------- 3 files changed, 17 insertions(+), 23 deletions(-) diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 02f70f7c..25b8a045 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -235,21 +235,21 @@ contract UpgradeableModularAccount is /// @inheritdoc IModuleManager /// @notice May be validated by a global validation. - function installExecution( - address module, - ExecutionManifest calldata manifest, - bytes calldata moduleInstallData - ) external override wrapNativeFunction { + function installExecution(address module, ExecutionManifest calldata manifest, bytes calldata moduleInstallData) + external + override + wrapNativeFunction + { _installExecution(module, manifest, moduleInstallData); } /// @inheritdoc IModuleManager /// @notice May be validated by a global validation. - function uninstallExecution( - address module, - ExecutionManifest calldata manifest, - bytes calldata moduleUninstallData - ) external override wrapNativeFunction { + function uninstallExecution(address module, ExecutionManifest calldata manifest, bytes calldata moduleUninstallData) + external + override + wrapNativeFunction + { _uninstallExecution(module, manifest, moduleUninstallData); } diff --git a/src/helpers/KnownSelectors.sol b/src/helpers/KnownSelectors.sol index 3c2c258d..1af67369 100644 --- a/src/helpers/KnownSelectors.sol +++ b/src/helpers/KnownSelectors.sol @@ -50,9 +50,9 @@ library KnownSelectors { function isIModuleFunction(bytes4 selector) internal pure returns (bool) { return selector == IModule.onInstall.selector || selector == IModule.onUninstall.selector || selector == IExecution.executionManifest.selector || selector == IModule.moduleMetadata.selector - || selector == IExecutionHook.preExecutionHook.selector - || selector == IExecutionHook.postExecutionHook.selector || selector == IValidation.validateUserOp.selector - || selector == IValidation.validateRuntime.selector || selector == IValidation.validateSignature.selector + || selector == IExecutionHook.preExecutionHook.selector || selector == IExecutionHook.postExecutionHook.selector + || selector == IValidation.validateUserOp.selector || selector == IValidation.validateRuntime.selector + || selector == IValidation.validateSignature.selector || selector == IValidationHook.preUserOpValidationHook.selector || selector == IValidationHook.preRuntimeValidationHook.selector; } diff --git a/src/interfaces/IModuleManager.sol b/src/interfaces/IModuleManager.sol index 74e3a338..7a031c7c 100644 --- a/src/interfaces/IModuleManager.sol +++ b/src/interfaces/IModuleManager.sol @@ -19,11 +19,8 @@ interface IModuleManager { /// @param manifest the manifest describing functions to install /// @param moduleInstallData Optional data to be decoded and used by the module to setup initial module data /// for the modular account. - function installExecution( - address module, - ExecutionManifest calldata manifest, - bytes calldata moduleInstallData - ) external; + function installExecution(address module, ExecutionManifest calldata manifest, bytes calldata moduleInstallData) + external; /// @notice Temporary install function - pending a different user-supplied install config & manifest validation /// path. @@ -63,9 +60,6 @@ interface IModuleManager { /// @param manifest the manifest describing functions to uninstall. /// @param moduleUninstallData Optional data to be decoded and used by the module to clear module data for the /// modular account. - function uninstallExecution( - address module, - ExecutionManifest calldata manifest, - bytes calldata moduleUninstallData - ) external; + function uninstallExecution(address module, ExecutionManifest calldata manifest, bytes calldata moduleUninstallData) + external; } From 71cf394619752ce45d8ce2cc9ed5da4d029d4163 Mon Sep 17 00:00:00 2001 From: zer0dot Date: Fri, 26 Jul 2024 18:23:01 -0400 Subject: [PATCH 108/111] chore: update foundry toml to match develop --- foundry.toml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/foundry.toml b/foundry.toml index d2a56c3a..a1096640 100644 --- a/foundry.toml +++ b/foundry.toml @@ -47,7 +47,10 @@ runs = 5000 depth = 32 [fmt] +line_length = 115 +wrap_comments = true +sort_imports = true +number_underscore = "thousands" +int_types = "long" - -# See more config options https://github.com/foundry-rs/foundry/tree/master/configmainnet = { key = "${ETHERSCAN_API_KEY}" } -goerli = { key = "${ETHERSCAN_API_KEY}" } +# See more config options https://github.com/foundry-rs/foundry/tree/master/config From 0e5479ba2fa9f71af27828f6f7888e1f2874605e Mon Sep 17 00:00:00 2001 From: zer0dot Date: Fri, 26 Jul 2024 18:23:29 -0400 Subject: [PATCH 109/111] chore: formatting with new foundry toml --- src/account/AccountFactory.sol | 12 +++-- src/account/AccountLoupe.sol | 10 +++- src/account/ModuleManagerInternals.sol | 9 ++-- src/account/UpgradeableModularAccount.sol | 38 +++++++------- src/helpers/HookConfigLib.sol | 9 +++- src/helpers/KnownSelectors.sol | 9 ++-- src/interfaces/IModuleManager.sol | 14 +++-- src/interfaces/IValidation.sol | 11 ++-- src/modules/ERC20TokenLimitModule.sol | 3 +- .../permissionhooks/AllowlistModule.sol | 3 +- .../validation/SingleSignerValidation.sol | 13 +++-- test/account/AccountExecHooks.t.sol | 6 ++- test/account/AccountLoupe.t.sol | 4 +- test/account/AccountReturnData.t.sol | 3 +- test/account/GlobalValidationTest.t.sol | 3 +- test/account/PerHookData.t.sol | 51 ++++++++++++------- test/account/SelfCallAuthorization.t.sol | 21 +++++--- test/account/UpgradeableModularAccount.t.sol | 8 ++- test/comparison/CompareSimpleAccount.t.sol | 4 +- test/libraries/HookConfigLib.t.sol | 4 +- test/libraries/SparseCalldataSegmentLib.t.sol | 9 +++- test/libraries/ValidationConfigLib.t.sol | 3 +- test/mocks/SingleSignerFactoryFixture.sol | 9 ++-- test/mocks/modules/ReturnDataModuleMocks.sol | 5 +- test/mocks/modules/ValidationModuleMocks.sol | 4 +- test/module/AllowlistModule.t.sol | 16 ++++-- test/module/ERC20TokenLimitModule.t.sol | 37 +++++++++----- test/module/NativeTokenLimitModule.t.sol | 8 ++- test/module/SingleSignerValidation.t.sol | 4 +- test/module/TokenReceiverModule.t.sol | 3 +- test/script/Deploy.s.t.sol | 4 +- test/utils/AccountTestBase.sol | 14 +++-- test/utils/CustomValidationTestBase.sol | 5 +- test/utils/OptimizedTest.sol | 12 +++-- 34 files changed, 253 insertions(+), 115 deletions(-) diff --git a/src/account/AccountFactory.sol b/src/account/AccountFactory.sol index ae7db475..2e70e59b 100644 --- a/src/account/AccountFactory.sol +++ b/src/account/AccountFactory.sol @@ -7,9 +7,9 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; +import {ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {UpgradeableModularAccount} from "../account/UpgradeableModularAccount.sol"; import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; -import {ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {LibClone} from "solady/utils/LibClone.sol"; contract AccountFactory is Ownable { @@ -39,7 +39,10 @@ contract AccountFactory is Ownable { * This method returns an existing account address so that entryPoint.getSenderAddress() would work even after * account creation */ - function createAccount(address owner, uint256 salt, uint32 entityId) external returns (UpgradeableModularAccount) { + function createAccount(address owner, uint256 salt, uint32 entityId) + external + returns (UpgradeableModularAccount) + { address addr = getAddress(owner, salt, entityId); // short circuit if exists @@ -69,7 +72,10 @@ contract AccountFactory is Ownable { */ function getAddress(address owner, uint256 salt, uint32 entityId) public view returns (address) { return LibClone.predictDeterministicAddressERC1967( - address(ACCOUNT_IMPL), _getImmutableArgs(owner, entityId), getSalt(owner, salt, entityId), address(this) + address(ACCOUNT_IMPL), + _getImmutableArgs(owner, entityId), + getSalt(owner, salt, entityId), + address(this) ); } diff --git a/src/account/AccountLoupe.sol b/src/account/AccountLoupe.sol index 24daedd7..110e7e72 100644 --- a/src/account/AccountLoupe.sol +++ b/src/account/AccountLoupe.sol @@ -45,7 +45,12 @@ abstract contract AccountLoupe is IAccountLoupe { } /// @inheritdoc IAccountLoupe - function getExecutionHooks(bytes4 selector) external view override returns (ExecutionHook[] memory execHooks) { + function getExecutionHooks(bytes4 selector) + external + view + override + returns (ExecutionHook[] memory execHooks) + { EnumerableSet.Bytes32Set storage hooks = getAccountStorage().selectorData[selector].executionHooks; uint256 executionHooksLength = hooks.length(); @@ -69,7 +74,8 @@ abstract contract AccountLoupe is IAccountLoupe { override returns (ExecutionHook[] memory permissionHooks) { - EnumerableSet.Bytes32Set storage hooks = getAccountStorage().validationData[validationFunction].permissionHooks; + EnumerableSet.Bytes32Set storage hooks = + getAccountStorage().validationData[validationFunction].permissionHooks; uint256 executionHooksLength = hooks.length(); permissionHooks = new ExecutionHook[](executionHooksLength); for (uint256 i = 0; i < executionHooksLength; ++i) { diff --git a/src/account/ModuleManagerInternals.sol b/src/account/ModuleManagerInternals.sol index 4d95d0c9..c064d1a9 100644 --- a/src/account/ModuleManagerInternals.sol +++ b/src/account/ModuleManagerInternals.sol @@ -86,7 +86,8 @@ abstract contract ModuleManagerInternals is IModuleManager { } function _addValidationFunction(ValidationConfig validationConfig, bytes4[] memory selectors) internal { - ValidationData storage _validationData = getAccountStorage().validationData[validationConfig.moduleEntity()]; + ValidationData storage _validationData = + getAccountStorage().validationData[validationConfig.moduleEntity()]; if (validationConfig.isGlobal()) { _validationData.isGlobal = true; @@ -236,7 +237,8 @@ abstract contract ModuleManagerInternals is IModuleManager { bytes calldata installData, bytes[] calldata hooks ) internal { - ValidationData storage _validationData = getAccountStorage().validationData[validationConfig.moduleEntity()]; + ValidationData storage _validationData = + getAccountStorage().validationData[validationConfig.moduleEntity()]; for (uint256 i = 0; i < hooks.length; ++i) { HookConfig hookConfig = HookConfig.wrap(bytes26(hooks[i][:26])); @@ -300,7 +302,8 @@ abstract contract ModuleManagerInternals is IModuleManager { for (uint256 i = 0; i < _validationData.permissionHooks.length(); ++i) { bytes calldata hookData = hookUninstallDatas[hookIndex]; - (address hookModule,) = ModuleEntityLib.unpack(toModuleEntity(_validationData.permissionHooks.at(i))); + (address hookModule,) = + ModuleEntityLib.unpack(toModuleEntity(_validationData.permissionHooks.at(i))); _onUninstall(hookModule, hookData); hookIndex++; } diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 25b8a045..4dd274e4 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -235,21 +235,21 @@ contract UpgradeableModularAccount is /// @inheritdoc IModuleManager /// @notice May be validated by a global validation. - function installExecution(address module, ExecutionManifest calldata manifest, bytes calldata moduleInstallData) - external - override - wrapNativeFunction - { + function installExecution( + address module, + ExecutionManifest calldata manifest, + bytes calldata moduleInstallData + ) external override wrapNativeFunction { _installExecution(module, manifest, moduleInstallData); } /// @inheritdoc IModuleManager /// @notice May be validated by a global validation. - function uninstallExecution(address module, ExecutionManifest calldata manifest, bytes calldata moduleUninstallData) - external - override - wrapNativeFunction - { + function uninstallExecution( + address module, + ExecutionManifest calldata manifest, + bytes calldata moduleUninstallData + ) external override wrapNativeFunction { _uninstallExecution(module, manifest, moduleUninstallData); } @@ -426,7 +426,8 @@ contract UpgradeableModularAccount is } (address module, uint32 entityId) = preUserOpValidationHooks[i].unpack(); - uint256 currentValidationRes = IValidationHook(module).preUserOpValidationHook(entityId, userOp, userOpHash); + uint256 currentValidationRes = + IValidationHook(module).preUserOpValidationHook(entityId, userOp, userOpHash); if (uint160(currentValidationRes) > 1) { // If the aggregator is not 0 or 1, it is an unexpected value @@ -629,7 +630,8 @@ contract UpgradeableModularAccount is msg.sender != address(_ENTRY_POINT) && msg.sender != address(this) && !_storage.selectorData[msg.sig].isPublic ) { - ModuleEntity directCallValidationKey = ModuleEntityLib.pack(msg.sender, DIRECT_CALL_VALIDATION_ENTITYID); + ModuleEntity directCallValidationKey = + ModuleEntityLib.pack(msg.sender, DIRECT_CALL_VALIDATION_ENTITYID); _checkIfValidationAppliesCallData(msg.data, directCallValidationKey, false); @@ -650,15 +652,17 @@ contract UpgradeableModularAccount is } // Exec hooks - PostExecToRun[] memory postExecutionHooks = _doPreHooks(_storage.selectorData[msg.sig].executionHooks, msg.data); + PostExecToRun[] memory postExecutionHooks = + _doPreHooks(_storage.selectorData[msg.sig].executionHooks, msg.data); return (postPermissionHooks, postExecutionHooks); } - function _checkIfValidationAppliesCallData(bytes calldata callData, ModuleEntity validationFunction, bool isGlobal) - internal - view - { + function _checkIfValidationAppliesCallData( + bytes calldata callData, + ModuleEntity validationFunction, + bool isGlobal + ) internal view { bytes4 outerSelector = bytes4(callData[:4]); if (outerSelector == this.executeUserOp.selector) { // If the selector is executeUserOp, pull the actual selector from the following data, diff --git a/src/helpers/HookConfigLib.sol b/src/helpers/HookConfigLib.sol index dd6f82e7..9c94e4bd 100644 --- a/src/helpers/HookConfigLib.sol +++ b/src/helpers/HookConfigLib.sol @@ -44,7 +44,8 @@ library HookConfigLib { bytes32 internal constant _EXEC_HOOK_HAS_POST = bytes32(uint256(1) << 48); function packValidationHook(ModuleEntity _hookFunction) internal pure returns (HookConfig) { - return HookConfig.wrap(bytes26(bytes26(ModuleEntity.unwrap(_hookFunction)) | bytes26(_HOOK_TYPE_VALIDATION))); + return + HookConfig.wrap(bytes26(bytes26(ModuleEntity.unwrap(_hookFunction)) | bytes26(_HOOK_TYPE_VALIDATION))); } function packValidationHook(address _module, uint32 _entityId) internal pure returns (HookConfig) { @@ -58,7 +59,11 @@ library HookConfigLib { ); } - function packExecHook(ModuleEntity _hookFunction, bool _hasPre, bool _hasPost) internal pure returns (HookConfig) { + function packExecHook(ModuleEntity _hookFunction, bool _hasPre, bool _hasPost) + internal + pure + returns (HookConfig) + { return HookConfig.wrap( bytes26( bytes26(ModuleEntity.unwrap(_hookFunction)) diff --git a/src/helpers/KnownSelectors.sol b/src/helpers/KnownSelectors.sol index 1af67369..6e1cfca0 100644 --- a/src/helpers/KnownSelectors.sol +++ b/src/helpers/KnownSelectors.sol @@ -30,7 +30,8 @@ library KnownSelectors { // check against IERC165 methods || selector == IERC165.supportsInterface.selector // check against UUPSUpgradeable methods - || selector == UUPSUpgradeable.proxiableUUID.selector || selector == UUPSUpgradeable.upgradeToAndCall.selector + || selector == UUPSUpgradeable.proxiableUUID.selector + || selector == UUPSUpgradeable.upgradeToAndCall.selector // check against IStandardExecutor methods || selector == IStandardExecutor.execute.selector || selector == IStandardExecutor.executeBatch.selector || selector == IStandardExecutor.executeWithAuthorization.selector @@ -50,9 +51,9 @@ library KnownSelectors { function isIModuleFunction(bytes4 selector) internal pure returns (bool) { return selector == IModule.onInstall.selector || selector == IModule.onUninstall.selector || selector == IExecution.executionManifest.selector || selector == IModule.moduleMetadata.selector - || selector == IExecutionHook.preExecutionHook.selector || selector == IExecutionHook.postExecutionHook.selector - || selector == IValidation.validateUserOp.selector || selector == IValidation.validateRuntime.selector - || selector == IValidation.validateSignature.selector + || selector == IExecutionHook.preExecutionHook.selector + || selector == IExecutionHook.postExecutionHook.selector || selector == IValidation.validateUserOp.selector + || selector == IValidation.validateRuntime.selector || selector == IValidation.validateSignature.selector || selector == IValidationHook.preUserOpValidationHook.selector || selector == IValidationHook.preRuntimeValidationHook.selector; } diff --git a/src/interfaces/IModuleManager.sol b/src/interfaces/IModuleManager.sol index 7a031c7c..74e3a338 100644 --- a/src/interfaces/IModuleManager.sol +++ b/src/interfaces/IModuleManager.sol @@ -19,8 +19,11 @@ interface IModuleManager { /// @param manifest the manifest describing functions to install /// @param moduleInstallData Optional data to be decoded and used by the module to setup initial module data /// for the modular account. - function installExecution(address module, ExecutionManifest calldata manifest, bytes calldata moduleInstallData) - external; + function installExecution( + address module, + ExecutionManifest calldata manifest, + bytes calldata moduleInstallData + ) external; /// @notice Temporary install function - pending a different user-supplied install config & manifest validation /// path. @@ -60,6 +63,9 @@ interface IModuleManager { /// @param manifest the manifest describing functions to uninstall. /// @param moduleUninstallData Optional data to be decoded and used by the module to clear module data for the /// modular account. - function uninstallExecution(address module, ExecutionManifest calldata manifest, bytes calldata moduleUninstallData) - external; + function uninstallExecution( + address module, + ExecutionManifest calldata manifest, + bytes calldata moduleUninstallData + ) external; } diff --git a/src/interfaces/IValidation.sol b/src/interfaces/IValidation.sol index e5fcfefa..4f8fbbb8 100644 --- a/src/interfaces/IValidation.sol +++ b/src/interfaces/IValidation.sol @@ -43,8 +43,11 @@ interface IValidation is IModule { /// @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); + function validateSignature( + address account, + uint32 entityId, + address sender, + bytes32 hash, + bytes calldata signature + ) external view returns (bytes4); } diff --git a/src/modules/ERC20TokenLimitModule.sol b/src/modules/ERC20TokenLimitModule.sol index 0507bfd0..bb13dec3 100644 --- a/src/modules/ERC20TokenLimitModule.sol +++ b/src/modules/ERC20TokenLimitModule.sol @@ -78,7 +78,8 @@ contract ERC20TokenLimitModule is BaseModule, IExecutionHook { /// @inheritdoc IModule function onInstall(bytes calldata data) external override { - (uint32 startEntityId, ERC20SpendLimit[] memory spendLimits) = abi.decode(data, (uint32, ERC20SpendLimit[])); + (uint32 startEntityId, ERC20SpendLimit[] memory spendLimits) = + abi.decode(data, (uint32, ERC20SpendLimit[])); if (startEntityId + spendLimits.length > type(uint32).max) { revert ExceededNumberOfEntities(); diff --git a/src/modules/permissionhooks/AllowlistModule.sol b/src/modules/permissionhooks/AllowlistModule.sol index 7a09b895..2fdf1362 100644 --- a/src/modules/permissionhooks/AllowlistModule.sol +++ b/src/modules/permissionhooks/AllowlistModule.sol @@ -26,7 +26,8 @@ contract AllowlistModule is IValidationHook, BaseModule { } mapping(address target => mapping(address account => AllowlistEntry)) public targetAllowlist; - mapping(address target => mapping(bytes4 selector => mapping(address account => bool))) public selectorAllowlist; + mapping(address target => mapping(bytes4 selector => mapping(address account => bool))) public + selectorAllowlist; error TargetNotAllowed(); error SelectorNotAllowed(); diff --git a/src/modules/validation/SingleSignerValidation.sol b/src/modules/validation/SingleSignerValidation.sol index abdd8e4a..1d00e184 100644 --- a/src/modules/validation/SingleSignerValidation.sol +++ b/src/modules/validation/SingleSignerValidation.sol @@ -86,11 +86,14 @@ contract SingleSignerValidation is ISingleSignerValidation, BaseModule { } /// @inheritdoc IValidation - function validateRuntime(address account, uint32 entityId, address sender, uint256, bytes calldata, bytes calldata) - external - view - override - { + function validateRuntime( + address account, + uint32 entityId, + address sender, + uint256, + bytes calldata, + bytes calldata + ) external view override { if (sender != _getExpectedSigner(entityId, account)) { revert NotAuthorized(); } diff --git a/test/account/AccountExecHooks.t.sol b/test/account/AccountExecHooks.t.sol index c0dda6d7..2e0a90bc 100644 --- a/test/account/AccountExecHooks.t.sol +++ b/test/account/AccountExecHooks.t.sol @@ -31,7 +31,11 @@ contract AccountExecHooksTest is AccountTestBase { _transferOwnershipToTest(); _m1.executionFunctions.push( - ManifestExecutionFunction({executionSelector: _EXEC_SELECTOR, isPublic: true, allowGlobalValidation: false}) + ManifestExecutionFunction({ + executionSelector: _EXEC_SELECTOR, + isPublic: true, + allowGlobalValidation: false + }) ); } diff --git a/test/account/AccountLoupe.t.sol b/test/account/AccountLoupe.t.sol index ff2ecd00..bc75d356 100644 --- a/test/account/AccountLoupe.t.sol +++ b/test/account/AccountLoupe.t.sol @@ -100,7 +100,9 @@ contract AccountLoupeTest is CustomValidationTestBase { assertEq(hooks.length, 3); for (uint256 i = 0; i < hooks.length; i++) { - assertEq(ModuleEntity.unwrap(hooks[i].hookFunction), ModuleEntity.unwrap(expectedHooks[i].hookFunction)); + assertEq( + ModuleEntity.unwrap(hooks[i].hookFunction), ModuleEntity.unwrap(expectedHooks[i].hookFunction) + ); assertEq(hooks[i].isPreHook, expectedHooks[i].isPreHook); assertEq(hooks[i].isPostHook, expectedHooks[i].isPostHook); } diff --git a/test/account/AccountReturnData.t.sol b/test/account/AccountReturnData.t.sol index 9ec9001e..56da489e 100644 --- a/test/account/AccountReturnData.t.sol +++ b/test/account/AccountReturnData.t.sol @@ -64,7 +64,8 @@ contract AccountReturnDataTest is AccountTestBase { function test_returnData_singular_execute() public { bytes memory returnData = account1.executeWithAuthorization( abi.encodeCall( - account1.execute, (address(regularResultContract), 0, abi.encodeCall(RegularResultContract.foo, ())) + account1.execute, + (address(regularResultContract), 0, abi.encodeCall(RegularResultContract.foo, ())) ), _encodeSignature( ModuleEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID), diff --git a/test/account/GlobalValidationTest.t.sol b/test/account/GlobalValidationTest.t.sol index 02e3c3f8..b22d68eb 100644 --- a/test/account/GlobalValidationTest.t.sol +++ b/test/account/GlobalValidationTest.t.sol @@ -26,7 +26,8 @@ contract GlobalValidationTest is AccountTestBase { account2 = UpgradeableModularAccount(payable(factory.getAddress(owner2, 0))); vm.deal(address(account2), 100 ether); - _signerValidation = ModuleEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID); + _signerValidation = + ModuleEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID); ethRecipient = makeAddr("ethRecipient"); vm.deal(ethRecipient, 1 wei); diff --git a/test/account/PerHookData.t.sol b/test/account/PerHookData.t.sol index 75d71270..cc3f3415 100644 --- a/test/account/PerHookData.t.sol +++ b/test/account/PerHookData.t.sol @@ -39,8 +39,9 @@ contract PerHookDataTest is CustomValidationTestBase { PreValidationHookData[] memory preValidationHookData = new PreValidationHookData[](1); preValidationHookData[0] = PreValidationHookData({index: 0, validationData: abi.encodePacked(_counter)}); - userOp.signature = - _encodeSignature(_signerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature( + _signerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v) + ); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -61,8 +62,9 @@ contract PerHookDataTest is CustomValidationTestBase { validationData: abi.encodePacked(address(0x1234123412341234123412341234123412341234)) }); - userOp.signature = - _encodeSignature(_signerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature( + _signerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v) + ); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -106,8 +108,9 @@ contract PerHookDataTest is CustomValidationTestBase { preValidationHookData[0] = PreValidationHookData({index: 0, validationData: abi.encodePacked(_counter)}); preValidationHookData[1] = PreValidationHookData({index: 1, validationData: abi.encodePacked(_counter)}); - userOp.signature = - _encodeSignature(_signerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature( + _signerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v) + ); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -144,8 +147,9 @@ contract PerHookDataTest is CustomValidationTestBase { PreValidationHookData[] memory preValidationHookData = new PreValidationHookData[](1); preValidationHookData[0] = PreValidationHookData({index: 0, validationData: abi.encodePacked(beneficiary)}); - userOp.signature = - _encodeSignature(_signerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature( + _signerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v) + ); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -168,8 +172,9 @@ contract PerHookDataTest is CustomValidationTestBase { PreValidationHookData[] memory preValidationHookData = new PreValidationHookData[](1); preValidationHookData[0] = PreValidationHookData({index: 0, validationData: ""}); - userOp.signature = - _encodeSignature(_signerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v)); + userOp.signature = _encodeSignature( + _signerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v) + ); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -194,7 +199,8 @@ contract PerHookDataTest is CustomValidationTestBase { vm.prank(owner1); account1.executeWithAuthorization( abi.encodeCall( - UpgradeableModularAccount.execute, (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) + UpgradeableModularAccount.execute, + (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) ), _encodeSignature(_signerValidation, GLOBAL_VALIDATION, preValidationHookData, "") ); @@ -220,7 +226,8 @@ contract PerHookDataTest is CustomValidationTestBase { ); account1.executeWithAuthorization( abi.encodeCall( - UpgradeableModularAccount.execute, (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) + UpgradeableModularAccount.execute, + (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) ), _encodeSignature(_signerValidation, GLOBAL_VALIDATION, preValidationHookData, "") ); @@ -238,7 +245,8 @@ contract PerHookDataTest is CustomValidationTestBase { ); account1.executeWithAuthorization( abi.encodeCall( - UpgradeableModularAccount.execute, (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) + UpgradeableModularAccount.execute, + (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) ), _encodeSignature(_signerValidation, GLOBAL_VALIDATION, "") ); @@ -250,10 +258,13 @@ contract PerHookDataTest is CustomValidationTestBase { preValidationHookData[1] = PreValidationHookData({index: 1, validationData: abi.encodePacked(_counter)}); vm.prank(owner1); - vm.expectRevert(abi.encodeWithSelector(UpgradeableModularAccount.ValidationSignatureSegmentMissing.selector)); + vm.expectRevert( + abi.encodeWithSelector(UpgradeableModularAccount.ValidationSignatureSegmentMissing.selector) + ); account1.executeWithAuthorization( abi.encodeCall( - UpgradeableModularAccount.execute, (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) + UpgradeableModularAccount.execute, + (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) ), _encodeSignature(_signerValidation, GLOBAL_VALIDATION, preValidationHookData, "") ); @@ -288,7 +299,8 @@ contract PerHookDataTest is CustomValidationTestBase { vm.expectRevert(abi.encodeWithSelector(UpgradeableModularAccount.NonCanonicalEncoding.selector)); account1.executeWithAuthorization( abi.encodeCall( - UpgradeableModularAccount.execute, (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) + UpgradeableModularAccount.execute, + (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ())) ), _encodeSignature(_signerValidation, GLOBAL_VALIDATION, preValidationHookData, "") ); @@ -331,7 +343,12 @@ contract PerHookDataTest is CustomValidationTestBase { ); return ( - _signerValidation, true, true, new bytes4[](0), abi.encode(TEST_DEFAULT_VALIDATION_ENTITY_ID, owner1), hooks + _signerValidation, + true, + true, + new bytes4[](0), + abi.encode(TEST_DEFAULT_VALIDATION_ENTITY_ID, owner1), + hooks ); } } diff --git a/test/account/SelfCallAuthorization.t.sol b/test/account/SelfCallAuthorization.t.sol index 482ddde7..ed158a30 100644 --- a/test/account/SelfCallAuthorization.t.sol +++ b/test/account/SelfCallAuthorization.t.sol @@ -85,7 +85,8 @@ contract SelfCallAuthorizationTest is AccountTestBase { // Using global validation, self-call bypasses custom validation needed for ComprehensiveModule.foo _runUserOp( abi.encodeCall( - UpgradeableModularAccount.execute, (address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())) + UpgradeableModularAccount.execute, + (address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())) ), abi.encodeWithSelector( IEntryPoint.FailedOpWithRevert.selector, @@ -151,7 +152,8 @@ contract SelfCallAuthorizationTest is AccountTestBase { // Using global validation, self-call bypasses custom validation needed for ComprehensiveModule.foo _runtimeCall( abi.encodeCall( - UpgradeableModularAccount.execute, (address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())) + UpgradeableModularAccount.execute, + (address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())) ), abi.encodeWithSelector(UpgradeableModularAccount.SelfCallRecursionDepthExceeded.selector) ); @@ -174,8 +176,9 @@ contract SelfCallAuthorizationTest is AccountTestBase { calls[0] = Call(address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())); calls[1] = Call(address(account1), 0, abi.encodeCall(ComprehensiveModule.foo, ())); - PackedUserOperation memory userOp = - _generateUserOpWithComprehensiveModuleValidation(abi.encodeCall(IStandardExecutor.executeBatch, (calls))); + PackedUserOperation memory userOp = _generateUserOpWithComprehensiveModuleValidation( + abi.encodeCall(IStandardExecutor.executeBatch, (calls)) + ); PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; @@ -256,7 +259,8 @@ contract SelfCallAuthorizationTest is AccountTestBase { PackedUserOperation memory userOp = _generateUserOpWithComprehensiveModuleValidation( abi.encodePacked( - IAccountExecute.executeUserOp.selector, abi.encodeCall(IStandardExecutor.executeBatch, (outerCalls)) + IAccountExecute.executeUserOp.selector, + abi.encodeCall(IStandardExecutor.executeBatch, (outerCalls)) ) ); @@ -301,7 +305,12 @@ contract SelfCallAuthorizationTest is AccountTestBase { account1.executeWithAuthorization( abi.encodeCall( UpgradeableModularAccount.installValidation, - (ValidationConfigLib.pack(comprehensiveModuleValidation, false, false), selectors, "", new bytes[](0)) + ( + ValidationConfigLib.pack(comprehensiveModuleValidation, false, false), + selectors, + "", + new bytes[](0) + ) ), _encodeSignature(_signerValidation, GLOBAL_VALIDATION, "") ); diff --git a/test/account/UpgradeableModularAccount.t.sol b/test/account/UpgradeableModularAccount.t.sol index f21509f0..0bb281a2 100644 --- a/test/account/UpgradeableModularAccount.t.sol +++ b/test/account/UpgradeableModularAccount.t.sol @@ -345,7 +345,9 @@ contract UpgradeableModularAccountTest is AccountTestBase { bytes32 slot = account3.proxiableUUID(); // account has impl from factory - assertEq(address(factory.accountImplementation()), address(uint160(uint256(vm.load(address(account1), slot))))); + assertEq( + address(factory.accountImplementation()), address(uint160(uint256(vm.load(address(account1), slot)))) + ); account1.upgradeToAndCall(address(account3), bytes("")); // account has new impl assertEq(address(account3), address(uint160(uint256(vm.load(address(account1), slot))))); @@ -386,7 +388,9 @@ contract UpgradeableModularAccountTest is AccountTestBase { for (uint256 i = 0; i < accountWrites.length; i++) { bytes32 valWritten = vm.load(addr, accountWrites[i]); // solhint-disable-next-line no-console - console.log(string.concat("write loc: ", vm.toString(accountWrites[i]), " val: ", vm.toString(valWritten))); + console.log( + string.concat("write loc: ", vm.toString(accountWrites[i]), " val: ", vm.toString(valWritten)) + ); } for (uint256 i = 0; i < accountReads.length; i++) { diff --git a/test/comparison/CompareSimpleAccount.t.sol b/test/comparison/CompareSimpleAccount.t.sol index 359e18a8..a7f59b69 100644 --- a/test/comparison/CompareSimpleAccount.t.sol +++ b/test/comparison/CompareSimpleAccount.t.sol @@ -137,7 +137,9 @@ contract CompareSimpleAccountTest is Test { sender: account2, nonce: 0, initCode: "", - callData: abi.encodeCall(SimpleAccount.execute, (address(counter), 0, abi.encodeCall(Counter.increment, ()))), + callData: abi.encodeCall( + SimpleAccount.execute, (address(counter), 0, abi.encodeCall(Counter.increment, ())) + ), accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), preVerificationGas: 0, gasFees: _encodeGas(1, 2), diff --git a/test/libraries/HookConfigLib.t.sol b/test/libraries/HookConfigLib.t.sol index c8064c67..7a4671b8 100644 --- a/test/libraries/HookConfigLib.t.sol +++ b/test/libraries/HookConfigLib.t.sol @@ -53,7 +53,9 @@ contract HookConfigLibTest is Test { } assertEq( - ModuleEntity.unwrap(hookConfig.moduleEntity()), ModuleEntity.unwrap(hookFunction), "moduleEntity mismatch" + ModuleEntity.unwrap(hookConfig.moduleEntity()), + ModuleEntity.unwrap(hookFunction), + "moduleEntity mismatch" ); assertEq(hookConfig.isValidationHook(), isValidation, "isValidation mismatch"); diff --git a/test/libraries/SparseCalldataSegmentLib.t.sol b/test/libraries/SparseCalldataSegmentLib.t.sol index 374dafd8..7edea4e4 100644 --- a/test/libraries/SparseCalldataSegmentLib.t.sol +++ b/test/libraries/SparseCalldataSegmentLib.t.sol @@ -33,7 +33,8 @@ contract SparseCalldataSegmentLibTest is Test { bytes memory encoded = _encodeWithIndex(segments, indices); // Decode - (bytes[] memory decodedBodies, uint8[] memory decodedIndices) = this.decodeWithIndex(encoded, segments.length); + (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"); @@ -54,7 +55,11 @@ contract SparseCalldataSegmentLibTest is Test { return result; } - function _encodeWithIndex(bytes[] memory segments, uint8[] memory indices) internal pure returns (bytes memory) { + function _encodeWithIndex(bytes[] memory segments, uint8[] memory indices) + internal + pure + returns (bytes memory) + { require(segments.length == indices.length, "segments len != indices len"); bytes memory result = ""; diff --git a/test/libraries/ValidationConfigLib.t.sol b/test/libraries/ValidationConfigLib.t.sol index f903c413..4d49c383 100644 --- a/test/libraries/ValidationConfigLib.t.sol +++ b/test/libraries/ValidationConfigLib.t.sol @@ -19,7 +19,8 @@ contract ValidationConfigLibTest is Test { bool isGlobal, bool isSignatureValidation ) public { - ValidationConfig validationConfig = ValidationConfigLib.pack(module, entityId, isGlobal, isSignatureValidation); + ValidationConfig validationConfig = + ValidationConfigLib.pack(module, entityId, isGlobal, isSignatureValidation); // Test unpacking underlying (address module2, uint32 entityId2, bool isGlobal2, bool isSignatureValidation2) = diff --git a/test/mocks/SingleSignerFactoryFixture.sol b/test/mocks/SingleSignerFactoryFixture.sol index ac044b63..48f7a778 100644 --- a/test/mocks/SingleSignerFactoryFixture.sol +++ b/test/mocks/SingleSignerFactoryFixture.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; +import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; +import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; -import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; -import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; import {SingleSignerValidation} from "../../src/modules/validation/SingleSignerValidation.sol"; import {LibClone} from "solady/utils/LibClone.sol"; @@ -27,8 +27,9 @@ contract SingleSignerFactoryFixture is OptimizedTest { constructor(IEntryPoint _entryPoint, SingleSignerValidation _singleSignerValidation) { entryPoint = _entryPoint; accountImplementation = _deployUpgradeableModularAccount(_entryPoint); - _PROXY_BYTECODE_HASH = - keccak256(abi.encodePacked(type(ERC1967Proxy).creationCode, abi.encode(address(accountImplementation), ""))); + _PROXY_BYTECODE_HASH = keccak256( + abi.encodePacked(type(ERC1967Proxy).creationCode, abi.encode(address(accountImplementation), "")) + ); singleSignerValidation = _singleSignerValidation; } diff --git a/test/mocks/modules/ReturnDataModuleMocks.sol b/test/mocks/modules/ReturnDataModuleMocks.sol index f15cfbc2..d0e8180e 100644 --- a/test/mocks/modules/ReturnDataModuleMocks.sol +++ b/test/mocks/modules/ReturnDataModuleMocks.sol @@ -75,7 +75,10 @@ contract ResultConsumerModule is IExecution, BaseModule, IValidation { revert NotImplemented(); } - function validateRuntime(address, uint32, address sender, uint256, bytes calldata, bytes calldata) external view { + function validateRuntime(address, uint32, address sender, uint256, bytes calldata, bytes calldata) + external + view + { if (sender != address(this)) { revert NotAuthorized(); } diff --git a/test/mocks/modules/ValidationModuleMocks.sol b/test/mocks/modules/ValidationModuleMocks.sol index 0bb9b452..3470495f 100644 --- a/test/mocks/modules/ValidationModuleMocks.sol +++ b/test/mocks/modules/ValidationModuleMocks.sol @@ -113,7 +113,9 @@ contract MockUserOpValidationModule is MockBaseUserOpValidationModule { } contract MockUserOpValidation1HookModule is MockBaseUserOpValidationModule { - function setValidationData(uint256 userOpValidationFunctionData, uint256 preUserOpValidationHook1Data) external { + function setValidationData(uint256 userOpValidationFunctionData, uint256 preUserOpValidationHook1Data) + external + { _userOpValidationFunctionData = userOpValidationFunctionData; _preUserOpValidationHook1Data = preUserOpValidationHook1Data; } diff --git a/test/module/AllowlistModule.t.sol b/test/module/AllowlistModule.t.sol index 0f8dd0e8..b0e09c90 100644 --- a/test/module/AllowlistModule.t.sol +++ b/test/module/AllowlistModule.t.sol @@ -146,7 +146,8 @@ contract AllowlistModuleTest is CustomValidationTestBase { for (uint256 i = 0; i < calls.length; i++) { Call memory call = calls[i]; - (bool allowed, bool hasSelectorAllowlist) = allowlistModule.targetAllowlist(call.target, address(account1)); + (bool allowed, bool hasSelectorAllowlist) = + allowlistModule.targetAllowlist(call.target, address(account1)); if (allowed) { if ( hasSelectorAllowlist @@ -176,7 +177,8 @@ contract AllowlistModuleTest is CustomValidationTestBase { for (uint256 i = 0; i < calls.length; i++) { Call memory call = calls[i]; - (bool allowed, bool hasSelectorAllowlist) = allowlistModule.targetAllowlist(call.target, address(account1)); + (bool allowed, bool hasSelectorAllowlist) = + allowlistModule.targetAllowlist(call.target, address(account1)); if (allowed) { if ( hasSelectorAllowlist @@ -233,7 +235,8 @@ contract AllowlistModuleTest is CustomValidationTestBase { bool isCounter = seed % 2 == 0; seed = _next(seed); - address target = isCounter ? address(counters[seed % counters.length]) : address(uint160(uint256(seed))); + address target = + isCounter ? address(counters[seed % counters.length]) : address(uint160(uint256(seed))); bool hasSelectorAllowlist = seed % 2 == 0; seed = _next(seed); @@ -301,7 +304,12 @@ contract AllowlistModuleTest is CustomValidationTestBase { ); return ( - _signerValidation, true, true, new bytes4[](0), abi.encode(TEST_DEFAULT_VALIDATION_ENTITY_ID, owner1), hooks + _signerValidation, + true, + true, + new bytes4[](0), + abi.encode(TEST_DEFAULT_VALIDATION_ENTITY_ID, owner1), + hooks ); } diff --git a/test/module/ERC20TokenLimitModule.t.sol b/test/module/ERC20TokenLimitModule.t.sol index 8bc45b79..96c846e4 100644 --- a/test/module/ERC20TokenLimitModule.t.sol +++ b/test/module/ERC20TokenLimitModule.t.sol @@ -40,8 +40,11 @@ contract ERC20TokenLimitModuleTest is AccountTestBase { erc20.mint(address(acct), 10 ether); ExecutionHook[] memory permissionHooks = new ExecutionHook[](1); - permissionHooks[0] = - ExecutionHook({hookFunction: ModuleEntityLib.pack(address(module), 0), isPreHook: true, isPostHook: false}); + permissionHooks[0] = ExecutionHook({ + hookFunction: ModuleEntityLib.pack(address(module), 0), + isPreHook: true, + isPostHook: false + }); // arr idx 0 => functionId of 0 has that spend uint256[] memory limits = new uint256[](1); @@ -80,7 +83,8 @@ contract ERC20TokenLimitModuleTest is AccountTestBase { function _getExecuteWithSpend(uint256 value) internal view returns (bytes memory) { return abi.encodeCall( - UpgradeableModularAccount.execute, (address(erc20), 0, abi.encodeCall(IERC20.transfer, (recipient, value))) + UpgradeableModularAccount.execute, + (address(erc20), 0, abi.encodeCall(IERC20.transfer, (recipient, value))) ); } @@ -93,8 +97,10 @@ contract ERC20TokenLimitModuleTest is AccountTestBase { function test_userOp_executeBatchLimit() public { Call[] memory calls = new Call[](3); - calls[0] = Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.transfer, (recipient, 1 wei))}); - calls[1] = Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.transfer, (recipient, 1 ether))}); + calls[0] = + Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.transfer, (recipient, 1 wei))}); + calls[1] = + Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.transfer, (recipient, 1 ether))}); calls[2] = Call({ target: address(erc20), value: 0, @@ -109,8 +115,10 @@ contract ERC20TokenLimitModuleTest is AccountTestBase { function test_userOp_executeBatch_approveAndTransferLimit() public { Call[] memory calls = new Call[](3); - calls[0] = Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.approve, (recipient, 1 wei))}); - calls[1] = Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.transfer, (recipient, 1 ether))}); + calls[0] = + Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.approve, (recipient, 1 wei))}); + calls[1] = + Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.transfer, (recipient, 1 ether))}); calls[2] = Call({ target: address(erc20), value: 0, @@ -125,8 +133,10 @@ contract ERC20TokenLimitModuleTest is AccountTestBase { function test_userOp_executeBatch_approveAndTransferLimit_fail() public { Call[] memory calls = new Call[](3); - calls[0] = Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.approve, (recipient, 1 wei))}); - calls[1] = Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.transfer, (recipient, 1 ether))}); + calls[0] = + Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.approve, (recipient, 1 wei))}); + calls[1] = + Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.transfer, (recipient, 1 ether))}); calls[2] = Call({ target: address(erc20), value: 0, @@ -145,15 +155,18 @@ contract ERC20TokenLimitModuleTest is AccountTestBase { function test_runtime_executeLimit() public { assertEq(module.limits(0, address(erc20), address(acct)), 10 ether); acct.executeWithAuthorization( - _getExecuteWithSpend(5 ether), _encodeSignature(ModuleEntityLib.pack(address(validationModule), 0), 1, "") + _getExecuteWithSpend(5 ether), + _encodeSignature(ModuleEntityLib.pack(address(validationModule), 0), 1, "") ); assertEq(module.limits(0, address(erc20), address(acct)), 5 ether); } function test_runtime_executeBatchLimit() public { Call[] memory calls = new Call[](3); - calls[0] = Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.approve, (recipient, 1 wei))}); - calls[1] = Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.transfer, (recipient, 1 ether))}); + calls[0] = + Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.approve, (recipient, 1 wei))}); + calls[1] = + Call({target: address(erc20), value: 0, data: abi.encodeCall(IERC20.transfer, (recipient, 1 ether))}); calls[2] = Call({ target: address(erc20), value: 0, diff --git a/test/module/NativeTokenLimitModule.t.sol b/test/module/NativeTokenLimitModule.t.sol index 8a823e38..895cd6da 100644 --- a/test/module/NativeTokenLimitModule.t.sol +++ b/test/module/NativeTokenLimitModule.t.sol @@ -52,7 +52,10 @@ contract NativeTokenLimitModuleTest is AccountTestBase { vm.prank(address(acct)); acct.installValidation( - ValidationConfigLib.pack(address(validationModule), 0, true, true), new bytes4[](0), new bytes(0), hooks + ValidationConfigLib.pack(address(validationModule), 0, true, true), + new bytes4[](0), + new bytes(0), + hooks ); validationFunction = ModuleEntityLib.pack(address(validationModule), 0); @@ -154,7 +157,8 @@ contract NativeTokenLimitModuleTest is AccountTestBase { vm.startPrank(address(entryPoint)); assertEq(module.limits(0, address(acct)), 10 ether); PackedUserOperation[] memory uos = new PackedUserOperation[](1); - uos[0] = _getPackedUO(200_000, 200_000, 200_000, 1, abi.encodeCall(IStandardExecutor.executeBatch, (calls))); + uos[0] = + _getPackedUO(200_000, 200_000, 200_000, 1, abi.encodeCall(IStandardExecutor.executeBatch, (calls))); entryPoint.handleOps(uos, bundler); assertEq(module.limits(0, address(acct)), 10 ether - 6 ether - 700_001); diff --git a/test/module/SingleSignerValidation.t.sol b/test/module/SingleSignerValidation.t.sol index 3300ce03..9358b59f 100644 --- a/test/module/SingleSignerValidation.t.sol +++ b/test/module/SingleSignerValidation.t.sol @@ -89,7 +89,9 @@ contract SingleSignerValidationTest is AccountTestBase { vm.prank(owner2); account.executeWithAuthorization( abi.encodeCall(UpgradeableModularAccount.execute, (ethRecipient, 1 wei, "")), - _encodeSignature(ModuleEntityLib.pack(address(singleSignerValidation), newEntityId), GLOBAL_VALIDATION, "") + _encodeSignature( + ModuleEntityLib.pack(address(singleSignerValidation), newEntityId), GLOBAL_VALIDATION, "" + ) ); assertEq(ethRecipient.balance, 1 wei); } diff --git a/test/module/TokenReceiverModule.t.sol b/test/module/TokenReceiverModule.t.sol index 47274cfa..d5df2a97 100644 --- a/test/module/TokenReceiverModule.t.sol +++ b/test/module/TokenReceiverModule.t.sol @@ -35,7 +35,8 @@ contract TokenReceiverModuleTest is OptimizedTest, IERC1155Receiver { function setUp() public { entryPoint = new EntryPoint(); - SingleSignerFactoryFixture factory = new SingleSignerFactoryFixture(entryPoint, _deploySingleSignerValidation()); + SingleSignerFactoryFixture factory = + new SingleSignerFactoryFixture(entryPoint, _deploySingleSignerValidation()); acct = factory.createAccount(address(this), 0); module = _deployTokenReceiverModule(); diff --git a/test/script/Deploy.s.t.sol b/test/script/Deploy.s.t.sol index dc13174b..105ce9b8 100644 --- a/test/script/Deploy.s.t.sol +++ b/test/script/Deploy.s.t.sol @@ -32,7 +32,9 @@ contract DeployTest is Test { _accountImpl = Create2.computeAddress( bytes32(0), - keccak256(abi.encodePacked(type(UpgradeableModularAccount).creationCode, abi.encode(address(_entryPoint)))), + keccak256( + abi.encodePacked(type(UpgradeableModularAccount).creationCode, abi.encode(address(_entryPoint))) + ), CREATE2_FACTORY ); diff --git a/test/utils/AccountTestBase.sol b/test/utils/AccountTestBase.sol index 30948367..733b93e4 100644 --- a/test/utils/AccountTestBase.sol +++ b/test/utils/AccountTestBase.sol @@ -11,7 +11,8 @@ import {Call, IStandardExecutor} from "../../src/interfaces/IStandardExecutor.so import {SingleSignerValidation} from "../../src/modules/validation/SingleSignerValidation.sol"; import {OptimizedTest} from "./OptimizedTest.sol"; -import {TEST_DEFAULT_VALIDATION_ENTITY_ID as EXT_CONST_TEST_DEFAULT_VALIDATION_ENTITY_ID} from "./TestConstants.sol"; +import {TEST_DEFAULT_VALIDATION_ENTITY_ID as EXT_CONST_TEST_DEFAULT_VALIDATION_ENTITY_ID} from + "./TestConstants.sol"; import {SingleSignerFactoryFixture} from "../mocks/SingleSignerFactoryFixture.sol"; @@ -58,7 +59,8 @@ abstract contract AccountTestBase is OptimizedTest { account1 = factory.createAccount(owner1, 0); vm.deal(address(account1), 100 ether); - _signerValidation = ModuleEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID); + _signerValidation = + ModuleEntityLib.pack(address(singleSignerValidation), TEST_DEFAULT_VALIDATION_ENTITY_ID); } function _runExecUserOp(address target, bytes memory callData) internal { @@ -123,7 +125,9 @@ abstract contract AccountTestBase is OptimizedTest { _runtimeCall(abi.encodeCall(IStandardExecutor.execute, (target, 0, callData)), expectedRevertData); } - function _runtimeExecExpFail(address target, bytes memory callData, bytes memory expectedRevertData) internal { + function _runtimeExecExpFail(address target, bytes memory callData, bytes memory expectedRevertData) + internal + { _runtimeCallExpFail(abi.encodeCall(IStandardExecutor.execute, (target, 0, callData)), expectedRevertData); } @@ -213,7 +217,9 @@ abstract contract AccountTestBase is OptimizedTest { for (uint256 i = 0; i < preValidationHookData.length; ++i) { sig = abi.encodePacked( sig, - _packValidationResWithIndex(preValidationHookData[i].index, preValidationHookData[i].validationData) + _packValidationResWithIndex( + preValidationHookData[i].index, preValidationHookData[i].validationData + ) ); } diff --git a/test/utils/CustomValidationTestBase.sol b/test/utils/CustomValidationTestBase.sol index c8a7a52f..3b313039 100644 --- a/test/utils/CustomValidationTestBase.sol +++ b/test/utils/CustomValidationTestBase.sol @@ -29,7 +29,10 @@ abstract contract CustomValidationTestBase is AccountTestBase { account1 = UpgradeableModularAccount(payable(new ERC1967Proxy{salt: 0}(accountImplementation, ""))); account1.initializeWithValidation( - ValidationConfigLib.pack(validationFunction, isGlobal, isSignatureValidation), selectors, installData, hooks + ValidationConfigLib.pack(validationFunction, isGlobal, isSignatureValidation), + selectors, + installData, + hooks ); vm.deal(address(account1), 100 ether); diff --git a/test/utils/OptimizedTest.sol b/test/utils/OptimizedTest.sol index 9194fe39..870d416a 100644 --- a/test/utils/OptimizedTest.sol +++ b/test/utils/OptimizedTest.sol @@ -29,12 +29,16 @@ abstract contract OptimizedTest is Test { return keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b)); } - function _deployUpgradeableModularAccount(IEntryPoint entryPoint) internal returns (UpgradeableModularAccount) { + function _deployUpgradeableModularAccount(IEntryPoint entryPoint) + internal + returns (UpgradeableModularAccount) + { return _isOptimizedTest() ? UpgradeableModularAccount( payable( deployCode( - "out-optimized/UpgradeableModularAccount.sol/UpgradeableModularAccount.json", abi.encode(entryPoint) + "out-optimized/UpgradeableModularAccount.sol/UpgradeableModularAccount.json", + abi.encode(entryPoint) ) ) ) @@ -49,7 +53,9 @@ abstract contract OptimizedTest is Test { function _deploySingleSignerValidation() internal returns (SingleSignerValidation) { return _isOptimizedTest() - ? SingleSignerValidation(deployCode("out-optimized/SingleSignerValidation.sol/SingleSignerValidation.json")) + ? SingleSignerValidation( + deployCode("out-optimized/SingleSignerValidation.sol/SingleSignerValidation.json") + ) : new SingleSignerValidation(); } } From bf65c85c92eeb3c6553b2f2adb4a8d581fd81078 Mon Sep 17 00:00:00 2001 From: zer0dot Date: Mon, 29 Jul 2024 13:05:43 -0400 Subject: [PATCH 110/111] feat: slight optimizations to factory --- src/account/AccountFactory.sol | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/account/AccountFactory.sol b/src/account/AccountFactory.sol index 2e70e59b..b75cf3b4 100644 --- a/src/account/AccountFactory.sol +++ b/src/account/AccountFactory.sol @@ -14,7 +14,6 @@ import {LibClone} from "solady/utils/LibClone.sol"; contract AccountFactory is Ownable { UpgradeableModularAccount public immutable ACCOUNT_IMPL; - bytes32 private immutable _PROXY_BYTECODE_HASH; uint32 public constant UNSTAKE_DELAY = 1 weeks; IEntryPoint public immutable ENTRY_POINT; address public immutable SINGLE_SIGNER_VALIDATION; @@ -26,8 +25,6 @@ contract AccountFactory is Ownable { address owner ) Ownable(owner) { ENTRY_POINT = _entryPoint; - _PROXY_BYTECODE_HASH = - keccak256(abi.encodePacked(type(ERC1967Proxy).creationCode, abi.encode(address(_accountImpl), ""))); ACCOUNT_IMPL = _accountImpl; SINGLE_SIGNER_VALIDATION = _singleSignerValidation; } @@ -43,13 +40,13 @@ contract AccountFactory is Ownable { external returns (UpgradeableModularAccount) { - address addr = getAddress(owner, salt, entityId); + bytes32 fullSalt = getSalt(owner, salt, entityId); + bytes memory immutables = _getImmutableArgs(owner, entityId); + address addr = getAddress(immutables, fullSalt); // short circuit if exists if (addr.code.length == 0) { - LibClone.createDeterministicERC1967( - address(ACCOUNT_IMPL), _getImmutableArgs(owner, entityId), getSalt(owner, salt, entityId) - ); + LibClone.createDeterministicERC1967(address(ACCOUNT_IMPL), immutables, fullSalt); } return UpgradeableModularAccount(payable(addr)); @@ -70,13 +67,8 @@ contract AccountFactory is Ownable { /** * calculate the counterfactual address of this account as it would be returned by createAccount() */ - function getAddress(address owner, uint256 salt, uint32 entityId) public view returns (address) { - return LibClone.predictDeterministicAddressERC1967( - address(ACCOUNT_IMPL), - _getImmutableArgs(owner, entityId), - getSalt(owner, salt, entityId), - address(this) - ); + function getAddress(bytes memory immutables, bytes32 salt) public view returns (address) { + return LibClone.predictDeterministicAddressERC1967(address(ACCOUNT_IMPL), immutables, salt, address(this)); } function getSalt(address owner, uint256 salt, uint32 entityId) public pure returns (bytes32) { From 58287b37318f8d177d350094c4bdca48f67441d2 Mon Sep 17 00:00:00 2001 From: zer0dot Date: Tue, 30 Jul 2024 22:14:35 -0400 Subject: [PATCH 111/111] chore: linting --- src/account/AccountFactory.sol | 3 --- src/modules/validation/SingleSignerValidation.sol | 4 +++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/account/AccountFactory.sol b/src/account/AccountFactory.sol index b75cf3b4..9dc1aa43 100644 --- a/src/account/AccountFactory.sol +++ b/src/account/AccountFactory.sol @@ -4,12 +4,9 @@ pragma solidity ^0.8.19; import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; import {ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol"; import {UpgradeableModularAccount} from "../account/UpgradeableModularAccount.sol"; -import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; import {LibClone} from "solady/utils/LibClone.sol"; contract AccountFactory is Ownable { diff --git a/src/modules/validation/SingleSignerValidation.sol b/src/modules/validation/SingleSignerValidation.sol index 1d00e184..2f24800d 100644 --- a/src/modules/validation/SingleSignerValidation.sol +++ b/src/modules/validation/SingleSignerValidation.sol @@ -45,6 +45,8 @@ contract SingleSignerValidation is ISingleSignerValidation, BaseModule { bytes4 internal constant _1271_MAGIC_VALUE = 0x1626ba7e; bytes4 internal constant _1271_INVALID = 0xffffffff; + error AppendedValidationMismatch(); + mapping(uint32 entityId => mapping(address account => address)) public signer; /// @inheritdoc ISingleSignerValidation @@ -153,7 +155,7 @@ contract SingleSignerValidation is ISingleSignerValidation, BaseModule { ModuleEntity validation = ModuleEntity.wrap(bytes24(immutables)); if (!validation.eq(ModuleEntityLib.pack(address(this), entityId))) { - revert("Validation incorrect"); + revert AppendedValidationMismatch(); } address decodedSigner;