From 3fa7f942c135cefe06035d5c2b5d8bd0cdab2ad8 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 12 Dec 2024 21:53:52 +0100 Subject: [PATCH] Add checks to ERC7579Utils.decodeBatch (#5353) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto GarcĂ­a Co-authored-by: Francisco Giordano Signed-off-by: Hadrien Croubois --- .../account/utils/draft-ERC7579Utils.sol | 53 ++- test/account/utils/draft-ERC7579Utils.t.sol | 421 ++++++++++++++++++ test/account/utils/draft-ERC7579Utils.test.js | 28 +- 3 files changed, 477 insertions(+), 25 deletions(-) create mode 100644 test/account/utils/draft-ERC7579Utils.t.sol diff --git a/contracts/account/utils/draft-ERC7579Utils.sol b/contracts/account/utils/draft-ERC7579Utils.sol index c28b514d573..52ccdb2c9f6 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 arrayLengthPointer = uint256(bytes32(executionCalldata[0:32])); + + // The array length (at arrayLengthPointer) 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 (arrayLengthPointer > bufferLength - 32) revert ERC7579DecodingError(); + + // Get the array length. arrayLengthPointer + 32 is bounded by bufferLength so it does not overflow. + uint256 arrayLength = uint256(bytes32(executionCalldata[arrayLengthPointer:arrayLengthPointer + 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 arrayLengthPointer + 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 - arrayLengthPointer - 32 < arrayLength * 32) + revert ERC7579DecodingError(); + + assembly ("memory-safe") { + executionBatch.offset := add(add(executionCalldata.offset, arrayLengthPointer), 32) + executionBatch.length := arrayLength + } } } 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'); });