diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 4937df7b..72832bbe 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -58,6 +58,8 @@ contract UpgradeableModularAccount is event ModularAccountInitialized(IEntryPoint indexed entryPoint); + event GotHere(); + error AuthorizeUpgradeReverted(bytes revertReason); error ExecFromPluginNotPermitted(address plugin, bytes4 selector); error ExecFromPluginExternalNotPermitted(address plugin, address target, uint256 value, bytes data); @@ -410,7 +412,8 @@ contract UpgradeableModularAccount is userOp.signature = signatureSegment.getBody(); (address plugin, uint8 functionId) = userOpValidationFunction.unpack(); - uint256 currentValidationData = IValidation(plugin).validateUserOp(functionId, userOp, userOpHash); + (uint256 currentValidationData, bytes memory validationComposition) = + IValidation(plugin).validateUserOp(functionId, userOp, userOpHash); if (preUserOpValidationHooks.length != 0) { // If we have other validation data we need to coalesce with @@ -418,6 +421,52 @@ contract UpgradeableModularAccount is } else { validationData = currentValidationData; } + + if (validationComposition.length > 0) { + // We have additional validations we need to run, in addition to the current one. + + //todo: enforce user op hash uniqueness + + FunctionReference[] memory chainedValidations = + abi.decode(validationComposition, (FunctionReference[])); + + emit GotHere(); + currentValidationData = + _doChainedValidation(chainedValidations, userOp, signatureSegment.getBody(), userOpHash); + + validationData = _coalescePreValidation(validationData, currentValidationData); + } + } + + return validationData; + } + + function _doChainedValidation( + FunctionReference[] memory chainedValidations, + PackedUserOperation memory userOp, + bytes calldata outerSignature, + bytes32 userOpHash + ) internal returns (uint256) { + uint256 validationData; + uint256 currentValidationData; + + bytes calldata signatureSegment; + (signatureSegment, outerSignature) = outerSignature.getNextSegment(); + + for (uint256 i = 0; i < chainedValidations.length; ++i) { + if (signatureSegment.getIndex() != i) { + // For chained validation, all signature segments must be explicitly given + revert SignatureSegmentOutOfOrder(); + } + + currentValidationData = + _doUserOpValidation(chainedValidations[i], userOp, signatureSegment.getBody(), userOpHash); + validationData = _coalescePreValidation(validationData, currentValidationData); + + // Load the next per-validation data segment, if one exists + if (i + 1 < chainedValidations.length) { + (signatureSegment, outerSignature) = outerSignature.getNextSegment(); + } } return validationData; diff --git a/src/interfaces/IValidation.sol b/src/interfaces/IValidation.sol index b3adcd3d..f675c04b 100644 --- a/src/interfaces/IValidation.sol +++ b/src/interfaces/IValidation.sol @@ -14,7 +14,7 @@ interface IValidation is IPlugin { /// @return Packed validation data for validAfter (6 bytes), validUntil (6 bytes), and authorizer (20 bytes). function validateUserOp(uint8 functionId, PackedUserOperation calldata userOp, bytes32 userOpHash) external - returns (uint256); + returns (uint256, bytes memory); /// @notice Run the runtime validationFunction specified by the `functionId`. /// @dev To indicate the entire call should revert, the function MUST revert. diff --git a/src/plugins/owner/ComposableMultisigPlugin.sol b/src/plugins/owner/ComposableMultisigPlugin.sol new file mode 100644 index 00000000..ccbe8cf5 --- /dev/null +++ b/src/plugins/owner/ComposableMultisigPlugin.sol @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.25; + +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; + +import {FunctionReference} from "../../helpers/FunctionReferenceLib.sol"; +import {IPlugin} from "../../interfaces/IPlugin.sol"; +import {IValidation} from "../../interfaces/IValidation.sol"; +import {BasePlugin} from "../BasePlugin.sol"; +import {PluginManifest, PluginMetadata} from "../../interfaces/IPlugin.sol"; + +// Non-threshold based multisig plugin - all owners must sign. +// Supports up to 100 owners per id. +contract ComposableMultisigPlugin is IValidation, BasePlugin { + struct OwnerInfo { + uint256 length; + FunctionReference[100] validations; + } + + uint256 internal constant _SIG_VALIDATION_PASSED = 0; + uint256 internal constant _SIG_VALIDATION_FAILED = 1; + + mapping(uint8 id => mapping(address account => OwnerInfo)) public ownerInfo; + + error AlreadyInitialized(); + error NotAuthorized(); + error NotInitialized(); + error InvalidOwners(); + + /// @inheritdoc IPlugin + function onInstall(bytes calldata data) external override { + uint8 id = uint8(bytes1(data[:1])); + + if (ownerInfo[id][msg.sender].length != 0) { + revert AlreadyInitialized(); + } + + FunctionReference[] memory validations = abi.decode(data[1:], (FunctionReference[])); + + if (validations.length == 0 || validations.length > 100) { + revert InvalidOwners(); + } + + ownerInfo[id][msg.sender].length = validations.length; + + for (uint256 i = 0; i < validations.length; i++) { + ownerInfo[id][msg.sender].validations[i] = validations[i]; + } + } + + /// @inheritdoc IPlugin + function onUninstall(bytes calldata data) external override { + uint8 id = uint8(bytes1(data[:1])); + + uint256 length = ownerInfo[id][msg.sender].length; + + if (length == 0) { + revert NotInitialized(); + } + + for (uint256 i = 0; i < length; i++) { + ownerInfo[id][msg.sender].validations[i] = FunctionReference.wrap(bytes21(0)); + } + + ownerInfo[id][msg.sender].length = 0; + } + + /// @inheritdoc IValidation + function validateUserOp(uint8 functionId, PackedUserOperation calldata, bytes32) + external + view + override + returns (uint256, bytes memory) + { + OwnerInfo storage info = ownerInfo[functionId][msg.sender]; + + if (info.length == 0) { + revert NotInitialized(); + } + + FunctionReference[] memory validations = new FunctionReference[](info.length); + + for (uint256 i = 0; i < info.length; i++) { + validations[i] = info.validations[i]; + } + + return (_SIG_VALIDATION_PASSED, abi.encode(validations)); + } + + /// @inheritdoc IValidation + function validateRuntime(uint8, address, uint256, bytes calldata, bytes calldata) external pure override { + revert NotImplemented(); + } + + // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ + // ┃ 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, address, bytes32, bytes calldata) external pure override returns (bytes4) { + revert NotImplemented(); + } + + /// @inheritdoc IPlugin + // solhint-disable-next-line no-empty-blocks + function pluginManifest() external pure override returns (PluginManifest memory) {} + + /// @inheritdoc IPlugin + // solhint-disable-next-line no-empty-blocks + function pluginMetadata() external pure virtual override returns (PluginMetadata memory) {} +} diff --git a/src/plugins/owner/ECDSAValidationPlugin.sol b/src/plugins/owner/ECDSAValidationPlugin.sol new file mode 100644 index 00000000..988338c8 --- /dev/null +++ b/src/plugins/owner/ECDSAValidationPlugin.sol @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.25; + +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; +import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; + +import {IPlugin} from "../../interfaces/IPlugin.sol"; +import {IValidation} from "../../interfaces/IValidation.sol"; +import {BasePlugin} from "../BasePlugin.sol"; +import {PluginManifest, PluginMetadata} from "../../interfaces/IPlugin.sol"; + +contract ECDSAValidationPlugin is IValidation, BasePlugin { + using ECDSA for bytes32; + using MessageHashUtils for bytes32; + + 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; + + error AlreadyInitialized(); + error NotAuthorized(); + error NotInitialized(); + + /// @inheritdoc IPlugin + function onInstall(bytes calldata data) external override { + uint8 id = uint8(bytes1(data[:1])); + + if (owners[id][msg.sender] != address(0)) { + revert AlreadyInitialized(); + } + + address owner = abi.decode(data[1:], (address)); + owners[id][msg.sender] = owner; + } + + /// @inheritdoc IPlugin + function onUninstall(bytes calldata data) external override { + uint8 id = uint8(bytes1(data[:1])); + + if (owners[id][msg.sender] == address(0)) { + revert NotInitialized(); + } + + delete owners[id][msg.sender]; + } + + /// @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, bytes memory) + { + // Validate the user op signature against the owner. + (address signer,,) = (userOpHash.toEthSignedMessageHash()).tryRecover(userOp.signature); + if (signer == address(0) || signer != owners[functionId][msg.sender]) { + return (_SIG_VALIDATION_FAILED, ""); + } + return (_SIG_VALIDATION_PASSED, ""); + } + + // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ + // ┃ 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 (digest.recover(signature) == owners[functionId][msg.sender]) { + return _1271_MAGIC_VALUE; + } + return _1271_INVALID; + } + + /// @inheritdoc IPlugin + // solhint-disable-next-line no-empty-blocks + function pluginManifest() external pure override returns (PluginManifest memory) {} + + /// @inheritdoc IPlugin + // solhint-disable-next-line no-empty-blocks + function pluginMetadata() external pure virtual override returns (PluginMetadata memory) {} +} diff --git a/src/plugins/owner/SingleOwnerPlugin.sol b/src/plugins/owner/SingleOwnerPlugin.sol index dbdd41b2..95468628 100644 --- a/src/plugins/owner/SingleOwnerPlugin.sol +++ b/src/plugins/owner/SingleOwnerPlugin.sol @@ -86,7 +86,7 @@ contract SingleOwnerPlugin is ISingleOwnerPlugin, BasePlugin { { if (functionId == uint8(FunctionId.VALIDATION_OWNER)) { // Validate that the sender is the owner of the account or self. - if (sender != _owners[msg.sender] && sender != msg.sender) { + if (sender != _owners[msg.sender]) { revert NotAuthorized(); } return; @@ -99,15 +99,15 @@ contract SingleOwnerPlugin is ISingleOwnerPlugin, BasePlugin { external view override - returns (uint256) + returns (uint256, bytes memory) { if (functionId == uint8(FunctionId.VALIDATION_OWNER)) { // Validate the user op signature against the owner. (address signer,,) = (userOpHash.toEthSignedMessageHash()).tryRecover(userOp.signature); if (signer == address(0) || signer != _owners[msg.sender]) { - return _SIG_VALIDATION_FAILED; + return (_SIG_VALIDATION_FAILED, ""); } - return _SIG_VALIDATION_PASSED; + return (_SIG_VALIDATION_PASSED, ""); } revert NotImplemented(); } diff --git a/test/account/ComposableValidation.t.sol b/test/account/ComposableValidation.t.sol new file mode 100644 index 00000000..f9f9b615 --- /dev/null +++ b/test/account/ComposableValidation.t.sol @@ -0,0 +1,283 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.25; + +import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; +import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; + +import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; +import {ECDSAValidationPlugin} from "../../src/plugins/owner/ECDSAValidationPlugin.sol"; +import {IStandardExecutor, Call} from "../../src/interfaces/IStandardExecutor.sol"; +import {ComposableMultisigPlugin} from "../../src/plugins/owner/ComposableMultisigPlugin.sol"; +import {FunctionReference, FunctionReferenceLib} from "../../src/helpers/FunctionReferenceLib.sol"; + +import {CustomValidationTestBase} from "../utils/CustomValidationTestBase.sol"; + +contract ComposableValidationTest is CustomValidationTestBase { + using MessageHashUtils for bytes32; + + ECDSAValidationPlugin public ecdsaValidationPlugin; + ComposableMultisigPlugin public composableMultisigPlugin; + + function setUp() public { + ecdsaValidationPlugin = new ECDSAValidationPlugin(); + composableMultisigPlugin = new ComposableMultisigPlugin(); + + _ownerValidation = FunctionReferenceLib.pack(address(ecdsaValidationPlugin), uint8(123)); + } + + function test_basicUserOp_withECDSAValidation() public { + _customValidationSetup(); + + // Now that the account is set up with the ECDSAValidationPlugin, we can test the basic user op + PackedUserOperation memory userOp = PackedUserOperation({ + sender: address(account1), + nonce: 0, + initCode: hex"", + callData: abi.encodeCall(IStandardExecutor.execute, (beneficiary, 0, hex"")), + accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), + preVerificationGas: 0, + gasFees: _encodeGas(1, 1), + paymasterAndData: hex"", + signature: hex"" + }); + + bytes32 userOpHash = entryPoint.getUserOpHash(userOp); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash()); + + userOp.signature = _encodeSignature(_ownerValidation, DEFAULT_VALIDATION, abi.encodePacked(r, s, v)); + + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); + userOps[0] = userOp; + + entryPoint.handleOps(userOps, beneficiary); + } + + function test_basicUserOp_withComposableMultisig_oneLayer() public { + (address owner2, uint256 owner2Key) = makeAddrAndKey("owner2"); + (address owner3, uint256 owner3Key) = makeAddrAndKey("owner3"); + + _customValidationSetup(); + + // Install the multisig plugin with signers 2 and 3 + + FunctionReference composableMultisigValidation = + FunctionReferenceLib.pack(address(composableMultisigPlugin), uint8(0)); + FunctionReference owner2Validation = FunctionReferenceLib.pack(address(ecdsaValidationPlugin), uint8(2)); + FunctionReference owner3Validation = FunctionReferenceLib.pack(address(ecdsaValidationPlugin), uint8(3)); + + FunctionReference[] memory multisigSigners = new FunctionReference[](2); + multisigSigners[0] = owner2Validation; + multisigSigners[1] = owner3Validation; + + // Set up the ComposableMultisigPlugin + Call[] memory calls = new Call[](3); + calls[0] = Call( + address(ecdsaValidationPlugin), + 0, + abi.encodeCall(ECDSAValidationPlugin.onInstall, (abi.encodePacked(uint8(2), abi.encode(owner2)))) + ); + calls[1] = Call( + address(ecdsaValidationPlugin), + 0, + abi.encodeCall(ECDSAValidationPlugin.onInstall, (abi.encodePacked(uint8(3), abi.encode(owner3)))) + ); + calls[2] = Call( + address(account1), + 0, + abi.encodeCall( + UpgradeableModularAccount.installValidation, + ( + composableMultisigValidation, + true, + new bytes4[](0), + abi.encodePacked(uint8(0), abi.encode(multisigSigners)), + "" + ) + ) + ); + + vm.prank(owner1); + account1.executeWithAuthorization( + abi.encodeCall(IStandardExecutor.executeBatch, (calls)), + _encodeSignature(_ownerValidation, DEFAULT_VALIDATION, "") + ); + + // test the multisig validation + + PackedUserOperation memory userOp = PackedUserOperation({ + sender: address(account1), + nonce: 0, + initCode: hex"", + callData: abi.encodeCall(IStandardExecutor.execute, (beneficiary, 0, hex"")), + accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), + preVerificationGas: 0, + gasFees: _encodeGas(1, 1), + paymasterAndData: hex"", + signature: hex"" + }); + + bytes32 userOpHash = entryPoint.getUserOpHash(userOp); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); + bytes memory owner2Signature = abi.encodePacked(r, s, v); + + (v, r, s) = vm.sign(owner3Key, userOpHash.toEthSignedMessageHash()); + bytes memory owner3Signature = abi.encodePacked(r, s, v); + + userOp.signature = _encodeSignature( + composableMultisigValidation, + DEFAULT_VALIDATION, + abi.encodePacked( + _packValidationDataWithIndex(0, _packValidationDataWithIndex(0xff, owner2Signature)), + _packValidationDataWithIndex(1, _packValidationDataWithIndex(0xff, owner3Signature)) + ) + ); + + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); + userOps[0] = userOp; + + entryPoint.handleOps(userOps, beneficiary); + } + + function test_basicUserOp_withComposableMultisig_twoLayers() public { + (address owner2, uint256 owner2Key) = makeAddrAndKey("owner2"); + (address owner3, uint256 owner3Key) = makeAddrAndKey("owner3"); + (address owner4, uint256 owner4Key) = makeAddrAndKey("owner4"); + + _customValidationSetup(); + + // create signers 2, 3, 4 + // Install the multisig plugin with [signer 2, another multisig [signer 3, signer 4]] + + // To prevent stack too deep, put it in memory. + // 0 = outerMultisigValidation + // 1 = owner2Validation + // 2 = innerMultisigValidation + // 3 = owner3Validation + // 4 = owner4Validation + + FunctionReference[5] memory validations; + + validations[0] = FunctionReferenceLib.pack(address(composableMultisigPlugin), uint8(0)); + validations[1] = FunctionReferenceLib.pack(address(ecdsaValidationPlugin), uint8(2)); + validations[2] = FunctionReferenceLib.pack(address(composableMultisigPlugin), uint8(1)); + validations[3] = FunctionReferenceLib.pack(address(ecdsaValidationPlugin), uint8(3)); + validations[4] = FunctionReferenceLib.pack(address(ecdsaValidationPlugin), uint8(4)); + + FunctionReference[] memory innerMultisigSigners = new FunctionReference[](2); + innerMultisigSigners[0] = validations[3]; + innerMultisigSigners[1] = validations[4]; + + FunctionReference[] memory outerMultisigSigners = new FunctionReference[](2); + outerMultisigSigners[0] = validations[1]; + outerMultisigSigners[1] = validations[2]; + + // Set up the ComposableMultisigPlugin + Call[] memory calls = new Call[](5); + calls[0] = Call( + address(ecdsaValidationPlugin), + 0, + abi.encodeCall(ECDSAValidationPlugin.onInstall, (abi.encodePacked(uint8(2), abi.encode(owner2)))) + ); + calls[1] = Call( + address(ecdsaValidationPlugin), + 0, + abi.encodeCall(ECDSAValidationPlugin.onInstall, (abi.encodePacked(uint8(3), abi.encode(owner3)))) + ); + calls[2] = Call( + address(ecdsaValidationPlugin), + 0, + abi.encodeCall(ECDSAValidationPlugin.onInstall, (abi.encodePacked(uint8(4), abi.encode(owner4)))) + ); + calls[3] = Call( + address(composableMultisigPlugin), + 0, + abi.encodeCall( + ECDSAValidationPlugin.onInstall, (abi.encodePacked(uint8(1), abi.encode(innerMultisigSigners))) + ) + ); + calls[4] = Call( + address(account1), + 0, + abi.encodeCall( + UpgradeableModularAccount.installValidation, + ( + validations[0], + true, + new bytes4[](0), + abi.encodePacked(uint8(0), abi.encode(outerMultisigSigners)), + "" + ) + ) + ); + + vm.prank(owner1); + account1.executeWithAuthorization( + abi.encodeCall(IStandardExecutor.executeBatch, (calls)), + _encodeSignature(_ownerValidation, DEFAULT_VALIDATION, "") + ); + + // test the multisig of multisigs validation + + PackedUserOperation memory userOp = PackedUserOperation({ + sender: address(account1), + nonce: 0, + initCode: hex"", + callData: abi.encodeCall(IStandardExecutor.execute, (beneficiary, 0, hex"")), + accountGasLimits: _encodeGas(VERIFICATION_GAS_LIMIT, CALL_GAS_LIMIT), + preVerificationGas: 0, + gasFees: _encodeGas(1, 1), + paymasterAndData: hex"", + signature: hex"" + }); + + bytes32 userOpHash = entryPoint.getUserOpHash(userOp); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash()); + bytes memory owner2Signature = abi.encodePacked(r, s, v); + + (v, r, s) = vm.sign(owner3Key, userOpHash.toEthSignedMessageHash()); + bytes memory owner3Signature = abi.encodePacked(r, s, v); + + (v, r, s) = vm.sign(owner4Key, userOpHash.toEthSignedMessageHash()); + bytes memory owner4Signature = abi.encodePacked(r, s, v); + + userOp.signature = _encodeSignature( + validations[0], + DEFAULT_VALIDATION, + abi.encodePacked( + _packValidationDataWithIndex(0, _packValidationDataWithIndex(0xff, owner2Signature)), + _packValidationDataWithIndex( + 1, + _packValidationDataWithIndex( + 0xff, + abi.encodePacked( + _packValidationDataWithIndex(0, _packValidationDataWithIndex(0xff, owner3Signature)), + _packValidationDataWithIndex(1, _packValidationDataWithIndex(0xff, owner4Signature)) + ) + ) + ) + ) + ); + + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); + userOps[0] = userOp; + + entryPoint.handleOps(userOps, beneficiary); + } + + function _initialValidationConfig() + internal + virtual + override + returns (FunctionReference, bool, bytes4[] memory, bytes memory, bytes memory) + { + return ( + _ownerValidation, + true, + new bytes4[](0), + abi.encodePacked(uint8(123), abi.encode(owner1)), + abi.encodePacked("") + ); + } +} diff --git a/test/mocks/plugins/ComprehensivePlugin.sol b/test/mocks/plugins/ComprehensivePlugin.sol index 4062218b..daac72b7 100644 --- a/test/mocks/plugins/ComprehensivePlugin.sol +++ b/test/mocks/plugins/ComprehensivePlugin.sol @@ -66,10 +66,10 @@ contract ComprehensivePlugin is IValidation, IValidationHook, IExecutionHook, Ba external pure override - returns (uint256) + returns (uint256, bytes memory) { if (functionId == uint8(FunctionId.VALIDATION)) { - return 0; + return (0, ""); } revert NotImplemented(); } diff --git a/test/mocks/plugins/ReturnDataPluginMocks.sol b/test/mocks/plugins/ReturnDataPluginMocks.sol index 7826c755..dfd17264 100644 --- a/test/mocks/plugins/ReturnDataPluginMocks.sol +++ b/test/mocks/plugins/ReturnDataPluginMocks.sol @@ -74,7 +74,11 @@ 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(uint8, PackedUserOperation calldata, bytes32) + external + pure + returns (uint256, bytes memory) + { revert NotImplemented(); } diff --git a/test/mocks/plugins/ValidationPluginMocks.sol b/test/mocks/plugins/ValidationPluginMocks.sol index f6ed4a5f..ad956944 100644 --- a/test/mocks/plugins/ValidationPluginMocks.sol +++ b/test/mocks/plugins/ValidationPluginMocks.sol @@ -52,10 +52,10 @@ abstract contract MockBaseUserOpValidationPlugin is IValidation, IValidationHook external view override - returns (uint256) + returns (uint256, bytes memory) { if (functionId == uint8(FunctionId.USER_OP_VALIDATION)) { - return _userOpValidationFunctionData; + return (_userOpValidationFunctionData, ""); } revert NotImplemented(); } diff --git a/test/plugin/SingleOwnerPlugin.t.sol b/test/plugin/SingleOwnerPlugin.t.sol index 41997591..2fdef31e 100644 --- a/test/plugin/SingleOwnerPlugin.t.sol +++ b/test/plugin/SingleOwnerPlugin.t.sol @@ -133,7 +133,7 @@ contract SingleOwnerPluginTest is OptimizedTest { userOp.signature = abi.encodePacked(r, s, v); // sig check should fail - uint256 success = + (uint256 success,) = plugin.validateUserOp(uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER), userOp, userOpHash); assertEq(success, 1); @@ -142,7 +142,8 @@ contract SingleOwnerPluginTest is OptimizedTest { assertEq(signer, plugin.owner()); // sig check should pass - success = plugin.validateUserOp(uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER), userOp, userOpHash); + (success,) = + plugin.validateUserOp(uint8(ISingleOwnerPlugin.FunctionId.VALIDATION_OWNER), userOp, userOpHash); assertEq(success, 0); }