@@ -55,6 +55,7 @@ contract ERC7821 is Receiver {
5555 /// - `0x01000000000000000000...`: Single batch. Does not support optional `opData`.
5656 /// - `0x01000000000078210001...`: Single batch. Supports optional `opData`.
5757 /// - `0x01000000000078210002...`: Batch of batches.
58+ /// - `0x01000000000078210003...`: Single batch with common `to` address and optional `opData`.
5859 ///
5960 /// For the "batch of batches" mode, each batch will be recursively passed into
6061 /// `execute` internally with mode `0x01000000000078210001...`.
@@ -73,8 +74,10 @@ contract ERC7821 is Receiver {
7374 function execute (bytes32 mode , bytes calldata executionData ) public payable virtual {
7475 uint256 id = _executionModeId (mode);
7576 if (id == 3 ) return _executeBatchOfBatches (mode, executionData);
77+ if (id == 4 ) return _executeCalldataOptimal (mode, executionData);
7678 Call[] calldata calls;
7779 bytes calldata opData;
80+
7881 /// @solidity memory-safe-assembly
7982 assembly {
8083 if iszero (id) {
@@ -126,7 +129,40 @@ contract ERC7821 is Receiver {
126129 id := eq (m, 0x01000000000000000000 ) // 1.
127130 id := or (shl (1 , eq (m, 0x01000000000078210001 )), id) // 2.
128131 id := or (mul (3 , eq (m, 0x01000000000078210002 )), id) // 3.
132+ id := or (mul (4 , eq (m, 0x01000000000078210003 )), id) // 4.
133+ }
134+ }
135+
136+ /// @dev For execution of a batch of batches with a common `to` address.
137+ /// @dev if to == address(0), it will be replaced with address(this)
138+ /// Execution Data: abi.encode(address to, bytes[] dataArr, bytes opData)
139+ function _executeCalldataOptimal (bytes32 mode , bytes calldata executionData ) internal virtual {
140+ address to;
141+ bytes [] calldata dataArr;
142+ bytes calldata opData;
143+
144+ /// @solidity memory-safe-assembly
145+ assembly {
146+ to := calldataload (executionData.offset)
147+
148+ let dataOffset :=
149+ add (executionData.offset, calldataload (add (0x20 , executionData.offset)))
150+ dataArr.offset := add (dataOffset, 0x20 )
151+ dataArr.length := calldataload (dataOffset)
152+
153+ // This line is needed to ensure that opdata is valid in all code paths.
154+ // Otherwise the compiler complains.
155+ opData.length := 0
156+ // If the offset of `executionData` allows for `opData`, and the mode supports it.
157+ if gt (calldataload (add (0x20 , executionData.offset)), 0x40 ) {
158+ let opDataOffset :=
159+ add (executionData.offset, calldataload (add (0x40 , executionData.offset)))
160+ opData.offset := add (opDataOffset, 0x20 )
161+ opData.length := calldataload (opDataOffset)
162+ }
129163 }
164+
165+ _execute (mode, executionData, to, dataArr, opData);
130166 }
131167
132168 /// @dev For execution of a batch of batches.
@@ -190,6 +226,29 @@ contract ERC7821 is Receiver {
190226 revert (); // In your override, replace this with logic to operate on `opData`.
191227 }
192228
229+ /// @dev Executes the calls.
230+ /// Reverts and bubbles up error if any call fails.
231+ /// The `mode` and `executionData` are passed along in case there's a need to use them.
232+ function _execute (
233+ bytes32 mode ,
234+ bytes calldata executionData ,
235+ address to ,
236+ bytes [] calldata dataArr ,
237+ bytes calldata opData
238+ ) internal virtual {
239+ // Silence compiler warning on unused variables.
240+ mode = mode;
241+ executionData = executionData;
242+ // Very basic auth to only allow this contract to be called by itself.
243+ // Override this function to perform more complex auth with `opData`.
244+ if (opData.length == uint256 (0 )) {
245+ require (msg .sender == address (this ));
246+ // Remember to return `_execute(calls, extraData)` when you override this function.
247+ return _execute (dataArr, to, bytes32 (0 ));
248+ }
249+ revert (); // In your override, replace this with logic to operate on `opData`.
250+ }
251+
193252 /// @dev Executes the calls.
194253 /// Reverts and bubbles up error if any call fails.
195254 /// `extraData` can be any supplementary data (e.g. a memory pointer, some hash).
@@ -204,6 +263,28 @@ contract ERC7821 is Receiver {
204263 }
205264 }
206265
266+ /// @dev Executes the dataArr, with a common `to` address.
267+ /// @dev if to == address(0), it will be replaced with address(this)
268+ /// @dev value for all calls is set to 0
269+ /// Reverts and bubbles up error if any call fails.
270+ /// `extraData` can be any supplementary data (e.g. a memory pointer, some hash).
271+ function _execute (bytes [] calldata dataArr , address to , bytes32 extraData ) internal virtual {
272+ unchecked {
273+ uint256 i;
274+ // If `to` is address(0), it will be replaced with address(this)
275+ /// @solidity memory-safe-assembly
276+ assembly {
277+ let t := shr (96 , shl (96 , to))
278+ to := or (mul (address (), iszero (t)), t)
279+ }
280+ if (dataArr.length == uint256 (0 )) return ;
281+ do {
282+
283+ _execute (to, 0 , dataArr[i], extraData);
284+ } while (++ i != dataArr.length );
285+ }
286+ }
287+
207288 /// @dev Executes the call.
208289 /// Reverts and bubbles up error if any call fails.
209290 /// `extraData` can be any supplementary data (e.g. a memory pointer, some hash).
0 commit comments