From ab96c4871a6c406d7aa5d2ee3edaba21a1839061 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Mon, 16 Dec 2024 10:44:00 +0100 Subject: [PATCH] Cherrypick #5353 into release-v5.2 (#5367) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hadrien Croubois Co-authored-by: Ernesto GarcĂ­a Co-authored-by: Francisco Giordano --- .changeset/seven-insects-taste.md | 5 + .../account/utils/draft-ERC7579Utils.sol | 53 ++- foundry.toml | 1 + test/account/utils/draft-ERC4337Utils.test.js | 14 +- test/account/utils/draft-ERC7579Utils.t.sol | 421 ++++++++++++++++++ test/account/utils/draft-ERC7579Utils.test.js | 28 +- test/bin/EntryPoint070.abi | 1 + test/bin/EntryPoint070.bytecode | Bin 0 -> 16035 bytes test/bin/SenderCreator070.abi | 1 + test/bin/SenderCreator070.bytecode | Bin 0 -> 451 bytes test/helpers/erc4337-entrypoint.js | 31 ++ 11 files changed, 528 insertions(+), 27 deletions(-) create mode 100644 .changeset/seven-insects-taste.md create mode 100644 test/account/utils/draft-ERC7579Utils.t.sol create mode 100644 test/bin/EntryPoint070.abi create mode 100644 test/bin/EntryPoint070.bytecode create mode 100644 test/bin/SenderCreator070.abi create mode 100644 test/bin/SenderCreator070.bytecode create mode 100644 test/helpers/erc4337-entrypoint.js diff --git a/.changeset/seven-insects-taste.md b/.changeset/seven-insects-taste.md new file mode 100644 index 00000000000..bfa8737d7de --- /dev/null +++ b/.changeset/seven-insects-taste.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': patch +--- + +`ERC7579Utils`: Add ABI decoding checks on calldata bounds within `decodeBatch` diff --git a/contracts/account/utils/draft-ERC7579Utils.sol b/contracts/account/utils/draft-ERC7579Utils.sol index c28b514d573..b482f95c1d9 100644 --- a/contracts/account/utils/draft-ERC7579Utils.sol +++ b/contracts/account/utils/draft-ERC7579Utils.sol @@ -62,10 +62,13 @@ library ERC7579Utils { /// @dev The module type is not supported. error ERC7579UnsupportedModuleType(uint256 moduleTypeId); + /// @dev Input calldata not properly formatted and possibly malicious. + error ERC7579DecodingError(); + /// @dev Executes a single call. function execSingle( - ExecType execType, - bytes calldata executionCalldata + bytes calldata executionCalldata, + ExecType execType ) internal returns (bytes[] memory returnData) { (address target, uint256 value, bytes calldata callData) = decodeSingle(executionCalldata); returnData = new bytes[](1); @@ -74,8 +77,8 @@ library ERC7579Utils { /// @dev Executes a batch of calls. function execBatch( - ExecType execType, - bytes calldata executionCalldata + bytes calldata executionCalldata, + ExecType execType ) internal returns (bytes[] memory returnData) { Execution[] calldata executionBatch = decodeBatch(executionCalldata); returnData = new bytes[](executionBatch.length); @@ -92,8 +95,8 @@ library ERC7579Utils { /// @dev Executes a delegate call. function execDelegateCall( - ExecType execType, - bytes calldata executionCalldata + bytes calldata executionCalldata, + ExecType execType ) internal returns (bytes[] memory returnData) { (address target, bytes calldata callData) = decodeDelegate(executionCalldata); returnData = new bytes[](1); @@ -170,12 +173,40 @@ library ERC7579Utils { } /// @dev Decodes a batch of executions. See {encodeBatch}. + /// + /// NOTE: This function runs some checks and will throw a {ERC7579DecodingError} if the input is not properly formatted. function decodeBatch(bytes calldata executionCalldata) internal pure returns (Execution[] calldata executionBatch) { - assembly ("memory-safe") { - let ptr := add(executionCalldata.offset, calldataload(executionCalldata.offset)) - // Extract the ERC7579 Executions - executionBatch.offset := add(ptr, 32) - executionBatch.length := calldataload(ptr) + unchecked { + uint256 bufferLength = executionCalldata.length; + + // Check executionCalldata is not empty. + if (bufferLength < 32) revert ERC7579DecodingError(); + + // Get the offset of the array (pointer to the array length). + uint256 arrayLengthOffset = uint256(bytes32(executionCalldata[0:32])); + + // The array length (at arrayLengthOffset) should be 32 bytes long. We check that this is within the + // buffer bounds. Since we know bufferLength is at least 32, we can subtract with no overflow risk. + if (arrayLengthOffset > bufferLength - 32) revert ERC7579DecodingError(); + + // Get the array length. arrayLengthOffset + 32 is bounded by bufferLength so it does not overflow. + uint256 arrayLength = uint256(bytes32(executionCalldata[arrayLengthOffset:arrayLengthOffset + 32])); + + // Check that the buffer is long enough to store the array elements as "offset pointer": + // - each element of the array is an "offset pointer" to the data. + // - each "offset pointer" (to an array element) takes 32 bytes. + // - validity of the calldata at that location is checked when the array element is accessed, so we only + // need to check that the buffer is large enough to hold the pointers. + // + // Since we know bufferLength is at least arrayLengthOffset + 32, we can subtract with no overflow risk. + // Solidity limits length of such arrays to 2**64-1, this guarantees `arrayLength * 32` does not overflow. + if (arrayLength > type(uint64).max || bufferLength - arrayLengthOffset - 32 < arrayLength * 32) + revert ERC7579DecodingError(); + + assembly ("memory-safe") { + executionBatch.offset := add(add(executionCalldata.offset, arrayLengthOffset), 32) + executionBatch.length := arrayLength + } } } diff --git a/foundry.toml b/foundry.toml index 3f60b7cbbf1..78dd0781224 100644 --- a/foundry.toml +++ b/foundry.toml @@ -8,6 +8,7 @@ out = 'out' libs = ['node_modules', 'lib'] test = 'test' cache_path = 'cache_forge' +fs_permissions = [{ access = "read", path = "./test/bin" }] [fuzz] runs = 5000 diff --git a/test/account/utils/draft-ERC4337Utils.test.js b/test/account/utils/draft-ERC4337Utils.test.js index ab2500b5382..7c292910dfb 100644 --- a/test/account/utils/draft-ERC4337Utils.test.js +++ b/test/account/utils/draft-ERC4337Utils.test.js @@ -3,11 +3,13 @@ const { expect } = require('chai'); const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); const { packValidationData, UserOperation } = require('../../helpers/erc4337'); +const { deployEntrypoint } = require('../../helpers/erc4337-entrypoint'); const { MAX_UINT48 } = require('../../helpers/constants'); const ADDRESS_ONE = '0x0000000000000000000000000000000000000001'; const fixture = async () => { - const [authorizer, sender, entrypoint, factory, paymaster] = await ethers.getSigners(); + const { entrypoint } = await deployEntrypoint(); + const [authorizer, sender, factory, paymaster] = await ethers.getSigners(); const utils = await ethers.deployContract('$ERC4337Utils'); const SIG_VALIDATION_SUCCESS = await utils.$SIG_VALIDATION_SUCCESS(); const SIG_VALIDATION_FAILED = await utils.$SIG_VALIDATION_FAILED(); @@ -167,11 +169,19 @@ describe('ERC4337Utils', function () { describe('hash', function () { it('returns the operation hash with specified entrypoint and chainId', async function () { const userOp = new UserOperation({ sender: this.sender, nonce: 1 }); - const chainId = 0xdeadbeef; + const chainId = await ethers.provider.getNetwork().then(({ chainId }) => chainId); + const otherChainId = 0xdeadbeef; + // check that helper matches entrypoint logic + expect(this.entrypoint.getUserOpHash(userOp.packed)).to.eventually.equal(userOp.hash(this.entrypoint, chainId)); + + // check library against helper expect(this.utils.$hash(userOp.packed, this.entrypoint, chainId)).to.eventually.equal( userOp.hash(this.entrypoint, chainId), ); + expect(this.utils.$hash(userOp.packed, this.entrypoint, otherChainId)).to.eventually.equal( + userOp.hash(this.entrypoint, otherChainId), + ); }); }); diff --git a/test/account/utils/draft-ERC7579Utils.t.sol b/test/account/utils/draft-ERC7579Utils.t.sol new file mode 100644 index 00000000000..fdd4edf5958 --- /dev/null +++ b/test/account/utils/draft-ERC7579Utils.t.sol @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +// Parts of this test file are adapted from Adam Egyed (@adamegyed) proof of concept available at: +// https://github.com/adamegyed/erc7579-execute-vulnerability/tree/4589a30ff139e143d6c57183ac62b5c029217a90 +// +// solhint-disable no-console + +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; +import {PackedUserOperation, IAccount, IEntryPoint} from "@openzeppelin/contracts/interfaces/draft-IERC4337.sol"; +import {ERC4337Utils} from "@openzeppelin/contracts/account/utils/draft-ERC4337Utils.sol"; +import {ERC7579Utils, Mode, CallType, ExecType, ModeSelector, ModePayload, Execution} from "@openzeppelin/contracts/account/utils/draft-ERC7579Utils.sol"; +import {Test, Vm, console} from "forge-std/Test.sol"; + +contract SampleAccount is IAccount, Ownable { + using ECDSA for *; + using MessageHashUtils for *; + using ERC4337Utils for *; + using ERC7579Utils for *; + + IEntryPoint internal constant ENTRY_POINT = IEntryPoint(payable(0x0000000071727De22E5E9d8BAf0edAc6f37da032)); + + event Log(bool duringValidation, Execution[] calls); + + error UnsupportedCallType(CallType callType); + + constructor(address initialOwner) Ownable(initialOwner) {} + + function validateUserOp( + PackedUserOperation calldata userOp, + bytes32 userOpHash, + uint256 missingAccountFunds + ) external override returns (uint256 validationData) { + require(msg.sender == address(ENTRY_POINT), "only from EP"); + // Check signature + if (userOpHash.toEthSignedMessageHash().recover(userOp.signature) != owner()) { + revert OwnableUnauthorizedAccount(_msgSender()); + } + + // If this is an execute call with a batch operation, log the call details from the calldata + if (bytes4(userOp.callData[0x00:0x04]) == this.execute.selector) { + (CallType callType, , , ) = Mode.wrap(bytes32(userOp.callData[0x04:0x24])).decodeMode(); + + if (callType == ERC7579Utils.CALLTYPE_BATCH) { + // Remove the selector + bytes calldata params = userOp.callData[0x04:]; + + // Use the same vulnerable assignment technique here, but assert afterwards that the checks aren't + // broken here by comparing to the result of `abi.decode(...)`. + bytes calldata executionCalldata; + assembly ("memory-safe") { + let dataptr := add(params.offset, calldataload(add(params.offset, 0x20))) + executionCalldata.offset := add(dataptr, 32) + executionCalldata.length := calldataload(dataptr) + } + // Check that this decoding step is done correctly. + (, bytes memory executionCalldataMemory) = abi.decode(params, (bytes32, bytes)); + + require( + keccak256(executionCalldata) == keccak256(executionCalldataMemory), + "decoding during validation failed" + ); + // Now, we know that we have `bytes calldata executionCalldata` as would be decoded by the solidity + // builtin decoder for the `execute` function. + + // This is where the vulnerability from ExecutionLib results in a different result between validation + // andexecution. + + emit Log(true, executionCalldata.decodeBatch()); + } + } + + if (missingAccountFunds > 0) { + (bool success, ) = payable(msg.sender).call{value: missingAccountFunds}(""); + success; // Silence warning. The entrypoint should validate the result. + } + + return ERC4337Utils.SIG_VALIDATION_SUCCESS; + } + + function execute(Mode mode, bytes calldata executionCalldata) external payable { + require(msg.sender == address(this) || msg.sender == address(ENTRY_POINT), "not auth"); + + (CallType callType, ExecType execType, , ) = mode.decodeMode(); + + // check if calltype is batch or single + if (callType == ERC7579Utils.CALLTYPE_SINGLE) { + executionCalldata.execSingle(execType); + } else if (callType == ERC7579Utils.CALLTYPE_BATCH) { + executionCalldata.execBatch(execType); + + emit Log(false, executionCalldata.decodeBatch()); + } else if (callType == ERC7579Utils.CALLTYPE_DELEGATECALL) { + executionCalldata.execDelegateCall(execType); + } else { + revert UnsupportedCallType(callType); + } + } +} + +contract ERC7579UtilsTest is Test { + using MessageHashUtils for *; + using ERC4337Utils for *; + using ERC7579Utils for *; + + IEntryPoint private constant ENTRYPOINT = IEntryPoint(payable(0x0000000071727De22E5E9d8BAf0edAc6f37da032)); + address private _owner; + uint256 private _ownerKey; + address private _account; + address private _beneficiary; + address private _recipient1; + address private _recipient2; + + constructor() { + vm.etch(0x0000000071727De22E5E9d8BAf0edAc6f37da032, vm.readFileBinary("test/bin/EntryPoint070.bytecode")); + vm.etch(0xEFC2c1444eBCC4Db75e7613d20C6a62fF67A167C, vm.readFileBinary("test/bin/SenderCreator070.bytecode")); + + // signing key + (_owner, _ownerKey) = makeAddrAndKey("owner"); + + // ERC-4337 account + _account = address(new SampleAccount(_owner)); + vm.deal(_account, 1 ether); + + // other + _beneficiary = makeAddr("beneficiary"); + _recipient1 = makeAddr("recipient1"); + _recipient2 = makeAddr("recipient2"); + } + + function testExecuteBatchDecodeCorrectly() public { + Execution[] memory calls = new Execution[](2); + calls[0] = Execution({target: _recipient1, value: 1 wei, callData: ""}); + calls[1] = Execution({target: _recipient2, value: 1 wei, callData: ""}); + + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); + userOps[0] = PackedUserOperation({ + sender: _account, + nonce: 0, + initCode: "", + callData: abi.encodeCall( + SampleAccount.execute, + ( + ERC7579Utils.encodeMode( + ERC7579Utils.CALLTYPE_BATCH, + ERC7579Utils.EXECTYPE_DEFAULT, + ModeSelector.wrap(0x00), + ModePayload.wrap(0x00) + ), + ERC7579Utils.encodeBatch(calls) + ) + ), + accountGasLimits: _packGas(500_000, 500_000), + preVerificationGas: 0, + gasFees: _packGas(1, 1), + paymasterAndData: "", + signature: "" + }); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign( + _ownerKey, + this.hashUserOperation(userOps[0]).toEthSignedMessageHash() + ); + userOps[0].signature = abi.encodePacked(r, s, v); + + vm.recordLogs(); + ENTRYPOINT.handleOps(userOps, payable(_beneficiary)); + + assertEq(_recipient1.balance, 1 wei); + assertEq(_recipient2.balance, 1 wei); + + _collectAndPrintLogs(false); + } + + function testExecuteBatchDecodeEmpty() public { + bytes memory fakeCalls = abi.encodePacked( + uint256(1), // Length of execution[] + uint256(0x20), // offset + uint256(uint160(_recipient1)), // target + uint256(1), // value: 1 wei + uint256(0x60), // offset of data + uint256(0) // length of + ); + + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); + userOps[0] = PackedUserOperation({ + sender: _account, + nonce: 0, + initCode: "", + callData: abi.encodeCall( + SampleAccount.execute, + ( + ERC7579Utils.encodeMode( + ERC7579Utils.CALLTYPE_BATCH, + ERC7579Utils.EXECTYPE_DEFAULT, + ModeSelector.wrap(0x00), + ModePayload.wrap(0x00) + ), + abi.encodePacked( + uint256(0x70) // fake offset pointing to paymasterAndData + ) + ) + ), + accountGasLimits: _packGas(500_000, 500_000), + preVerificationGas: 0, + gasFees: _packGas(1, 1), + paymasterAndData: abi.encodePacked(address(0), fakeCalls), + signature: "" + }); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign( + _ownerKey, + this.hashUserOperation(userOps[0]).toEthSignedMessageHash() + ); + userOps[0].signature = abi.encodePacked(r, s, v); + + vm.expectRevert( + abi.encodeWithSelector( + IEntryPoint.FailedOpWithRevert.selector, + 0, + "AA23 reverted", + abi.encodeWithSelector(ERC7579Utils.ERC7579DecodingError.selector) + ) + ); + ENTRYPOINT.handleOps(userOps, payable(_beneficiary)); + + _collectAndPrintLogs(false); + } + + function testExecuteBatchDecodeDifferent() public { + bytes memory execCallData = abi.encodePacked( + uint256(0x20), // offset pointing to the next segment + uint256(5), // Length of execution[] + uint256(0), // offset of calls[0], and target (!!) + uint256(0x20), // offset of calls[1], and value (!!) + uint256(0), // offset of calls[2], and rel offset of data (!!) + uint256(0) // offset of calls[3]. + // There is one more to read by the array length, but it's not present here. This will be + // paymasterAndData.length during validation, pointing to an all-zero call. + // During execution, the offset will be 0, pointing to a call with value. + ); + + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); + userOps[0] = PackedUserOperation({ + sender: _account, + nonce: 0, + initCode: "", + callData: abi.encodePacked( + SampleAccount.execute.selector, + ERC7579Utils.encodeMode( + ERC7579Utils.CALLTYPE_BATCH, + ERC7579Utils.EXECTYPE_DEFAULT, + ModeSelector.wrap(0x00), + ModePayload.wrap(0x00) + ), + uint256(0x5c), // offset pointing to the next segment + uint224(type(uint224).max), // Padding to align the `bytes` types + // type(uint256).max, // unknown padding + uint256(execCallData.length), // Length of the data + execCallData + ), + accountGasLimits: _packGas(500_000, 500_000), + preVerificationGas: 0, + gasFees: _packGas(1, 1), + paymasterAndData: abi.encodePacked(uint256(0), uint256(0)), // padding length to create an offset + signature: "" + }); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign( + _ownerKey, + this.hashUserOperation(userOps[0]).toEthSignedMessageHash() + ); + userOps[0].signature = abi.encodePacked(r, s, v); + + vm.expectRevert( + abi.encodeWithSelector( + IEntryPoint.FailedOpWithRevert.selector, + 0, + "AA23 reverted", + abi.encodeWithSelector(ERC7579Utils.ERC7579DecodingError.selector) + ) + ); + ENTRYPOINT.handleOps(userOps, payable(_beneficiary)); + + _collectAndPrintLogs(true); + } + + function testDecodeBatch() public { + // BAD: buffer empty + vm.expectRevert(ERC7579Utils.ERC7579DecodingError.selector); + this.callDecodeBatch(""); + + // BAD: buffer too short + vm.expectRevert(ERC7579Utils.ERC7579DecodingError.selector); + this.callDecodeBatch(abi.encodePacked(uint128(0))); + + // GOOD + this.callDecodeBatch(abi.encode(0)); + // Note: Solidity also supports this even though it's odd. Offset 0 means array is at the same location, which + // is interpreted as an array of length 0, which doesn't require any more data + // solhint-disable-next-line var-name-mixedcase + uint256[] memory _1 = abi.decode(abi.encode(0), (uint256[])); + _1; + + // BAD: offset is out of bounds + vm.expectRevert(ERC7579Utils.ERC7579DecodingError.selector); + this.callDecodeBatch(abi.encode(1)); + + // GOOD + this.callDecodeBatch(abi.encode(32, 0)); + + // BAD: reported array length extends beyond bounds + vm.expectRevert(ERC7579Utils.ERC7579DecodingError.selector); + this.callDecodeBatch(abi.encode(32, 1)); + + // GOOD + this.callDecodeBatch(abi.encode(32, 1, 0)); + + // GOOD + // + // 0000000000000000000000000000000000000000000000000000000000000020 (32) offset + // 0000000000000000000000000000000000000000000000000000000000000001 ( 1) array length + // 0000000000000000000000000000000000000000000000000000000000000020 (32) element 0 offset + // 000000000000000000000000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (recipient) target for element #0 + // 000000000000000000000000000000000000000000000000000000000000002a (42) value for element #0 + // 0000000000000000000000000000000000000000000000000000000000000060 (96) offset to calldata for element #0 + // 000000000000000000000000000000000000000000000000000000000000000c (12) length of the calldata for element #0 + // 48656c6c6f20576f726c64210000000000000000000000000000000000000000 (..) buffer for the calldata for element #0 + assertEq( + bytes("Hello World!"), + this.callDecodeBatchAndGetFirstBytes( + abi.encode(32, 1, 32, _recipient1, 42, 96, 12, bytes12("Hello World!")) + ) + ); + + // This is invalid, the first element of the array points is out of bounds + // but we allow it past initial validation, because solidity will validate later when the bytes field is accessed + // + // 0000000000000000000000000000000000000000000000000000000000000020 (32) offset + // 0000000000000000000000000000000000000000000000000000000000000001 ( 1) array length + // 0000000000000000000000000000000000000000000000000000000000000020 (32) element 0 offset + // + bytes memory invalid = abi.encode(32, 1, 32); + this.callDecodeBatch(invalid); + vm.expectRevert(); + this.callDecodeBatchAndGetFirst(invalid); + + // this is invalid: the bytes field of the first element of the array is out of bounds + // but we allow it past initial validation, because solidity will validate later when the bytes field is accessed + // + // 0000000000000000000000000000000000000000000000000000000000000020 (32) offset + // 0000000000000000000000000000000000000000000000000000000000000001 ( 1) array length + // 0000000000000000000000000000000000000000000000000000000000000020 (32) element 0 offset + // 000000000000000000000000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (recipient) target for element #0 + // 000000000000000000000000000000000000000000000000000000000000002a (42) value for element #0 + // 0000000000000000000000000000000000000000000000000000000000000060 (96) offset to calldata for element #0 + // + bytes memory invalidDeeply = abi.encode(32, 1, 32, _recipient1, 42, 96); + this.callDecodeBatch(invalidDeeply); + // Note that this is ok because we don't return the value. Returning it would introduce a check that would fails. + this.callDecodeBatchAndGetFirst(invalidDeeply); + vm.expectRevert(); + this.callDecodeBatchAndGetFirstBytes(invalidDeeply); + } + + function callDecodeBatch(bytes calldata executionCalldata) public pure { + ERC7579Utils.decodeBatch(executionCalldata); + } + + function callDecodeBatchAndGetFirst(bytes calldata executionCalldata) public pure { + ERC7579Utils.decodeBatch(executionCalldata)[0]; + } + + function callDecodeBatchAndGetFirstBytes(bytes calldata executionCalldata) public pure returns (bytes calldata) { + return ERC7579Utils.decodeBatch(executionCalldata)[0].callData; + } + + function hashUserOperation(PackedUserOperation calldata useroperation) public view returns (bytes32) { + return useroperation.hash(address(ENTRYPOINT), block.chainid); + } + + function _collectAndPrintLogs(bool includeTotalValue) internal { + Vm.Log[] memory logs = vm.getRecordedLogs(); + for (uint256 i = 0; i < logs.length; i++) { + if (logs[i].emitter == _account) { + _printDecodedCalls(logs[i].data, includeTotalValue); + } + } + } + + function _printDecodedCalls(bytes memory logData, bool includeTotalValue) internal pure { + (bool duringValidation, Execution[] memory calls) = abi.decode(logData, (bool, Execution[])); + + console.log( + string.concat( + "Batch execute contents, as read during ", + duringValidation ? "validation" : "execution", + ": " + ) + ); + console.log(" Execution[] length: %s", calls.length); + + uint256 totalValue = 0; + for (uint256 i = 0; i < calls.length; ++i) { + console.log(string.concat(" calls[", vm.toString(i), "].target = ", vm.toString(calls[i].target))); + console.log(string.concat(" calls[", vm.toString(i), "].value = ", vm.toString(calls[i].value))); + console.log(string.concat(" calls[", vm.toString(i), "].data = ", vm.toString(calls[i].callData))); + totalValue += calls[i].value; + } + + if (includeTotalValue) { + console.log(" Total value: %s", totalValue); + } + } + + function _packGas(uint256 upper, uint256 lower) internal pure returns (bytes32) { + return bytes32(uint256((upper << 128) | uint128(lower))); + } +} diff --git a/test/account/utils/draft-ERC7579Utils.test.js b/test/account/utils/draft-ERC7579Utils.test.js index e72b6698d60..7419c667b69 100644 --- a/test/account/utils/draft-ERC7579Utils.test.js +++ b/test/account/utils/draft-ERC7579Utils.test.js @@ -34,7 +34,7 @@ describe('ERC7579Utils', function () { const value = 0x012; const data = encodeSingle(this.target, value, this.target.interface.encodeFunctionData('mockFunction')); - await expect(this.utils.$execSingle(EXEC_TYPE_DEFAULT, data)).to.emit(this.target, 'MockFunctionCalled'); + await expect(this.utils.$execSingle(data, EXEC_TYPE_DEFAULT)).to.emit(this.target, 'MockFunctionCalled'); expect(ethers.provider.getBalance(this.target)).to.eventually.equal(value); }); @@ -47,7 +47,7 @@ describe('ERC7579Utils', function () { this.target.interface.encodeFunctionData('mockFunctionWithArgs', [42, '0x1234']), ); - await expect(this.utils.$execSingle(EXEC_TYPE_DEFAULT, data)) + await expect(this.utils.$execSingle(data, EXEC_TYPE_DEFAULT)) .to.emit(this.target, 'MockFunctionCalledWithArgs') .withArgs(42, '0x1234'); @@ -62,7 +62,7 @@ describe('ERC7579Utils', function () { this.target.interface.encodeFunctionData('mockFunctionRevertsReason'), ); - await expect(this.utils.$execSingle(EXEC_TYPE_DEFAULT, data)).to.be.revertedWith('CallReceiverMock: reverting'); + await expect(this.utils.$execSingle(data, EXEC_TYPE_DEFAULT)).to.be.revertedWith('CallReceiverMock: reverting'); }); it('emits ERC7579TryExecuteFail event when target reverts in try ExecType', async function () { @@ -73,7 +73,7 @@ describe('ERC7579Utils', function () { this.target.interface.encodeFunctionData('mockFunctionRevertsReason'), ); - await expect(this.utils.$execSingle(EXEC_TYPE_TRY, data)) + await expect(this.utils.$execSingle(data, EXEC_TYPE_TRY)) .to.emit(this.utils, 'ERC7579TryExecuteFail') .withArgs( CALL_TYPE_CALL, @@ -88,7 +88,7 @@ describe('ERC7579Utils', function () { const value = 0x012; const data = encodeSingle(this.target, value, this.target.interface.encodeFunctionData('mockFunction')); - await expect(this.utils.$execSingle('0x03', data)) + await expect(this.utils.$execSingle(data, '0x03')) .to.be.revertedWithCustomError(this.utils, 'ERC7579UnsupportedExecType') .withArgs('0x03'); }); @@ -103,7 +103,7 @@ describe('ERC7579Utils', function () { [this.anotherTarget, value2, this.anotherTarget.interface.encodeFunctionData('mockFunction')], ); - await expect(this.utils.$execBatch(EXEC_TYPE_DEFAULT, data)) + await expect(this.utils.$execBatch(data, EXEC_TYPE_DEFAULT)) .to.emit(this.target, 'MockFunctionCalled') .to.emit(this.anotherTarget, 'MockFunctionCalled'); @@ -123,7 +123,7 @@ describe('ERC7579Utils', function () { ], ); - await expect(this.utils.$execBatch(EXEC_TYPE_DEFAULT, data)) + await expect(this.utils.$execBatch(data, EXEC_TYPE_DEFAULT)) .to.emit(this.target, 'MockFunctionCalledWithArgs') .to.emit(this.anotherTarget, 'MockFunctionCalledWithArgs'); @@ -139,7 +139,7 @@ describe('ERC7579Utils', function () { [this.anotherTarget, value2, this.anotherTarget.interface.encodeFunctionData('mockFunctionRevertsReason')], ); - await expect(this.utils.$execBatch(EXEC_TYPE_DEFAULT, data)).to.be.revertedWith('CallReceiverMock: reverting'); + await expect(this.utils.$execBatch(data, EXEC_TYPE_DEFAULT)).to.be.revertedWith('CallReceiverMock: reverting'); }); it('emits ERC7579TryExecuteFail event when any target reverts in try ExecType', async function () { @@ -150,7 +150,7 @@ describe('ERC7579Utils', function () { [this.anotherTarget, value2, this.anotherTarget.interface.encodeFunctionData('mockFunctionRevertsReason')], ); - await expect(this.utils.$execBatch(EXEC_TYPE_TRY, data)) + await expect(this.utils.$execBatch(data, EXEC_TYPE_TRY)) .to.emit(this.utils, 'ERC7579TryExecuteFail') .withArgs( CALL_TYPE_BATCH, @@ -173,7 +173,7 @@ describe('ERC7579Utils', function () { [this.anotherTarget, value2, this.anotherTarget.interface.encodeFunctionData('mockFunction')], ); - await expect(this.utils.$execBatch('0x03', data)) + await expect(this.utils.$execBatch(data, '0x03')) .to.be.revertedWithCustomError(this.utils, 'ERC7579UnsupportedExecType') .withArgs('0x03'); }); @@ -189,20 +189,20 @@ describe('ERC7579Utils', function () { ); expect(ethers.provider.getStorage(this.utils.target, slot)).to.eventually.equal(ethers.ZeroHash); - await this.utils.$execDelegateCall(EXEC_TYPE_DEFAULT, data); + await this.utils.$execDelegateCall(data, EXEC_TYPE_DEFAULT); expect(ethers.provider.getStorage(this.utils.target, slot)).to.eventually.equal(value); }); it('reverts when target reverts in default ExecType', async function () { const data = encodeDelegate(this.target, this.target.interface.encodeFunctionData('mockFunctionRevertsReason')); - await expect(this.utils.$execDelegateCall(EXEC_TYPE_DEFAULT, data)).to.be.revertedWith( + await expect(this.utils.$execDelegateCall(data, EXEC_TYPE_DEFAULT)).to.be.revertedWith( 'CallReceiverMock: reverting', ); }); it('emits ERC7579TryExecuteFail event when target reverts in try ExecType', async function () { const data = encodeDelegate(this.target, this.target.interface.encodeFunctionData('mockFunctionRevertsReason')); - await expect(this.utils.$execDelegateCall(EXEC_TYPE_TRY, data)) + await expect(this.utils.$execDelegateCall(data, EXEC_TYPE_TRY)) .to.emit(this.utils, 'ERC7579TryExecuteFail') .withArgs( CALL_TYPE_CALL, @@ -215,7 +215,7 @@ describe('ERC7579Utils', function () { it('reverts with an invalid exec type', async function () { const data = encodeDelegate(this.target, this.target.interface.encodeFunctionData('mockFunction')); - await expect(this.utils.$execDelegateCall('0x03', data)) + await expect(this.utils.$execDelegateCall(data, '0x03')) .to.be.revertedWithCustomError(this.utils, 'ERC7579UnsupportedExecType') .withArgs('0x03'); }); diff --git a/test/bin/EntryPoint070.abi b/test/bin/EntryPoint070.abi new file mode 100644 index 00000000000..3f3b1d6e5ff --- /dev/null +++ b/test/bin/EntryPoint070.abi @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"bool","name":"success","type":"bool"},{"internalType":"bytes","name":"ret","type":"bytes"}],"name":"DelegateAndRevert","type":"error"},{"inputs":[{"internalType":"uint256","name":"opIndex","type":"uint256"},{"internalType":"string","name":"reason","type":"string"}],"name":"FailedOp","type":"error"},{"inputs":[{"internalType":"uint256","name":"opIndex","type":"uint256"},{"internalType":"string","name":"reason","type":"string"},{"internalType":"bytes","name":"inner","type":"bytes"}],"name":"FailedOpWithRevert","type":"error"},{"inputs":[{"internalType":"bytes","name":"returnData","type":"bytes"}],"name":"PostOpReverted","type":"error"},{"inputs":[],"name":"ReentrancyGuardReentrantCall","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"SenderAddressResult","type":"error"},{"inputs":[{"internalType":"address","name":"aggregator","type":"address"}],"name":"SignatureValidationFailed","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"userOpHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"address","name":"factory","type":"address"},{"indexed":false,"internalType":"address","name":"paymaster","type":"address"}],"name":"AccountDeployed","type":"event"},{"anonymous":false,"inputs":[],"name":"BeforeExecution","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"totalDeposit","type":"uint256"}],"name":"Deposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"userOpHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"nonce","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"revertReason","type":"bytes"}],"name":"PostOpRevertReason","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"aggregator","type":"address"}],"name":"SignatureAggregatorChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"totalStaked","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"unstakeDelaySec","type":"uint256"}],"name":"StakeLocked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"withdrawTime","type":"uint256"}],"name":"StakeUnlocked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"address","name":"withdrawAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"StakeWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"userOpHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"paymaster","type":"address"},{"indexed":false,"internalType":"uint256","name":"nonce","type":"uint256"},{"indexed":false,"internalType":"bool","name":"success","type":"bool"},{"indexed":false,"internalType":"uint256","name":"actualGasCost","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"actualGasUsed","type":"uint256"}],"name":"UserOperationEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"userOpHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"nonce","type":"uint256"}],"name":"UserOperationPrefundTooLow","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"userOpHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"nonce","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"revertReason","type":"bytes"}],"name":"UserOperationRevertReason","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"address","name":"withdrawAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Withdrawn","type":"event"},{"inputs":[{"internalType":"uint32","name":"unstakeDelaySec","type":"uint32"}],"name":"addStake","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"target","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"delegateAndRevert","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"depositTo","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"deposits","outputs":[{"internalType":"uint256","name":"deposit","type":"uint256"},{"internalType":"bool","name":"staked","type":"bool"},{"internalType":"uint112","name":"stake","type":"uint112"},{"internalType":"uint32","name":"unstakeDelaySec","type":"uint32"},{"internalType":"uint48","name":"withdrawTime","type":"uint48"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"getDepositInfo","outputs":[{"components":[{"internalType":"uint256","name":"deposit","type":"uint256"},{"internalType":"bool","name":"staked","type":"bool"},{"internalType":"uint112","name":"stake","type":"uint112"},{"internalType":"uint32","name":"unstakeDelaySec","type":"uint32"},{"internalType":"uint48","name":"withdrawTime","type":"uint48"}],"internalType":"struct IStakeManager.DepositInfo","name":"info","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint192","name":"key","type":"uint192"}],"name":"getNonce","outputs":[{"internalType":"uint256","name":"nonce","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"initCode","type":"bytes"}],"name":"getSenderAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"bytes","name":"initCode","type":"bytes"},{"internalType":"bytes","name":"callData","type":"bytes"},{"internalType":"bytes32","name":"accountGasLimits","type":"bytes32"},{"internalType":"uint256","name":"preVerificationGas","type":"uint256"},{"internalType":"bytes32","name":"gasFees","type":"bytes32"},{"internalType":"bytes","name":"paymasterAndData","type":"bytes"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct PackedUserOperation","name":"userOp","type":"tuple"}],"name":"getUserOpHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"components":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"bytes","name":"initCode","type":"bytes"},{"internalType":"bytes","name":"callData","type":"bytes"},{"internalType":"bytes32","name":"accountGasLimits","type":"bytes32"},{"internalType":"uint256","name":"preVerificationGas","type":"uint256"},{"internalType":"bytes32","name":"gasFees","type":"bytes32"},{"internalType":"bytes","name":"paymasterAndData","type":"bytes"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct PackedUserOperation[]","name":"userOps","type":"tuple[]"},{"internalType":"contract IAggregator","name":"aggregator","type":"address"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct IEntryPoint.UserOpsPerAggregator[]","name":"opsPerAggregator","type":"tuple[]"},{"internalType":"address payable","name":"beneficiary","type":"address"}],"name":"handleAggregatedOps","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"bytes","name":"initCode","type":"bytes"},{"internalType":"bytes","name":"callData","type":"bytes"},{"internalType":"bytes32","name":"accountGasLimits","type":"bytes32"},{"internalType":"uint256","name":"preVerificationGas","type":"uint256"},{"internalType":"bytes32","name":"gasFees","type":"bytes32"},{"internalType":"bytes","name":"paymasterAndData","type":"bytes"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct PackedUserOperation[]","name":"ops","type":"tuple[]"},{"internalType":"address payable","name":"beneficiary","type":"address"}],"name":"handleOps","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint192","name":"key","type":"uint192"}],"name":"incrementNonce","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"callData","type":"bytes"},{"components":[{"components":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"verificationGasLimit","type":"uint256"},{"internalType":"uint256","name":"callGasLimit","type":"uint256"},{"internalType":"uint256","name":"paymasterVerificationGasLimit","type":"uint256"},{"internalType":"uint256","name":"paymasterPostOpGasLimit","type":"uint256"},{"internalType":"uint256","name":"preVerificationGas","type":"uint256"},{"internalType":"address","name":"paymaster","type":"address"},{"internalType":"uint256","name":"maxFeePerGas","type":"uint256"},{"internalType":"uint256","name":"maxPriorityFeePerGas","type":"uint256"}],"internalType":"struct EntryPoint.MemoryUserOp","name":"mUserOp","type":"tuple"},{"internalType":"bytes32","name":"userOpHash","type":"bytes32"},{"internalType":"uint256","name":"prefund","type":"uint256"},{"internalType":"uint256","name":"contextOffset","type":"uint256"},{"internalType":"uint256","name":"preOpGas","type":"uint256"}],"internalType":"struct EntryPoint.UserOpInfo","name":"opInfo","type":"tuple"},{"internalType":"bytes","name":"context","type":"bytes"}],"name":"innerHandleOp","outputs":[{"internalType":"uint256","name":"actualGasCost","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint192","name":"","type":"uint192"}],"name":"nonceSequenceNumber","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unlockStake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address payable","name":"withdrawAddress","type":"address"}],"name":"withdrawStake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address payable","name":"withdrawAddress","type":"address"},{"internalType":"uint256","name":"withdrawAmount","type":"uint256"}],"name":"withdrawTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file diff --git a/test/bin/EntryPoint070.bytecode b/test/bin/EntryPoint070.bytecode new file mode 100644 index 0000000000000000000000000000000000000000..fce261af5dc93a09c7ab25cd89463d1b23c5a223 GIT binary patch literal 16035 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 literal 0 HcmV?d00001 diff --git a/test/bin/SenderCreator070.abi b/test/bin/SenderCreator070.abi new file mode 100644 index 00000000000..0a1f0e4fbac --- /dev/null +++ b/test/bin/SenderCreator070.abi @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"bytes","name":"initCode","type":"bytes"}],"name":"createSender","outputs":[{"internalType":"address","name":"sender","type":"address"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/test/bin/SenderCreator070.bytecode b/test/bin/SenderCreator070.bytecode new file mode 100644 index 0000000000000000000000000000000000000000..8344c20281957f0aff5dd71a3650f5553f520704 GIT binary patch literal 451 zcmYdjNMJD&5KUy@4^Lod_!|u*OcNf+B!}}!nTaGas6s?d5*fR~6BO$I;|G7t7=^$p zSxhIU!<0^FY!OUkYzsF_P;O-u5(P1v6Id8cn;L<_O+aClrgla~FuNf^1WXgM=S?Hy zqy~_wq2Yv-K1i@{OyF*16k`-?W}ML6Bru^#P#mOjQcHqLOca_W~?3eV~_71&aW^K+7&892lli~-q#;|Bl$ literal 0 HcmV?d00001 diff --git a/test/helpers/erc4337-entrypoint.js b/test/helpers/erc4337-entrypoint.js new file mode 100644 index 00000000000..aba49f4c458 --- /dev/null +++ b/test/helpers/erc4337-entrypoint.js @@ -0,0 +1,31 @@ +const { ethers } = require('hardhat'); +const { setCode } = require('@nomicfoundation/hardhat-network-helpers'); +const fs = require('fs'); +const path = require('path'); + +const INSTANCES = { + entrypoint: { + address: '0x0000000071727De22E5E9d8BAf0edAc6f37da032', + abi: JSON.parse(fs.readFileSync(path.resolve(__dirname, '../bin/EntryPoint070.abi'), 'utf-8')), + bytecode: fs.readFileSync(path.resolve(__dirname, '../bin/EntryPoint070.bytecode'), 'hex'), + }, + sendercreator: { + address: '0xEFC2c1444eBCC4Db75e7613d20C6a62fF67A167C', + abi: JSON.parse(fs.readFileSync(path.resolve(__dirname, '../bin/SenderCreator070.abi'), 'utf-8')), + bytecode: fs.readFileSync(path.resolve(__dirname, '../bin/SenderCreator070.bytecode'), 'hex'), + }, +}; + +function deployEntrypoint() { + return Promise.all( + Object.entries(INSTANCES).map(([name, { address, abi, bytecode }]) => + setCode(address, '0x' + bytecode.replace(/0x/, '')) + .then(() => ethers.getContractAt(abi, address)) + .then(instance => ({ [name]: instance })), + ), + ).then(namedInstances => Object.assign(...namedInstances)); +} + +module.exports = { + deployEntrypoint, +};