Skip to content

Commit 53dd5cb

Browse files
committed
feat: further optimize
1 parent b88318d commit 53dd5cb

10 files changed

Lines changed: 832 additions & 250 deletions
Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
// SPDX-License-Identifier: MIT AND Apache-2.0
2+
pragma solidity 0.8.23;
3+
4+
import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
5+
import { IEntryPoint } from "@account-abstraction/interfaces/IEntryPoint.sol";
6+
import { Execution } from "@erc7579/interfaces/IERC7579Account.sol";
7+
8+
import { EIP7702DeleGatorCore } from "./EIP7702DeleGatorCore.sol";
9+
import { IDelegationManager } from "../interfaces/IDelegationManager.sol";
10+
import { IEIP7702BatchDeleGator } from "../interfaces/IEIP7702BatchDeleGator.sol";
11+
import { ERC1271Lib } from "../libraries/ERC1271Lib.sol";
12+
import { BatchAuthorizationLib } from "../libraries/BatchAuthorizationLib.sol";
13+
14+
/**
15+
* @title EIP7702BatchDeleGator
16+
* @notice Stateful EIP-7702 DeleGator with ERC-7821 signed relay batches and unordered nonce replay protection.
17+
* @dev Standard ERC-7579 execution remains on inherited `execute(ModeCode,bytes)` with EntryPoint/self access control.
18+
* @dev Signed relay batches use the child-only `executeBatch(bytes32,bytes)` entrypoint.
19+
*/
20+
contract EIP7702BatchDeleGator is EIP7702DeleGatorCore, IEIP7702BatchDeleGator {
21+
using BatchAuthorizationLib for Execution[];
22+
23+
////////////////////////////// Constants //////////////////////////////
24+
25+
/// @dev The name of the contract used in the EIP-712 domain.
26+
string public constant NAME = "EIP7702BatchDeleGator";
27+
28+
/// @dev The version used in the domainSeparator for EIP712.
29+
string public constant DOMAIN_VERSION = "1";
30+
31+
/// @dev The semantic version of the contract.
32+
string public constant VERSION = "1.0.0";
33+
34+
/// @dev Single batch, revert on failure — `abi.encode(Execution[])` only.
35+
bytes32 public constant MODE_BATCH_SIMPLE =
36+
bytes32(uint256(0x0100000000000000000000000000000000000000000000000000000000000000));
37+
38+
/// @dev Single batch with optional `opData` — `abi.encode(Execution[], bytes)`.
39+
bytes32 public constant MODE_BATCH_WITH_OPDATA =
40+
bytes32(uint256(0x0100000000007821000100000000000000000000000000000000000000000000));
41+
42+
/// @dev Nested signed batches — `abi.encode(bytes[])`.
43+
bytes32 public constant MODE_BATCH_OF_BATCHES =
44+
bytes32(uint256(0x0100000000007821000200000000000000000000000000000000000000000000));
45+
46+
/// @custom:storage-location erc7201:DeleGator.EIP7702BatchDeleGator.nonce
47+
bytes32 private constant NONCE_STORAGE_LOCATION =
48+
0x1093877edb0cc0e2b2ea60a70fdf07c1dd8a109e13f7d461cf4b95c014189900;
49+
50+
////////////////////////////// Storage //////////////////////////////
51+
52+
struct NonceStorage {
53+
/// @dev Bitmap of used relay nonces. Nonce word is `nonce >> 8`; bit is `uint8(nonce)`.
54+
mapping(uint256 word => uint256 bitmap) nonceBitmap;
55+
}
56+
57+
////////////////////////////// Events //////////////////////////////
58+
59+
event NonceInvalidated(uint256 indexed nonce);
60+
event NoncesInvalidated(uint256 indexed word, uint256 mask);
61+
62+
////////////////////////////// Errors //////////////////////////////
63+
64+
error UnsupportedBatchExecutionMode();
65+
error UnauthorizedBatchExecuteCaller();
66+
error UnauthorizedRelayer();
67+
error InvalidBatchSignature();
68+
error BatchAuthorizationExpired();
69+
error NonceAlreadyUsed();
70+
71+
////////////////////////////// Constructor //////////////////////////////
72+
73+
/**
74+
* @notice Constructor for the EIP7702Batch DeleGator.
75+
* @param _delegationManager Address of the trusted DelegationManager contract.
76+
* @param _entryPoint Address of the EntryPoint contract.
77+
*/
78+
constructor(IDelegationManager _delegationManager, IEntryPoint _entryPoint)
79+
EIP7702DeleGatorCore(_delegationManager, _entryPoint, NAME, DOMAIN_VERSION)
80+
{ }
81+
82+
////////////////////////////// External Methods //////////////////////////////
83+
84+
/// @inheritdoc IEIP7702BatchDeleGator
85+
function executeBatch(bytes32 mode, bytes calldata executionData) external payable {
86+
_routeBatchCalldata(mode, executionData);
87+
}
88+
89+
/// @inheritdoc IEIP7702BatchDeleGator
90+
function supportsBatchExecutionMode(bytes32 mode) external pure returns (bool) {
91+
return _batchExecutionModeId(mode) != 0;
92+
}
93+
94+
/// @inheritdoc IEIP7702BatchDeleGator
95+
function hashBatchAuthorizationWithNonce(
96+
Execution[] calldata executions,
97+
uint256 nonce,
98+
uint256 deadline,
99+
address relayer
100+
)
101+
external
102+
view
103+
returns (bytes32)
104+
{
105+
return _hashBatchAuthorizationWithNonce(executions, nonce, deadline, relayer);
106+
}
107+
108+
/// @inheritdoc IEIP7702BatchDeleGator
109+
function isNonceUsed(uint256 nonce) external view returns (bool) {
110+
(uint256 word, uint256 mask) = _nonceWordAndMask(nonce);
111+
return _nonceStorage().nonceBitmap[word] & mask != 0;
112+
}
113+
114+
/// @inheritdoc IEIP7702BatchDeleGator
115+
function nonceBitmap(uint256 word) external view returns (uint256 bitmap) {
116+
return _nonceStorage().nonceBitmap[word];
117+
}
118+
119+
/// @inheritdoc IEIP7702BatchDeleGator
120+
function invalidateNonce(uint256 nonce) external onlyEntryPointOrSelf {
121+
_consumeNonce(nonce);
122+
emit NonceInvalidated(nonce);
123+
}
124+
125+
/// @inheritdoc IEIP7702BatchDeleGator
126+
function invalidateNonces(uint256 word, uint256 mask) external onlyEntryPointOrSelf {
127+
_nonceStorage().nonceBitmap[word] |= mask;
128+
emit NoncesInvalidated(word, mask);
129+
}
130+
131+
////////////////////////////// Internal Methods //////////////////////////////
132+
133+
/**
134+
* @notice Verifies relay signatures against the delegated EOA address.
135+
* @param _hash The data signed.
136+
* @param _signature A 65-byte signature produced by the EIP7702 EOA.
137+
*/
138+
function _isValidSignature(bytes32 _hash, bytes calldata _signature) internal view override returns (bytes4) {
139+
if (ECDSA.recover(_hash, _signature) == address(this)) return ERC1271Lib.EIP1271_MAGIC_VALUE;
140+
141+
return ERC1271Lib.SIG_VALIDATION_FAILED;
142+
}
143+
144+
/// @dev Mode id: 0 invalid, 1 simple batch, 2 batch + optional opData, 3 batch-of-batches.
145+
function _batchExecutionModeId(bytes32 mode) internal pure returns (uint256 id) {
146+
/// @solidity memory-safe-assembly
147+
assembly {
148+
let m := and(shr(mul(22, 8), mode), 0xffff00000000ffffffff)
149+
id := eq(m, 0x01000000000000000000)
150+
id := or(shl(1, eq(m, 0x01000000000078210001)), id)
151+
id := or(mul(3, eq(m, 0x01000000000078210002)), id)
152+
}
153+
}
154+
155+
function _routeBatchCalldata(bytes32 mode, bytes calldata executionData) internal {
156+
uint256 id = _batchExecutionModeId(mode);
157+
if (id == 0) revert UnsupportedBatchExecutionMode();
158+
159+
if (id == 3) {
160+
mode ^= bytes32(uint256(3 << (22 * 8)));
161+
bytes[] memory batches = abi.decode(executionData, (bytes[]));
162+
uint256 n = batches.length;
163+
for (uint256 i = 0; i < n;) {
164+
_routeBatchMemory(mode, batches[i]);
165+
unchecked {
166+
++i;
167+
}
168+
}
169+
return;
170+
}
171+
172+
Execution[] memory executions;
173+
bytes memory opData;
174+
175+
if (id == 2) {
176+
(executions, opData) = abi.decode(executionData, (Execution[], bytes));
177+
} else {
178+
executions = abi.decode(executionData, (Execution[]));
179+
opData = "";
180+
}
181+
182+
_authorizeAndExecuteBatch(executions, opData);
183+
}
184+
185+
function _routeBatchMemory(bytes32 mode, bytes memory executionData) internal {
186+
uint256 id = _batchExecutionModeId(mode);
187+
if (id == 0) revert UnsupportedBatchExecutionMode();
188+
189+
if (id == 3) {
190+
mode ^= bytes32(uint256(3 << (22 * 8)));
191+
bytes[] memory batches = abi.decode(executionData, (bytes[]));
192+
uint256 n = batches.length;
193+
for (uint256 i = 0; i < n;) {
194+
_routeBatchMemory(mode, batches[i]);
195+
unchecked {
196+
++i;
197+
}
198+
}
199+
return;
200+
}
201+
202+
Execution[] memory executions;
203+
bytes memory opData;
204+
205+
if (id == 2) {
206+
(executions, opData) = abi.decode(executionData, (Execution[], bytes));
207+
} else {
208+
executions = abi.decode(executionData, (Execution[]));
209+
opData = "";
210+
}
211+
212+
_authorizeAndExecuteBatch(executions, opData);
213+
}
214+
215+
function _authorizeAndExecuteBatch(Execution[] memory executions, bytes memory opData) internal {
216+
if (opData.length != 0) {
217+
_verifyBatchAuthorization(executions, opData);
218+
} else if (msg.sender != address(this)) {
219+
revert UnauthorizedBatchExecuteCaller();
220+
}
221+
222+
_executeExecutions(executions);
223+
}
224+
225+
function _verifyBatchAuthorization(Execution[] memory executions, bytes memory opData) internal {
226+
(uint256 nonce, uint256 deadline, address relayer, bytes memory signature) =
227+
abi.decode(opData, (uint256, uint256, address, bytes));
228+
229+
if (block.timestamp > deadline) revert BatchAuthorizationExpired();
230+
if (relayer != address(0) && relayer != msg.sender) revert UnauthorizedRelayer();
231+
232+
(uint256 word, uint256 mask) = _nonceWordAndMask(nonce);
233+
NonceStorage storage $ = _nonceStorage();
234+
uint256 bitmap = $.nonceBitmap[word];
235+
if (bitmap & mask != 0) revert NonceAlreadyUsed();
236+
237+
bytes32 callsDigest = BatchAuthorizationLib.executionsDigest(executions);
238+
bytes32 structHash = BatchAuthorizationLib.batchAuthorizationWithNonceStructHash(callsDigest, nonce, deadline, relayer);
239+
bytes32 digest = _hashTypedDataV4(structHash);
240+
241+
address recovered = ECDSA.recover(digest, signature);
242+
if (recovered != address(this)) revert InvalidBatchSignature();
243+
244+
$.nonceBitmap[word] = bitmap | mask;
245+
}
246+
247+
function _hashBatchAuthorizationWithNonce(
248+
Execution[] calldata executions,
249+
uint256 nonce,
250+
uint256 deadline,
251+
address relayer
252+
)
253+
internal
254+
view
255+
returns (bytes32)
256+
{
257+
bytes32 callsDigest = BatchAuthorizationLib.executionsDigestCalldata(executions);
258+
bytes32 structHash = BatchAuthorizationLib.batchAuthorizationWithNonceStructHash(callsDigest, nonce, deadline, relayer);
259+
return _hashTypedDataV4(structHash);
260+
}
261+
262+
function _executeExecutions(Execution[] memory executions) internal {
263+
uint256 n = executions.length;
264+
for (uint256 i = 0; i < n;) {
265+
Execution memory execution = executions[i];
266+
address target = execution.target == address(0) ? address(this) : execution.target;
267+
bytes memory callData = execution.callData;
268+
bool ok;
269+
270+
/// @solidity memory-safe-assembly
271+
assembly {
272+
ok := call(gas(), target, mload(add(execution, 0x20)), add(callData, 0x20), mload(callData), 0, 0)
273+
if iszero(ok) {
274+
let ptr := mload(0x40)
275+
let size := returndatasize()
276+
returndatacopy(ptr, 0, size)
277+
revert(ptr, size)
278+
}
279+
}
280+
281+
unchecked {
282+
++i;
283+
}
284+
}
285+
}
286+
287+
function _nonceWordAndMask(uint256 nonce) internal pure returns (uint256 word, uint256 mask) {
288+
word = nonce >> 8;
289+
mask = 1 << uint8(nonce);
290+
}
291+
292+
function _consumeNonce(uint256 nonce) internal {
293+
(uint256 word, uint256 mask) = _nonceWordAndMask(nonce);
294+
NonceStorage storage $ = _nonceStorage();
295+
uint256 bitmap = $.nonceBitmap[word];
296+
if (bitmap & mask != 0) revert NonceAlreadyUsed();
297+
$.nonceBitmap[word] = bitmap | mask;
298+
}
299+
300+
function _nonceStorage() private pure returns (NonceStorage storage $) {
301+
/// @solidity memory-safe-assembly
302+
assembly {
303+
$.slot := NONCE_STORAGE_LOCATION
304+
}
305+
}
306+
}

0 commit comments

Comments
 (0)