|
10 | 10 | copying the payload to memory before the call |
11 | 11 | - `bubblingValueCall`: arbitrary low-level call with caller-provided ETH value, |
12 | 12 | caller-provided input/output memory slices, and exact revert-data bubbling |
| 13 | + - `selfDelegateMulticallBytes`: Solidity-style `multicall(bytes[])` over a |
| 14 | + calldata bytes-array parameter, using `delegatecall(address(), data)` for |
| 15 | + each element and bubbling exact revert data |
13 | 16 |
|
14 | 17 | Trust assumption: the target contract's function matches the declared |
15 | 18 | selector and ABI encoding. For `callWithValue`, the caller is responsible for |
16 | 19 | preparing calldata at the supplied memory slice. `callWithValueBytes` copies a |
17 | 20 | bytes parameter into memory before calling. For arbitrary low-level calls, the |
18 | 21 | target contract behavior and calldata ABI are deliberately outside Verity core |
19 | | - and are surfaced as an explicit ECM assumption. |
| 22 | + and are surfaced as an explicit ECM assumption. The multicall helper is |
| 23 | + intentionally scoped to self-delegatecall: storage context is the current |
| 24 | + contract's storage, and no caller-selected implementation address is exposed. |
20 | 25 | -/ |
21 | 26 |
|
22 | 27 | import Compiler.ECM |
@@ -51,6 +56,90 @@ private def bubblingValueCallYul |
51 | 56 | ] |
52 | 57 | ]] |
53 | 58 |
|
| 59 | +def selfDelegateMulticallBytesBody |
| 60 | + (arrayParam ptrName indexName successName rdsName : String) : Except String (List YulStmt) := do |
| 61 | + if arrayParam.isEmpty then |
| 62 | + throw "selfDelegateMulticallBytes: arrayParam must be non-empty" |
| 63 | + let ptrExpr := YulExpr.ident ptrName |
| 64 | + let indexExpr := YulExpr.ident indexName |
| 65 | + let arrayDataOffset := YulExpr.ident s!"{arrayParam}_data_offset" |
| 66 | + let arrayLength := YulExpr.ident s!"{arrayParam}_length" |
| 67 | + let offsetTableBytes := YulExpr.call "mul" [arrayLength, YulExpr.lit 32] |
| 68 | + let elementOffsetSlot := YulExpr.call "add" [ |
| 69 | + arrayDataOffset, |
| 70 | + YulExpr.call "mul" [indexExpr, YulExpr.lit 32] |
| 71 | + ] |
| 72 | + let paddedSizeExpr := YulExpr.call "and" [ |
| 73 | + YulExpr.call "add" [YulExpr.ident "__mc_data_size", YulExpr.lit 31], |
| 74 | + YulExpr.call "not" [YulExpr.lit 31] |
| 75 | + ] |
| 76 | + pure [ |
| 77 | + YulStmt.let_ ptrName (YulExpr.call "mload" [YulExpr.lit freeMemoryPointer]), |
| 78 | + YulStmt.for_ |
| 79 | + [YulStmt.let_ indexName (YulExpr.lit 0)] |
| 80 | + (YulExpr.call "lt" [indexExpr, arrayLength]) |
| 81 | + [YulStmt.assign indexName (YulExpr.call "add" [indexExpr, YulExpr.lit 1])] |
| 82 | + [ |
| 83 | + YulStmt.let_ "__mc_rel_offset" (YulExpr.call "calldataload" [elementOffsetSlot]), |
| 84 | + YulStmt.if_ (YulExpr.call "lt" [YulExpr.ident "__mc_rel_offset", offsetTableBytes]) [ |
| 85 | + YulStmt.expr (YulExpr.call "revert" [YulExpr.lit 0, YulExpr.lit 0]) |
| 86 | + ], |
| 87 | + YulStmt.if_ (YulExpr.call "gt" [ |
| 88 | + YulExpr.ident "__mc_rel_offset", |
| 89 | + YulExpr.call "sub" [ |
| 90 | + YulExpr.call "not" [YulExpr.lit 0], |
| 91 | + arrayDataOffset |
| 92 | + ] |
| 93 | + ]) [ |
| 94 | + YulStmt.expr (YulExpr.call "revert" [YulExpr.lit 0, YulExpr.lit 0]) |
| 95 | + ], |
| 96 | + YulStmt.let_ "__mc_head_offset" (YulExpr.call "add" [arrayDataOffset, YulExpr.ident "__mc_rel_offset"]), |
| 97 | + YulStmt.if_ (YulExpr.call "gt" [ |
| 98 | + YulExpr.ident "__mc_head_offset", |
| 99 | + YulExpr.call "sub" [YulExpr.call "calldatasize" [], YulExpr.lit 32] |
| 100 | + ]) [ |
| 101 | + YulStmt.expr (YulExpr.call "revert" [YulExpr.lit 0, YulExpr.lit 0]) |
| 102 | + ], |
| 103 | + YulStmt.let_ "__mc_data_size" (YulExpr.call "calldataload" [YulExpr.ident "__mc_head_offset"]), |
| 104 | + YulStmt.let_ "__mc_data_offset" (YulExpr.call "add" [YulExpr.ident "__mc_head_offset", YulExpr.lit 32]), |
| 105 | + YulStmt.if_ (YulExpr.call "gt" [ |
| 106 | + YulExpr.ident "__mc_data_size", |
| 107 | + YulExpr.call "sub" [YulExpr.call "calldatasize" [], YulExpr.ident "__mc_data_offset"] |
| 108 | + ]) [ |
| 109 | + YulStmt.expr (YulExpr.call "revert" [YulExpr.lit 0, YulExpr.lit 0]) |
| 110 | + ], |
| 111 | + YulStmt.expr (YulExpr.call "calldatacopy" [ |
| 112 | + ptrExpr, |
| 113 | + YulExpr.ident "__mc_data_offset", |
| 114 | + YulExpr.ident "__mc_data_size" |
| 115 | + ]), |
| 116 | + YulStmt.expr (YulExpr.call "mstore" [ |
| 117 | + YulExpr.lit freeMemoryPointer, |
| 118 | + YulExpr.call "add" [ptrExpr, paddedSizeExpr] |
| 119 | + ]), |
| 120 | + YulStmt.let_ successName (YulExpr.call "delegatecall" [ |
| 121 | + YulExpr.call "gas" [], |
| 122 | + YulExpr.call "address" [], |
| 123 | + ptrExpr, |
| 124 | + YulExpr.ident "__mc_data_size", |
| 125 | + YulExpr.lit 0, |
| 126 | + YulExpr.lit 0 |
| 127 | + ]), |
| 128 | + YulStmt.if_ (YulExpr.call "iszero" [YulExpr.ident successName]) [ |
| 129 | + YulStmt.let_ rdsName (YulExpr.call "returndatasize" []), |
| 130 | + YulStmt.expr (YulExpr.call "returndatacopy" [ |
| 131 | + YulExpr.lit 0, YulExpr.lit 0, YulExpr.ident rdsName |
| 132 | + ]), |
| 133 | + YulStmt.expr (YulExpr.call "revert" [YulExpr.lit 0, YulExpr.ident rdsName]) |
| 134 | + ] |
| 135 | + ] |
| 136 | + ] |
| 137 | + |
| 138 | +theorem selfDelegateMulticallBytesBody_empty_param : |
| 139 | + selfDelegateMulticallBytesBody "" "__mc_ptr" "__mc_i" "__mc_success" "__mc_rds" = |
| 140 | + Except.error "selfDelegateMulticallBytes: arrayParam must be non-empty" := by |
| 141 | + rfl |
| 142 | + |
54 | 143 | /-- Generic external call with single uint256 return. |
55 | 144 | ABI-encodes `selector(args...)`, calls/staticcalls target, reverts on failure, |
56 | 145 | validates returndatasize >= 32, and binds the result. |
@@ -374,4 +463,32 @@ def callWithValueBytesModule (bytesParam : String) : ExternalCallModule where |
374 | 463 | def callWithValueBytes (target value : Expr) (bytesParam : String) : Stmt := |
375 | 464 | .ecm (callWithValueBytesModule bytesParam) [target, value] |
376 | 465 |
|
| 466 | +/-- Source-level `multicall(bytes[])` helper for self-delegatecall routers. |
| 467 | +
|
| 468 | + The named parameter must be a `bytes[]` ABI parameter. The generated Yul |
| 469 | + walks the ABI offset table, copies each element payload into memory, then |
| 470 | + executes `delegatecall(gas(), address(), ptr, size, 0, 0)`. On failure it |
| 471 | + forwards returndata exactly with `returndatacopy(0, 0, returndatasize())` |
| 472 | + followed by `revert(0, returndatasize())`. |
| 473 | +
|
| 474 | + Unlike the general `Expr.delegatecall` surface, this module has no dynamic |
| 475 | + implementation address and is intended for same-contract multicall only. -/ |
| 476 | +def selfDelegateMulticallBytesModule (arrayParam : String) : ExternalCallModule where |
| 477 | + name := "selfDelegateMulticallBytes" |
| 478 | + numArgs := 0 |
| 479 | + resultVars := [] |
| 480 | + writesState := true |
| 481 | + readsState := true |
| 482 | + axioms := ["self_delegate_multicall_bytes_revert_bubbling"] |
| 483 | + compile := fun _ctx args => do |
| 484 | + match args with |
| 485 | + | [] => |
| 486 | + selfDelegateMulticallBytesBody arrayParam "__mc_ptr" "__mc_i" "__mc_success" "__mc_rds" |
| 487 | + | _ => |
| 488 | + throw "selfDelegateMulticallBytes expects 0 arguments" |
| 489 | + |
| 490 | +/-- Convenience constructor for self-delegatecall `multicall(bytes[])`. -/ |
| 491 | +def selfDelegateMulticallBytes (arrayParam : String) : Stmt := |
| 492 | + .ecm (selfDelegateMulticallBytesModule arrayParam) [] |
| 493 | + |
377 | 494 | end Compiler.Modules.Calls |
0 commit comments