From efdf3cbfec798065c634bef5e7c12304a08ec01f Mon Sep 17 00:00:00 2001 From: Tanishk Goyal Date: Wed, 3 Sep 2025 19:32:04 -0400 Subject: [PATCH 1/7] feat: add new commonTo execution mode, for calldata optimization --- src/accounts/ERC7821.sol | 84 ++++++++++++++++++++++++++++ test/ERC7821.t.sol | 95 +++++++++++++++++++++++++++++++- test/utils/mocks/MockERC7821.sol | 18 ++++++ 3 files changed, 196 insertions(+), 1 deletion(-) diff --git a/src/accounts/ERC7821.sol b/src/accounts/ERC7821.sol index 2f0a22b040..2c441e431f 100644 --- a/src/accounts/ERC7821.sol +++ b/src/accounts/ERC7821.sol @@ -25,6 +25,11 @@ contract ERC7821 is Receiver { bytes data; // Calldata to send with the call. } + struct CallSansTo { + uint256 value; // Amount of native currency (i.e. Ether) to send. + bytes data; // Calldata to send with the call. + } + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CUSTOM ERRORS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ @@ -73,8 +78,10 @@ contract ERC7821 is Receiver { function execute(bytes32 mode, bytes calldata executionData) public payable virtual { uint256 id = _executionModeId(mode); if (id == 3) return _executeBatchOfBatches(mode, executionData); + if (id == 4) return _executeBatchCommonTo(mode, executionData); Call[] calldata calls; bytes calldata opData; + /// @solidity memory-safe-assembly assembly { if iszero(id) { @@ -126,9 +133,39 @@ contract ERC7821 is Receiver { id := eq(m, 0x01000000000000000000) // 1. id := or(shl(1, eq(m, 0x01000000000078210001)), id) // 2. id := or(mul(3, eq(m, 0x01000000000078210002)), id) // 3. + id := or(mul(4, eq(m, 0x01000000000078210003)), id) // 4. } } + function _executeBatchCommonTo(bytes32 mode, bytes calldata executionData) internal virtual { + // Execution Data: abi.encode(address to, CallSansTo[] calls, bytes opData) + address to; + CallSansTo[] calldata calls; + bytes calldata opData; + + assembly ("memory-safe") { + to := calldataload(executionData.offset) + + let callOffset := + add(executionData.offset, calldataload(add(0x20, executionData.offset))) + calls.offset := add(callOffset, 0x20) + calls.length := calldataload(callOffset) + + // This line is needed to ensure that opdata is valid in all code paths. + // Otherwise the compiler complains. + opData.length := 0 + // If the offset of `executionData` allows for `opData`, and the mode supports it. + if gt(calldataload(add(0x20, executionData.offset)), 0x40) { + let opDataOffset := + add(executionData.offset, calldataload(add(0x40, executionData.offset))) + opData.offset := add(opDataOffset, 0x20) + opData.length := calldataload(opDataOffset) + } + } + + _execute(mode, executionData, to, calls, opData); + } + /// @dev For execution of a batch of batches. function _executeBatchOfBatches(bytes32 mode, bytes calldata executionData) internal virtual { // Replace with `0x0100________78210001...` while preserving optional and reserved fields. @@ -190,6 +227,26 @@ contract ERC7821 is Receiver { revert(); // In your override, replace this with logic to operate on `opData`. } + function _execute( + bytes32 mode, + bytes calldata executionData, + address to, + CallSansTo[] calldata calls, + bytes calldata opData + ) internal virtual { + // Silence compiler warning on unused variables. + mode = mode; + executionData = executionData; + // Very basic auth to only allow this contract to be called by itself. + // Override this function to perform more complex auth with `opData`. + if (opData.length == uint256(0)) { + require(msg.sender == address(this)); + // Remember to return `_execute(calls, extraData)` when you override this function. + return _execute(calls, to, bytes32(0)); + } + revert(); // In your override, replace this with logic to operate on `opData`. + } + /// @dev Executes the calls. /// Reverts and bubbles up error if any call fails. /// `extraData` can be any supplementary data (e.g. a memory pointer, some hash). @@ -204,6 +261,17 @@ contract ERC7821 is Receiver { } } + function _execute(CallSansTo[] calldata calls, address to, bytes32 keyHash) internal virtual { + unchecked { + uint256 i; + if (calls.length == uint256(0)) return; + do { + (uint256 value, bytes calldata data) = _get(calls, i); + _execute(to, value, data, keyHash); + } while (++i != calls.length); + } + } + /// @dev Executes the call. /// Reverts and bubbles up error if any call fails. /// `extraData` can be any supplementary data (e.g. a memory pointer, some hash). @@ -224,6 +292,22 @@ contract ERC7821 is Receiver { } } + function _get(CallSansTo[] calldata calls, uint256 i) + internal + view + virtual + returns (uint256 value, bytes calldata data) + { + /// @solidity memory-safe-assembly + assembly { + let c := add(calls.offset, calldataload(add(calls.offset, shl(5, i)))) + value := calldataload(c) + let o := add(c, calldataload(add(c, 0x20))) + data.offset := add(o, 0x20) + data.length := calldataload(o) + } + } + /// @dev Convenience function for getting `calls[i]`, without bounds checks. function _get(Call[] calldata calls, uint256 i) internal diff --git a/test/ERC7821.t.sol b/test/ERC7821.t.sol index c91de9bfd8..d240a8ace7 100644 --- a/test/ERC7821.t.sol +++ b/test/ERC7821.t.sol @@ -13,7 +13,7 @@ contract ERC7821Test is SoladyTest { address target; bytes32 internal constant _SUPPORTED_MODE = bytes10(0x01000000000078210001); - + bytes32 internal constant _COMMON_TO_MODE = bytes10(0x01000000000078210003); bytes[] internal _bytes; function setUp() public { @@ -54,6 +54,24 @@ contract ERC7821Test is SoladyTest { mbe.execute{value: _totalValue(calls)}(_SUPPORTED_MODE, data); } + function testERC7821CommonToGas() public { + vm.pauseGasMetering(); + vm.deal(address(this), 1 ether); + + ERC7821.CallSansTo[] memory calls = new ERC7821.CallSansTo[](2); + + calls[0].value = 123; + calls[0].data = abi.encodeWithSignature("returnsBytes(bytes)", "hehe"); + + calls[1].value = 789; + calls[1].data = abi.encodeWithSignature("returnsHash(bytes)", "lol"); + + bytes memory data = abi.encode(target, calls); + vm.resumeGasMetering(); + + mbe.execute{value: _totalValue(calls)}(_COMMON_TO_MODE, data); + } + function testERC7821(bytes memory opData) public { vm.deal(address(this), 1 ether); @@ -72,6 +90,23 @@ contract ERC7821Test is SoladyTest { assertEq(mbe.lastOpData(), opData); } + function testERC7821CommonTo(bytes memory opData) public { + vm.deal(address(this), 1 ether); + + ERC7821.CallSansTo[] memory calls = new ERC7821.CallSansTo[](2); + calls[0].value = 123; + calls[0].data = abi.encodeWithSignature("returnsBytes(bytes)", "hehe"); + + calls[1].value = 789; + calls[1].data = abi.encodeWithSignature("returnsHash(bytes)", "lol"); + + mbe.execute{value: _totalValue(calls)}( + _COMMON_TO_MODE, _encodeCommonTo(target, calls, opData) + ); + + assertEq(mbe.lastOpData(), opData); + } + function testERC7821ForRevert() public { ERC7821.Call[] memory calls = new ERC7821.Call[](1); calls[0].to = target; @@ -82,6 +117,15 @@ contract ERC7821Test is SoladyTest { mbe.execute{value: _totalValue(calls)}(_SUPPORTED_MODE, _encode(calls, "")); } + function testERC7821CommonToForRevert() public { + ERC7821.CallSansTo[] memory calls = new ERC7821.CallSansTo[](1); + calls[0].value = 0; + calls[0].data = abi.encodeWithSignature("revertsWithCustomError()"); + + vm.expectRevert(CustomError.selector); + mbe.execute{value: _totalValue(calls)}(_COMMON_TO_MODE, _encodeCommonTo(target, calls, "")); + } + function _encode(ERC7821.Call[] memory calls, bytes memory opData) internal returns (bytes memory) @@ -90,6 +134,14 @@ contract ERC7821Test is SoladyTest { return abi.encode(calls, opData); } + function _encodeCommonTo(address to, ERC7821.CallSansTo[] memory calls, bytes memory opData) + internal + returns (bytes memory) + { + if (_randomChance(2) && opData.length == 0) return abi.encode(to, calls); + return abi.encode(to, calls, opData); + } + struct Payload { bytes data; uint256 mode; @@ -125,6 +177,35 @@ contract ERC7821Test is SoladyTest { } } + function testERC7821CommonTo(bytes32) public { + vm.deal(address(this), 1 ether); + + ERC7821.CallSansTo[] memory calls = new ERC7821.CallSansTo[](_randomUniform() & 3); + Payload[] memory payloads = new Payload[](calls.length); + + for (uint256 i; i < calls.length; ++i) { + calls[i].value = _randomUniform() & 0xff; + bytes memory data = _truncateBytes(_randomBytes(), 0x1ff); + payloads[i].data = data; + if (_randomChance(2)) { + payloads[i].mode = 0; + calls[i].data = abi.encodeWithSignature("returnsBytes(bytes)", data); + } else { + payloads[i].mode = 1; + calls[i].data = abi.encodeWithSignature("returnsHash(bytes)", data); + } + } + + mbe.executeDirect{value: _totalValue(calls)}(calls, target); + + if (calls.length != 0 && _randomChance(32)) { + calls[_randomUniform() % calls.length].data = + abi.encodeWithSignature("revertsWithCustomError()"); + vm.expectRevert(CustomError.selector); + mbe.executeDirect{value: _totalValue(calls)}(calls, target); + } + } + function _totalValue(ERC7821.Call[] memory calls) internal pure returns (uint256 result) { unchecked { for (uint256 i; i < calls.length; ++i) { @@ -133,6 +214,18 @@ contract ERC7821Test is SoladyTest { } } + function _totalValue(ERC7821.CallSansTo[] memory calls) + internal + pure + returns (uint256 result) + { + unchecked { + for (uint256 i; i < calls.length; ++i) { + result += calls[i].value; + } + } + } + function testERC7821ExecuteBatchOfBatches() public { bytes32 mode = bytes32(0x0100000000007821000200000000000000000000000000000000000000000000); bytes[] memory batchBytes = new bytes[](3); diff --git a/test/utils/mocks/MockERC7821.sol b/test/utils/mocks/MockERC7821.sol index 7c8d2285b9..a213f17d6f 100644 --- a/test/utils/mocks/MockERC7821.sol +++ b/test/utils/mocks/MockERC7821.sol @@ -22,6 +22,17 @@ contract MockERC7821 is ERC7821, Brutalizer { _execute(calls, bytes32(0)); } + function _execute( + bytes32, + bytes calldata, + address to, + CallSansTo[] calldata calls, + bytes calldata opData + ) internal virtual override { + lastOpData = opData; + _execute(calls, to, bytes32(0)); + } + function execute(bytes32 mode, bytes calldata executionData) public payable virtual override { if (!isAuthorizedCaller[msg.sender]) revert Unauthorized(); super.execute(mode, executionData); @@ -34,6 +45,13 @@ contract MockERC7821 is ERC7821, Brutalizer { _checkMemory(); } + function executeDirect(CallSansTo[] calldata calls, address to) public payable virtual { + _misalignFreeMemoryPointer(); + _brutalizeMemory(); + _execute(calls, to, bytes32(0)); + _checkMemory(); + } + function setAuthorizedCaller(address target, bool status) public { isAuthorizedCaller[target] = status; } From 902400f3c92bf794e910cd7ccbbcd36d67e57d93 Mon Sep 17 00:00:00 2001 From: Tanishk Goyal Date: Wed, 3 Sep 2025 19:41:39 -0400 Subject: [PATCH 2/7] feat: add address(0) replacement to new execution mode --- src/accounts/ERC7821.sol | 3 +++ test/ERC7821.t.sol | 14 ++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/accounts/ERC7821.sol b/src/accounts/ERC7821.sol index 2c441e431f..0823790aef 100644 --- a/src/accounts/ERC7821.sol +++ b/src/accounts/ERC7821.sol @@ -264,6 +264,9 @@ contract ERC7821 is Receiver { function _execute(CallSansTo[] calldata calls, address to, bytes32 keyHash) internal virtual { unchecked { uint256 i; + if (to == address(0)) { + to = address(this); + } if (calls.length == uint256(0)) return; do { (uint256 value, bytes calldata data) = _get(calls, i); diff --git a/test/ERC7821.t.sol b/test/ERC7821.t.sol index d240a8ace7..ee529d188b 100644 --- a/test/ERC7821.t.sol +++ b/test/ERC7821.t.sol @@ -275,4 +275,18 @@ contract ERC7821Test is SoladyTest { function pushBytes(bytes memory x) public { _bytes.push(x); } + + function testERC7821CommonToWithZeroAddress() public { + // Test that when to=address(0), it gets replaced with address(this) (the MockERC7821 contract) + // We'll call executeDirect which directly calls the internal _execute function + ERC7821.CallSansTo[] memory calls = new ERC7821.CallSansTo[](1); + calls[0].value = 0; + calls[0].data = abi.encodeWithSignature("setAuthorizedCaller(address,bool)", address(0x123), true); + + // This should replace address(0) with address(mbe) and call setAuthorizedCaller on itself + mbe.executeDirect(calls, address(0)); + + // Verify the call succeeded by checking that address(0x123) is now authorized + assertTrue(mbe.isAuthorizedCaller(address(0x123))); + } } From 97bcb7093a9bb0dc8191d7c9a4c8211fee72ad4d Mon Sep 17 00:00:00 2001 From: Tanishk Goyal Date: Wed, 3 Sep 2025 19:42:11 -0400 Subject: [PATCH 3/7] chore: use memory-safe-assembly tag --- src/accounts/ERC7821.sol | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/accounts/ERC7821.sol b/src/accounts/ERC7821.sol index 0823790aef..f027faa882 100644 --- a/src/accounts/ERC7821.sol +++ b/src/accounts/ERC7821.sol @@ -137,13 +137,16 @@ contract ERC7821 is Receiver { } } + /// @dev For execution of a batch of batches with a common `to` address. + /// @dev if to == address(0), it will be replaced with address(this) + /// Execution Data: abi.encode(address to, CallSansTo[] calls, bytes opData) function _executeBatchCommonTo(bytes32 mode, bytes calldata executionData) internal virtual { - // Execution Data: abi.encode(address to, CallSansTo[] calls, bytes opData) address to; CallSansTo[] calldata calls; bytes calldata opData; - assembly ("memory-safe") { + /// @solidity memory-safe-assembly + assembly { to := calldataload(executionData.offset) let callOffset := From 4098fed91a05ad73c3cefa480c655dbc25145c0a Mon Sep 17 00:00:00 2001 From: Tanishk Goyal Date: Wed, 3 Sep 2025 19:44:15 -0400 Subject: [PATCH 4/7] chore: natspec --- src/accounts/ERC7821.sol | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/accounts/ERC7821.sol b/src/accounts/ERC7821.sol index f027faa882..c4999cdf4a 100644 --- a/src/accounts/ERC7821.sol +++ b/src/accounts/ERC7821.sol @@ -60,6 +60,7 @@ contract ERC7821 is Receiver { /// - `0x01000000000000000000...`: Single batch. Does not support optional `opData`. /// - `0x01000000000078210001...`: Single batch. Supports optional `opData`. /// - `0x01000000000078210002...`: Batch of batches. + /// - `0x01000000000078210003...`: Single batch with common `to` address and optional `opData`. /// /// For the "batch of batches" mode, each batch will be recursively passed into /// `execute` internally with mode `0x01000000000078210001...`. @@ -230,6 +231,9 @@ contract ERC7821 is Receiver { revert(); // In your override, replace this with logic to operate on `opData`. } + /// @dev Executes the calls. + /// Reverts and bubbles up error if any call fails. + /// The `mode` and `executionData` are passed along in case there's a need to use them. function _execute( bytes32 mode, bytes calldata executionData, @@ -264,6 +268,9 @@ contract ERC7821 is Receiver { } } + /// @dev Executes the calls. + /// Reverts and bubbles up error if any call fails. + /// `extraData` can be any supplementary data (e.g. a memory pointer, some hash). function _execute(CallSansTo[] calldata calls, address to, bytes32 keyHash) internal virtual { unchecked { uint256 i; @@ -298,6 +305,7 @@ contract ERC7821 is Receiver { } } + /// @dev Convenience function for getting `calls[i]`, without bounds checks. function _get(CallSansTo[] calldata calls, uint256 i) internal view From 00fb8f2f3e756020aa12189a0d1725037a9bc65e Mon Sep 17 00:00:00 2001 From: Tanishk Goyal Date: Wed, 3 Sep 2025 20:01:30 -0400 Subject: [PATCH 5/7] fix: clean to address, before checking if it is 0 --- src/accounts/ERC7821.sol | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/accounts/ERC7821.sol b/src/accounts/ERC7821.sol index c4999cdf4a..4532e416a5 100644 --- a/src/accounts/ERC7821.sol +++ b/src/accounts/ERC7821.sol @@ -274,8 +274,11 @@ contract ERC7821 is Receiver { function _execute(CallSansTo[] calldata calls, address to, bytes32 keyHash) internal virtual { unchecked { uint256 i; - if (to == address(0)) { - to = address(this); + // If `to` is address(0), it will be replaced with address(this) + /// @solidity memory-safe-assembly + assembly { + let t := shr(96, shl(96, to)) + to := or(mul(address(), iszero(t)), t) } if (calls.length == uint256(0)) return; do { From b22a4663eb6ed96ea14e05d1a0325fe748e789ac Mon Sep 17 00:00:00 2001 From: Tanishk Goyal Date: Tue, 30 Sep 2025 18:17:26 +0530 Subject: [PATCH 6/7] feat: replace commonTo mode with calldata optimal mode --- src/accounts/ERC7821.sol | 56 +++++++------------- test/ERC7821.t.sol | 88 ++++++++++++-------------------- test/utils/mocks/MockERC7821.sol | 8 +-- 3 files changed, 55 insertions(+), 97 deletions(-) diff --git a/src/accounts/ERC7821.sol b/src/accounts/ERC7821.sol index 4532e416a5..d508187df2 100644 --- a/src/accounts/ERC7821.sol +++ b/src/accounts/ERC7821.sol @@ -25,11 +25,6 @@ contract ERC7821 is Receiver { bytes data; // Calldata to send with the call. } - struct CallSansTo { - uint256 value; // Amount of native currency (i.e. Ether) to send. - bytes data; // Calldata to send with the call. - } - /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CUSTOM ERRORS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ @@ -79,7 +74,7 @@ contract ERC7821 is Receiver { function execute(bytes32 mode, bytes calldata executionData) public payable virtual { uint256 id = _executionModeId(mode); if (id == 3) return _executeBatchOfBatches(mode, executionData); - if (id == 4) return _executeBatchCommonTo(mode, executionData); + if (id == 4) return _executeCalldataOptimal(mode, executionData); Call[] calldata calls; bytes calldata opData; @@ -140,20 +135,20 @@ contract ERC7821 is Receiver { /// @dev For execution of a batch of batches with a common `to` address. /// @dev if to == address(0), it will be replaced with address(this) - /// Execution Data: abi.encode(address to, CallSansTo[] calls, bytes opData) - function _executeBatchCommonTo(bytes32 mode, bytes calldata executionData) internal virtual { + /// Execution Data: abi.encode(address to, bytes[] datas, bytes opData) + function _executeCalldataOptimal(bytes32 mode, bytes calldata executionData) internal virtual { address to; - CallSansTo[] calldata calls; + bytes[] calldata datas; bytes calldata opData; /// @solidity memory-safe-assembly assembly { to := calldataload(executionData.offset) - let callOffset := + let dataOffset := add(executionData.offset, calldataload(add(0x20, executionData.offset))) - calls.offset := add(callOffset, 0x20) - calls.length := calldataload(callOffset) + datas.offset := add(dataOffset, 0x20) + datas.length := calldataload(dataOffset) // This line is needed to ensure that opdata is valid in all code paths. // Otherwise the compiler complains. @@ -167,7 +162,7 @@ contract ERC7821 is Receiver { } } - _execute(mode, executionData, to, calls, opData); + _execute(mode, executionData, to, datas, opData); } /// @dev For execution of a batch of batches. @@ -238,7 +233,7 @@ contract ERC7821 is Receiver { bytes32 mode, bytes calldata executionData, address to, - CallSansTo[] calldata calls, + bytes[] calldata datas, bytes calldata opData ) internal virtual { // Silence compiler warning on unused variables. @@ -249,7 +244,7 @@ contract ERC7821 is Receiver { if (opData.length == uint256(0)) { require(msg.sender == address(this)); // Remember to return `_execute(calls, extraData)` when you override this function. - return _execute(calls, to, bytes32(0)); + return _execute(datas, to, bytes32(0)); } revert(); // In your override, replace this with logic to operate on `opData`. } @@ -268,10 +263,12 @@ contract ERC7821 is Receiver { } } - /// @dev Executes the calls. + /// @dev Executes the datas, with a common `to` address. + /// @dev if to == address(0), it will be replaced with address(this) + /// @dev value for all calls is set to 0 /// Reverts and bubbles up error if any call fails. /// `extraData` can be any supplementary data (e.g. a memory pointer, some hash). - function _execute(CallSansTo[] calldata calls, address to, bytes32 keyHash) internal virtual { + function _execute(bytes[] calldata datas, address to, bytes32 extraData) internal virtual { unchecked { uint256 i; // If `to` is address(0), it will be replaced with address(this) @@ -280,11 +277,11 @@ contract ERC7821 is Receiver { let t := shr(96, shl(96, to)) to := or(mul(address(), iszero(t)), t) } - if (calls.length == uint256(0)) return; + if (datas.length == uint256(0)) return; do { - (uint256 value, bytes calldata data) = _get(calls, i); - _execute(to, value, data, keyHash); - } while (++i != calls.length); + + _execute(to, 0, datas[i], extraData); + } while (++i != datas.length); } } @@ -308,23 +305,6 @@ contract ERC7821 is Receiver { } } - /// @dev Convenience function for getting `calls[i]`, without bounds checks. - function _get(CallSansTo[] calldata calls, uint256 i) - internal - view - virtual - returns (uint256 value, bytes calldata data) - { - /// @solidity memory-safe-assembly - assembly { - let c := add(calls.offset, calldataload(add(calls.offset, shl(5, i)))) - value := calldataload(c) - let o := add(c, calldataload(add(c, 0x20))) - data.offset := add(o, 0x20) - data.length := calldataload(o) - } - } - /// @dev Convenience function for getting `calls[i]`, without bounds checks. function _get(Call[] calldata calls, uint256 i) internal diff --git a/test/ERC7821.t.sol b/test/ERC7821.t.sol index ee529d188b..a1e817c7d5 100644 --- a/test/ERC7821.t.sol +++ b/test/ERC7821.t.sol @@ -13,7 +13,7 @@ contract ERC7821Test is SoladyTest { address target; bytes32 internal constant _SUPPORTED_MODE = bytes10(0x01000000000078210001); - bytes32 internal constant _COMMON_TO_MODE = bytes10(0x01000000000078210003); + bytes32 internal constant _CALLDATA_OPTIMAL_MODE = bytes10(0x01000000000078210003); bytes[] internal _bytes; function setUp() public { @@ -54,22 +54,19 @@ contract ERC7821Test is SoladyTest { mbe.execute{value: _totalValue(calls)}(_SUPPORTED_MODE, data); } - function testERC7821CommonToGas() public { + function testERC7821CalldataOptimalGas() public { vm.pauseGasMetering(); vm.deal(address(this), 1 ether); - ERC7821.CallSansTo[] memory calls = new ERC7821.CallSansTo[](2); + bytes[] memory datas = new bytes[](2); - calls[0].value = 123; - calls[0].data = abi.encodeWithSignature("returnsBytes(bytes)", "hehe"); + datas[0] = abi.encodeWithSignature("returnsBytes(bytes)", "hehe"); + datas[1] = abi.encodeWithSignature("returnsHash(bytes)", "lol"); - calls[1].value = 789; - calls[1].data = abi.encodeWithSignature("returnsHash(bytes)", "lol"); - - bytes memory data = abi.encode(target, calls); + bytes memory data = abi.encode(target, datas); vm.resumeGasMetering(); - mbe.execute{value: _totalValue(calls)}(_COMMON_TO_MODE, data); + mbe.execute(_CALLDATA_OPTIMAL_MODE, data); } function testERC7821(bytes memory opData) public { @@ -90,19 +87,14 @@ contract ERC7821Test is SoladyTest { assertEq(mbe.lastOpData(), opData); } - function testERC7821CommonTo(bytes memory opData) public { + function testERC7821CalldataOptimal(bytes memory opData) public { vm.deal(address(this), 1 ether); - ERC7821.CallSansTo[] memory calls = new ERC7821.CallSansTo[](2); - calls[0].value = 123; - calls[0].data = abi.encodeWithSignature("returnsBytes(bytes)", "hehe"); - - calls[1].value = 789; - calls[1].data = abi.encodeWithSignature("returnsHash(bytes)", "lol"); + bytes[] memory datas = new bytes[](2); + datas[0] = abi.encodeWithSignature("returnsBytes(bytes)", "hehe"); + datas[1] = abi.encodeWithSignature("returnsHash(bytes)", "lol"); - mbe.execute{value: _totalValue(calls)}( - _COMMON_TO_MODE, _encodeCommonTo(target, calls, opData) - ); + mbe.execute(_CALLDATA_OPTIMAL_MODE, _encodeCalldataOptimal(target, datas, opData)); assertEq(mbe.lastOpData(), opData); } @@ -117,13 +109,12 @@ contract ERC7821Test is SoladyTest { mbe.execute{value: _totalValue(calls)}(_SUPPORTED_MODE, _encode(calls, "")); } - function testERC7821CommonToForRevert() public { - ERC7821.CallSansTo[] memory calls = new ERC7821.CallSansTo[](1); - calls[0].value = 0; - calls[0].data = abi.encodeWithSignature("revertsWithCustomError()"); + function testERC7821CalldataOptimalForRevert() public { + bytes[] memory datas = new bytes[](1); + datas[0] = abi.encodeWithSignature("revertsWithCustomError()"); vm.expectRevert(CustomError.selector); - mbe.execute{value: _totalValue(calls)}(_COMMON_TO_MODE, _encodeCommonTo(target, calls, "")); + mbe.execute(_CALLDATA_OPTIMAL_MODE, _encodeCalldataOptimal(target, datas, "")); } function _encode(ERC7821.Call[] memory calls, bytes memory opData) @@ -134,12 +125,12 @@ contract ERC7821Test is SoladyTest { return abi.encode(calls, opData); } - function _encodeCommonTo(address to, ERC7821.CallSansTo[] memory calls, bytes memory opData) + function _encodeCalldataOptimal(address to, bytes[] memory datas, bytes memory opData) internal returns (bytes memory) { - if (_randomChance(2) && opData.length == 0) return abi.encode(to, calls); - return abi.encode(to, calls, opData); + if (_randomChance(2) && opData.length == 0) return abi.encode(to, datas); + return abi.encode(to, datas, opData); } struct Payload { @@ -177,32 +168,31 @@ contract ERC7821Test is SoladyTest { } } - function testERC7821CommonTo(bytes32) public { + function testERC7821CalldataOptimal(bytes32) public { vm.deal(address(this), 1 ether); - ERC7821.CallSansTo[] memory calls = new ERC7821.CallSansTo[](_randomUniform() & 3); - Payload[] memory payloads = new Payload[](calls.length); + bytes[] memory datas = new bytes[](_randomUniform() & 3); + Payload[] memory payloads = new Payload[](datas.length); - for (uint256 i; i < calls.length; ++i) { - calls[i].value = _randomUniform() & 0xff; + for (uint256 i; i < datas.length; ++i) { bytes memory data = _truncateBytes(_randomBytes(), 0x1ff); payloads[i].data = data; if (_randomChance(2)) { payloads[i].mode = 0; - calls[i].data = abi.encodeWithSignature("returnsBytes(bytes)", data); + datas[i] = abi.encodeWithSignature("returnsBytes(bytes)", data); } else { payloads[i].mode = 1; - calls[i].data = abi.encodeWithSignature("returnsHash(bytes)", data); + datas[i] = abi.encodeWithSignature("returnsHash(bytes)", data); } } - mbe.executeDirect{value: _totalValue(calls)}(calls, target); + mbe.executeDirect(datas, target); - if (calls.length != 0 && _randomChance(32)) { - calls[_randomUniform() % calls.length].data = + if (datas.length != 0 && _randomChance(32)) { + datas[_randomUniform() % datas.length] = abi.encodeWithSignature("revertsWithCustomError()"); vm.expectRevert(CustomError.selector); - mbe.executeDirect{value: _totalValue(calls)}(calls, target); + mbe.executeDirect(datas, target); } } @@ -214,17 +204,6 @@ contract ERC7821Test is SoladyTest { } } - function _totalValue(ERC7821.CallSansTo[] memory calls) - internal - pure - returns (uint256 result) - { - unchecked { - for (uint256 i; i < calls.length; ++i) { - result += calls[i].value; - } - } - } function testERC7821ExecuteBatchOfBatches() public { bytes32 mode = bytes32(0x0100000000007821000200000000000000000000000000000000000000000000); @@ -276,15 +255,14 @@ contract ERC7821Test is SoladyTest { _bytes.push(x); } - function testERC7821CommonToWithZeroAddress() public { + function testERC7821CalldataOptimalWithZeroAddress() public { // Test that when to=address(0), it gets replaced with address(this) (the MockERC7821 contract) // We'll call executeDirect which directly calls the internal _execute function - ERC7821.CallSansTo[] memory calls = new ERC7821.CallSansTo[](1); - calls[0].value = 0; - calls[0].data = abi.encodeWithSignature("setAuthorizedCaller(address,bool)", address(0x123), true); + bytes[] memory datas = new bytes[](1); + datas[0] = abi.encodeWithSignature("setAuthorizedCaller(address,bool)", address(0x123), true); // This should replace address(0) with address(mbe) and call setAuthorizedCaller on itself - mbe.executeDirect(calls, address(0)); + mbe.executeDirect(datas, address(0)); // Verify the call succeeded by checking that address(0x123) is now authorized assertTrue(mbe.isAuthorizedCaller(address(0x123))); diff --git a/test/utils/mocks/MockERC7821.sol b/test/utils/mocks/MockERC7821.sol index a213f17d6f..ad6ade944a 100644 --- a/test/utils/mocks/MockERC7821.sol +++ b/test/utils/mocks/MockERC7821.sol @@ -26,11 +26,11 @@ contract MockERC7821 is ERC7821, Brutalizer { bytes32, bytes calldata, address to, - CallSansTo[] calldata calls, + bytes[] calldata datas, bytes calldata opData ) internal virtual override { lastOpData = opData; - _execute(calls, to, bytes32(0)); + _execute(datas, to, bytes32(0)); } function execute(bytes32 mode, bytes calldata executionData) public payable virtual override { @@ -45,10 +45,10 @@ contract MockERC7821 is ERC7821, Brutalizer { _checkMemory(); } - function executeDirect(CallSansTo[] calldata calls, address to) public payable virtual { + function executeDirect(bytes[] calldata datas, address to) public payable virtual { _misalignFreeMemoryPointer(); _brutalizeMemory(); - _execute(calls, to, bytes32(0)); + _execute(datas, to, bytes32(0)); _checkMemory(); } From a3f85142a0eff4dea59517015ef044fe304f03f3 Mon Sep 17 00:00:00 2001 From: Tanishk Goyal Date: Tue, 30 Sep 2025 18:23:57 +0530 Subject: [PATCH 7/7] chore: replace datas with dataArr --- src/accounts/ERC7821.sol | 24 +++++++-------- test/ERC7821.t.sol | 52 ++++++++++++++++---------------- test/utils/mocks/MockERC7821.sol | 8 ++--- 3 files changed, 42 insertions(+), 42 deletions(-) diff --git a/src/accounts/ERC7821.sol b/src/accounts/ERC7821.sol index d508187df2..9f22bb97f1 100644 --- a/src/accounts/ERC7821.sol +++ b/src/accounts/ERC7821.sol @@ -135,10 +135,10 @@ contract ERC7821 is Receiver { /// @dev For execution of a batch of batches with a common `to` address. /// @dev if to == address(0), it will be replaced with address(this) - /// Execution Data: abi.encode(address to, bytes[] datas, bytes opData) + /// Execution Data: abi.encode(address to, bytes[] dataArr, bytes opData) function _executeCalldataOptimal(bytes32 mode, bytes calldata executionData) internal virtual { address to; - bytes[] calldata datas; + bytes[] calldata dataArr; bytes calldata opData; /// @solidity memory-safe-assembly @@ -147,8 +147,8 @@ contract ERC7821 is Receiver { let dataOffset := add(executionData.offset, calldataload(add(0x20, executionData.offset))) - datas.offset := add(dataOffset, 0x20) - datas.length := calldataload(dataOffset) + dataArr.offset := add(dataOffset, 0x20) + dataArr.length := calldataload(dataOffset) // This line is needed to ensure that opdata is valid in all code paths. // Otherwise the compiler complains. @@ -162,7 +162,7 @@ contract ERC7821 is Receiver { } } - _execute(mode, executionData, to, datas, opData); + _execute(mode, executionData, to, dataArr, opData); } /// @dev For execution of a batch of batches. @@ -233,7 +233,7 @@ contract ERC7821 is Receiver { bytes32 mode, bytes calldata executionData, address to, - bytes[] calldata datas, + bytes[] calldata dataArr, bytes calldata opData ) internal virtual { // Silence compiler warning on unused variables. @@ -244,7 +244,7 @@ contract ERC7821 is Receiver { if (opData.length == uint256(0)) { require(msg.sender == address(this)); // Remember to return `_execute(calls, extraData)` when you override this function. - return _execute(datas, to, bytes32(0)); + return _execute(dataArr, to, bytes32(0)); } revert(); // In your override, replace this with logic to operate on `opData`. } @@ -263,12 +263,12 @@ contract ERC7821 is Receiver { } } - /// @dev Executes the datas, with a common `to` address. + /// @dev Executes the dataArr, with a common `to` address. /// @dev if to == address(0), it will be replaced with address(this) /// @dev value for all calls is set to 0 /// Reverts and bubbles up error if any call fails. /// `extraData` can be any supplementary data (e.g. a memory pointer, some hash). - function _execute(bytes[] calldata datas, address to, bytes32 extraData) internal virtual { + function _execute(bytes[] calldata dataArr, address to, bytes32 extraData) internal virtual { unchecked { uint256 i; // If `to` is address(0), it will be replaced with address(this) @@ -277,11 +277,11 @@ contract ERC7821 is Receiver { let t := shr(96, shl(96, to)) to := or(mul(address(), iszero(t)), t) } - if (datas.length == uint256(0)) return; + if (dataArr.length == uint256(0)) return; do { - _execute(to, 0, datas[i], extraData); - } while (++i != datas.length); + _execute(to, 0, dataArr[i], extraData); + } while (++i != dataArr.length); } } diff --git a/test/ERC7821.t.sol b/test/ERC7821.t.sol index a1e817c7d5..878577483f 100644 --- a/test/ERC7821.t.sol +++ b/test/ERC7821.t.sol @@ -58,12 +58,12 @@ contract ERC7821Test is SoladyTest { vm.pauseGasMetering(); vm.deal(address(this), 1 ether); - bytes[] memory datas = new bytes[](2); + bytes[] memory dataArr = new bytes[](2); - datas[0] = abi.encodeWithSignature("returnsBytes(bytes)", "hehe"); - datas[1] = abi.encodeWithSignature("returnsHash(bytes)", "lol"); + dataArr[0] = abi.encodeWithSignature("returnsBytes(bytes)", "hehe"); + dataArr[1] = abi.encodeWithSignature("returnsHash(bytes)", "lol"); - bytes memory data = abi.encode(target, datas); + bytes memory data = abi.encode(target, dataArr); vm.resumeGasMetering(); mbe.execute(_CALLDATA_OPTIMAL_MODE, data); @@ -90,11 +90,11 @@ contract ERC7821Test is SoladyTest { function testERC7821CalldataOptimal(bytes memory opData) public { vm.deal(address(this), 1 ether); - bytes[] memory datas = new bytes[](2); - datas[0] = abi.encodeWithSignature("returnsBytes(bytes)", "hehe"); - datas[1] = abi.encodeWithSignature("returnsHash(bytes)", "lol"); + bytes[] memory dataArr = new bytes[](2); + dataArr[0] = abi.encodeWithSignature("returnsBytes(bytes)", "hehe"); + dataArr[1] = abi.encodeWithSignature("returnsHash(bytes)", "lol"); - mbe.execute(_CALLDATA_OPTIMAL_MODE, _encodeCalldataOptimal(target, datas, opData)); + mbe.execute(_CALLDATA_OPTIMAL_MODE, _encodeCalldataOptimal(target, dataArr, opData)); assertEq(mbe.lastOpData(), opData); } @@ -110,11 +110,11 @@ contract ERC7821Test is SoladyTest { } function testERC7821CalldataOptimalForRevert() public { - bytes[] memory datas = new bytes[](1); - datas[0] = abi.encodeWithSignature("revertsWithCustomError()"); + bytes[] memory dataArr = new bytes[](1); + dataArr[0] = abi.encodeWithSignature("revertsWithCustomError()"); vm.expectRevert(CustomError.selector); - mbe.execute(_CALLDATA_OPTIMAL_MODE, _encodeCalldataOptimal(target, datas, "")); + mbe.execute(_CALLDATA_OPTIMAL_MODE, _encodeCalldataOptimal(target, dataArr, "")); } function _encode(ERC7821.Call[] memory calls, bytes memory opData) @@ -125,12 +125,12 @@ contract ERC7821Test is SoladyTest { return abi.encode(calls, opData); } - function _encodeCalldataOptimal(address to, bytes[] memory datas, bytes memory opData) + function _encodeCalldataOptimal(address to, bytes[] memory dataArr, bytes memory opData) internal returns (bytes memory) { - if (_randomChance(2) && opData.length == 0) return abi.encode(to, datas); - return abi.encode(to, datas, opData); + if (_randomChance(2) && opData.length == 0) return abi.encode(to, dataArr); + return abi.encode(to, dataArr, opData); } struct Payload { @@ -171,28 +171,28 @@ contract ERC7821Test is SoladyTest { function testERC7821CalldataOptimal(bytes32) public { vm.deal(address(this), 1 ether); - bytes[] memory datas = new bytes[](_randomUniform() & 3); - Payload[] memory payloads = new Payload[](datas.length); + bytes[] memory dataArr = new bytes[](_randomUniform() & 3); + Payload[] memory payloads = new Payload[](dataArr.length); - for (uint256 i; i < datas.length; ++i) { + for (uint256 i; i < dataArr.length; ++i) { bytes memory data = _truncateBytes(_randomBytes(), 0x1ff); payloads[i].data = data; if (_randomChance(2)) { payloads[i].mode = 0; - datas[i] = abi.encodeWithSignature("returnsBytes(bytes)", data); + dataArr[i] = abi.encodeWithSignature("returnsBytes(bytes)", data); } else { payloads[i].mode = 1; - datas[i] = abi.encodeWithSignature("returnsHash(bytes)", data); + dataArr[i] = abi.encodeWithSignature("returnsHash(bytes)", data); } } - mbe.executeDirect(datas, target); + mbe.executeDirect(dataArr, target); - if (datas.length != 0 && _randomChance(32)) { - datas[_randomUniform() % datas.length] = + if (dataArr.length != 0 && _randomChance(32)) { + dataArr[_randomUniform() % dataArr.length] = abi.encodeWithSignature("revertsWithCustomError()"); vm.expectRevert(CustomError.selector); - mbe.executeDirect(datas, target); + mbe.executeDirect(dataArr, target); } } @@ -258,11 +258,11 @@ contract ERC7821Test is SoladyTest { function testERC7821CalldataOptimalWithZeroAddress() public { // Test that when to=address(0), it gets replaced with address(this) (the MockERC7821 contract) // We'll call executeDirect which directly calls the internal _execute function - bytes[] memory datas = new bytes[](1); - datas[0] = abi.encodeWithSignature("setAuthorizedCaller(address,bool)", address(0x123), true); + bytes[] memory dataArr = new bytes[](1); + dataArr[0] = abi.encodeWithSignature("setAuthorizedCaller(address,bool)", address(0x123), true); // This should replace address(0) with address(mbe) and call setAuthorizedCaller on itself - mbe.executeDirect(datas, address(0)); + mbe.executeDirect(dataArr, address(0)); // Verify the call succeeded by checking that address(0x123) is now authorized assertTrue(mbe.isAuthorizedCaller(address(0x123))); diff --git a/test/utils/mocks/MockERC7821.sol b/test/utils/mocks/MockERC7821.sol index ad6ade944a..f1b10cc09f 100644 --- a/test/utils/mocks/MockERC7821.sol +++ b/test/utils/mocks/MockERC7821.sol @@ -26,11 +26,11 @@ contract MockERC7821 is ERC7821, Brutalizer { bytes32, bytes calldata, address to, - bytes[] calldata datas, + bytes[] calldata dataArr, bytes calldata opData ) internal virtual override { lastOpData = opData; - _execute(datas, to, bytes32(0)); + _execute(dataArr, to, bytes32(0)); } function execute(bytes32 mode, bytes calldata executionData) public payable virtual override { @@ -45,10 +45,10 @@ contract MockERC7821 is ERC7821, Brutalizer { _checkMemory(); } - function executeDirect(bytes[] calldata datas, address to) public payable virtual { + function executeDirect(bytes[] calldata dataArr, address to) public payable virtual { _misalignFreeMemoryPointer(); _brutalizeMemory(); - _execute(datas, to, bytes32(0)); + _execute(dataArr, to, bytes32(0)); _checkMemory(); }