From 60891e98f60995a830406923bde17291e3aa2f0d Mon Sep 17 00:00:00 2001 From: Vectorized Date: Fri, 22 Nov 2024 03:06:55 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Revert=20back=20ERC782?= =?UTF-8?q?1=20to=20original=20implementation=20(#1173)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/accounts/ERC7821.sol | 63 +++----------------------------- test/ERC7821.t.sol | 46 ++--------------------- test/utils/mocks/MockERC7821.sol | 10 ++--- 3 files changed, 13 insertions(+), 106 deletions(-) diff --git a/src/accounts/ERC7821.sol b/src/accounts/ERC7821.sol index c13549c3f0..1171986ea7 100644 --- a/src/accounts/ERC7821.sol +++ b/src/accounts/ERC7821.sol @@ -15,13 +15,6 @@ abstract contract ERC7821 { bytes data; } - /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ - /* ERRORS */ - /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ - - /// @dev The execution mode is not supported. - error UnsupportedExecutionMode(); - /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* FUNCTIONS TO OVERRIDE */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ @@ -33,66 +26,21 @@ abstract contract ERC7821 { /* EXECUTION OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ - /// @dev Executes the `calls` in `executionData` and returns the results. - /// The `results` are the returned data from each call. + /// @dev Executes the `calls` and returns the results. /// Reverts and bubbles up error if any call fails. - /// - /// `executionData` encoding: - /// - If `opData` is empty, `executionData` is simply `abi.encode(calls)`. - /// - Else, `executionData` is `abi.encode(calls, opData)`. - /// See: https://eips.ethereum.org/EIPS/eip-7579 - /// - /// Authorization checks: - /// - If `opData` is empty, the implementation SHOULD require that - /// `msg.sender == address(this)`. - /// - If `opData` is not empty, the implementation SHOULD use the signature - /// encoded in `opData` to determine if the caller can perform the execution. - /// - /// `opData` may be used to store additional data for authentication, - /// paymaster data, gas limits, etc. - function execute(bytes32 mode, bytes calldata executionData) + function execute(Call[] calldata calls, bytes calldata opData) public payable virtual returns (bytes[] memory results) { - if (!supportsExecutionMode(mode)) { - /// @solidity memory-safe-assembly - assembly { - mstore(0x00, 0x7f181275) // `UnsupportedExecutionMode()`. - revert(0x1c, 0x04) - } - } - Call[] calldata calls; - bytes calldata opData; - /// @solidity memory-safe-assembly - assembly { - opData.length := 0 - let o := add(executionData.offset, calldataload(executionData.offset)) - calls.offset := add(o, 0x20) - calls.length := calldataload(o) - if iszero(lt(calldataload(executionData.offset), 0x40)) { - let p := add(executionData.offset, calldataload(add(executionData.offset, 0x20))) - opData.length := calldataload(p) - opData.offset := add(p, 0x20) - } - } _authorizeExecute(calls, opData); return _execute(calls); } - /// @dev Provided for execution mode support detection. - function supportsExecutionMode(bytes32 mode) public pure virtual returns (bool result) { - // Only supports atomic batched executions. - // For the encoding scheme, see: https://eips.ethereum.org/EIPS/eip-7579 - // Bytes Layout: - // - [0] ( 1 byte ) `0x01` for batch call. - // - [1] ( 1 byte ) `0x00` for revert on any failure. - // - [2..5] ( 4 bytes) Reserved by ERC7579 for future standardization. - // - [6..7] ( 2 bytes) `0x7821`. - // - [8..9] ( 2 bytes) Version in hex format. - // - [9..31] (22 bytes) Unused. Free for use. - return bytes10(mode) & 0xffff00000000ffffffff == 0x01000000000078210001; + /// @dev This function is provided for frontends to detect support. + function minimalBatchExecutorVersion() public pure virtual returns (uint256) { + return 1; // This number may change. } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ @@ -114,7 +62,6 @@ abstract contract ERC7821 { bytes calldata data; /// @solidity memory-safe-assembly assembly { - // Directly extract `calls[i]` without bounds checks. let c := add(calls.offset, calldataload(add(calls.offset, shl(5, i)))) target := calldataload(c) value := calldataload(add(c, 0x20)) diff --git a/test/ERC7821.t.sol b/test/ERC7821.t.sol index 06b949bdb1..2402914ff4 100644 --- a/test/ERC7821.t.sol +++ b/test/ERC7821.t.sol @@ -12,8 +12,6 @@ contract ERC7821Test is SoladyTest { address target; - bytes32 internal constant _SUPPORTED_MODE = bytes10(0x01000000000078210001); - function setUp() public { mbe = new MockERC7821(); target = LibClone.clone(address(this)); @@ -31,34 +29,7 @@ contract ERC7821Test is SoladyTest { return keccak256(b); } - function testERC7821Gas() public { - vm.pauseGasMetering(); - vm.deal(address(this), 1 ether); - - ERC7821.Call[] memory calls = new ERC7821.Call[](2); - - calls[0].target = target; - calls[0].value = 123; - calls[0].data = abi.encodeWithSignature("returnsBytes(bytes)", "hehe"); - - calls[1].target = target; - calls[1].value = 789; - calls[1].data = abi.encodeWithSignature("returnsHash(bytes)", "lol"); - - bytes memory data = abi.encode(calls); - vm.resumeGasMetering(); - - bytes[] memory results = mbe.execute{value: _totalValue(calls)}(_SUPPORTED_MODE, data); - - vm.pauseGasMetering(); - - assertEq(results.length, 2); - assertEq(abi.decode(results[0], (bytes)), "hehe"); - assertEq(abi.decode(results[1], (bytes32)), keccak256("lol")); - vm.resumeGasMetering(); - } - - function testERC7821(bytes memory opData) public { + function testERC7821() public { vm.deal(address(this), 1 ether); ERC7821.Call[] memory calls = new ERC7821.Call[](2); @@ -71,10 +42,7 @@ contract ERC7821Test is SoladyTest { calls[1].value = 789; calls[1].data = abi.encodeWithSignature("returnsHash(bytes)", "lol"); - bytes[] memory results = - mbe.execute{value: _totalValue(calls)}(_SUPPORTED_MODE, _encode(calls, opData)); - - assertEq(mbe.lastOpData(), opData); + bytes[] memory results = mbe.execute{value: _totalValue(calls)}(calls, ""); assertEq(results.length, 2); assertEq(abi.decode(results[0], (bytes)), "hehe"); @@ -88,15 +56,7 @@ contract ERC7821Test is SoladyTest { calls[0].data = abi.encodeWithSignature("revertsWithCustomError()"); vm.expectRevert(CustomError.selector); - mbe.execute{value: _totalValue(calls)}(_SUPPORTED_MODE, _encode(calls, "")); - } - - function _encode(ERC7821.Call[] memory calls, bytes memory opData) - internal - returns (bytes memory) - { - if (_randomChance(2) && opData.length == 0) return abi.encode(calls); - return abi.encode(calls, opData); + mbe.execute{value: _totalValue(calls)}(calls, ""); } struct Payload { diff --git a/test/utils/mocks/MockERC7821.sol b/test/utils/mocks/MockERC7821.sol index 2603b9603e..0c1218f7bb 100644 --- a/test/utils/mocks/MockERC7821.sol +++ b/test/utils/mocks/MockERC7821.sol @@ -7,11 +7,11 @@ import {Brutalizer} from "../Brutalizer.sol"; /// @dev WARNING! This mock is strictly intended for testing purposes only. /// Do NOT copy anything here into production code unless you really know what you are doing. contract MockERC7821 is ERC7821, Brutalizer { - bytes public lastOpData; - - function _authorizeExecute(Call[] calldata, bytes calldata opData) internal virtual override { - lastOpData = opData; - } + function _authorizeExecute(Call[] calldata calls, bytes calldata opData) + internal + virtual + override + {} function executeDirect(Call[] calldata calls) public From b38e2c025f8cd1ecbfc1b92678fe05ac950a4bb1 Mon Sep 17 00:00:00 2001 From: Vectorized Date: Thu, 21 Nov 2024 19:07:56 +0000 Subject: [PATCH 2/4] Bump version to 0.0.271 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 36ecb9e251..f93a68442f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "solady", "license": "MIT", - "version": "0.0.270", + "version": "0.0.271", "description": "Optimized Solidity snippets.", "files": [ "src/**/*.sol", From 9ec5e0e13a822bb6006f36a8f29eb986906d602d Mon Sep 17 00:00:00 2001 From: wanxiangchwng Date: Sat, 23 Nov 2024 19:57:03 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=A5=A2=20Fix=20broken=20links=20(#117?= =?UTF-8?q?5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wanxiangchwng --- README.md | 2 +- foundry.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index cd7f95f86e..16bbc282ed 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ I'm sooooooOooooooooOoooOoooooooooooooooo... ## Installation -To install with [**Foundry**](https://github.com/gakonst/foundry): +To install with [**Foundry**](https://github.com/foundry-rs/foundry): ```sh forge install vectorized/solady diff --git a/foundry.toml b/foundry.toml index 607d6263fd..a7772ef1e9 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,6 +1,6 @@ # Foundry Configuration File -# Default definitions: https://github.com/gakonst/foundry/blob/b7917fa8491aedda4dd6db53fbb206ea233cd531/config/src/lib.rs#L782 -# See more config options at: https://github.com/gakonst/foundry/tree/master/config +# Default definitions: https://github.com/foundry-rs/foundry/blob/b7917fa8491aedda4dd6db53fbb206ea233cd531/config/src/lib.rs#L782 +# See more config options at: https://github.com/foundry-rs/foundry/tree/master/.config # The Default Profile [profile.default] From e8759bd554b217103b291784a6dcf8143138e231 Mon Sep 17 00:00:00 2001 From: Vectorized Date: Sat, 23 Nov 2024 19:59:41 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Update=20ERC7821=20(#1?= =?UTF-8?q?174)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Optimize ERC7821 * Optimize 7821 * Add comments * T --- src/accounts/ERC7821.sol | 33 ++++++++++++++++---------------- test/utils/mocks/MockERC7821.sol | 10 +++++++--- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/accounts/ERC7821.sol b/src/accounts/ERC7821.sol index 1171986ea7..87c4875e01 100644 --- a/src/accounts/ERC7821.sol +++ b/src/accounts/ERC7821.sol @@ -3,25 +3,18 @@ pragma solidity ^0.8.4; /// @notice Minimal batch executor mixin. /// @author Solady (https://github.com/vectorized/solady/blob/main/src/accounts/ERC7821.sol) -abstract contract ERC7821 { +contract ERC7821 { /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* STRUCTS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Call struct for the `execute` function. struct Call { - address target; - uint256 value; - bytes data; + address target; // Replaced as `address(this)` if `address(0)`. + uint256 value; // Amount of native currency (i.e. Ether) to send. + bytes data; // Calldata to send with the call. } - /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ - /* FUNCTIONS TO OVERRIDE */ - /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ - - /// @dev Ensures that `execute` can only be called by the correct caller or `opData`. - function _authorizeExecute(Call[] calldata calls, bytes calldata opData) internal virtual; - /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* EXECUTION OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ @@ -32,15 +25,21 @@ abstract contract ERC7821 { public payable virtual - returns (bytes[] memory results) + returns (bytes[] memory) { - _authorizeExecute(calls, opData); - return _execute(calls); + // Very basic auth to only allow this contract to be called by itself. + // Override `execute` to perform more complex auth with `opData`. + if (opData.length == uint256(0)) { + require(msg.sender == address(this)); + // Remember to return `_execute(calls)` when you override `execute`. + return _execute(calls); + } + revert(); // In your override, replace this with logic to operate on `opData`. } /// @dev This function is provided for frontends to detect support. function minimalBatchExecutorVersion() public pure virtual returns (uint256) { - return 1; // This number may change. + return 1; // This number may be updated in the future. } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ @@ -62,8 +61,10 @@ abstract contract ERC7821 { bytes calldata data; /// @solidity memory-safe-assembly assembly { + // Direct extract the arguments of `call[i]` without bounds checks. let c := add(calls.offset, calldataload(add(calls.offset, shl(5, i)))) - target := calldataload(c) + // Replaces `target` with `address(this)` if `address(0)` is provided. + target := or(mul(address(), iszero(calldataload(c))), calldataload(c)) value := calldataload(add(c, 0x20)) let o := add(c, calldataload(add(c, 0x40))) data.offset := add(o, 0x20) diff --git a/test/utils/mocks/MockERC7821.sol b/test/utils/mocks/MockERC7821.sol index 0c1218f7bb..24b8fa07ef 100644 --- a/test/utils/mocks/MockERC7821.sol +++ b/test/utils/mocks/MockERC7821.sol @@ -7,11 +7,15 @@ import {Brutalizer} from "../Brutalizer.sol"; /// @dev WARNING! This mock is strictly intended for testing purposes only. /// Do NOT copy anything here into production code unless you really know what you are doing. contract MockERC7821 is ERC7821, Brutalizer { - function _authorizeExecute(Call[] calldata calls, bytes calldata opData) - internal + function execute(Call[] calldata calls, bytes calldata) + public + payable virtual override - {} + returns (bytes[] memory) + { + return _execute(calls); + } function executeDirect(Call[] calldata calls) public