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 + ); + } +}