feat: self-delegate multicall helper (#1997)#2029
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit c199d98. Configure here.
| YulStmt.if_ (YulExpr.call "lt" [YulExpr.ident "__mc_rel_offset", offsetTableBytes]) [ | ||
| YulStmt.expr (YulExpr.call "revert" [YulExpr.lit 0, YulExpr.lit 0]) | ||
| ], | ||
| YulStmt.let_ "__mc_head_offset" (YulExpr.call "add" [arrayDataOffset, YulExpr.ident "__mc_rel_offset"]), |
There was a problem hiding this comment.
Wrong bytes element head base
High Severity
Each bytes[] offset in calldata is relative to the array length word, but __mc_head_offset adds the loaded offset to {arrayParam}_data_offset, which genDynamicParamLoads places 32 bytes after that length word. Standard Solidity-encoded multicall calldata can resolve the wrong element head, so calldatacopy and delegatecall may use incorrect payload bytes.
Reviewed by Cursor Bugbot for commit c199d98. Configure here.
There was a problem hiding this comment.
Re: "Wrong bytes element head base" (High) — this is a false positive.
Per the Solidity ABI spec, bytes[] encodes as enc(k) enc((X[0],…,X[k-1])). The element head offsets live inside the inner tuple enc((X[0],…)), and tuple offsets are measured from the start of that tuple encoding — i.e. from the first head word, which is the byte immediately after the length word — not from the length word itself.
{arrayParam}_data_offset is exactly that position (the first head word, 32 bytes past the length word, as the finding itself notes). So __mc_head_offset = data_offset + __mc_rel_offset resolves the correct element head.
Worked example (bytes[] of two 2-byte elements, length word at byte P):
- length word
P; heads atP+0x20,P+0x40;X[0]len atP+0x60,X[1]len atP+0xA0. data_offset = P+0x20. Encodedhead[0] = 0x40(relative to tuple start), sodata_offset + 0x40 = P+0x60 = X[0]✓.- Using the length word
Pas base (as the finding suggests) would giveP+0x40 = head[1]— i.e. that base is the incorrect one.
This is corroborated by the in-code guard if __mc_rel_offset < arrayLength*32 then revert: the minimum valid relative offset equals the size of the heads table only when offsets are measured from the heads-block start (= data_offset). The emission additionally guards against pre-add wraparound (__mc_rel_offset > not(0) - data_offset), out-of-range head (> calldatasize-32), and out-of-range payload (> calldatasize - data_offset). No fix needed.
|
Bugbot HIGH "Wrong bytes element head base" — assessed as a FALSE POSITIVE (no code change). The finding reads This is corroborated by the existing Adjudicated via concrete ABI offset analysis of |


Summary
Implements issue #1997 —
multicall(bytes[]).Compiler.Modules.Calls.selfDelegateMulticallBytesModule/selfDelegateMulticallBytes: a scopedmulticall(bytes[])ECM that walks checked bytes-array calldata offsets, copies each payload, self-delegatecallsaddress(), and bubbles exact revert returndata.abiBoundary, not generic proxy upgradeability.Proofs
selfDelegateMulticallBytesBody_empty_param; proof-length check passed.Stacking
Based on
task/1993a-checked-arith(#1993 emission), which carries the Tier-1 dynamic-ABI + checked-arith machinery this helper relies on.Test plan
lake build Verity Contracts Compiler PrintAxioms→ "Build completed successfully."make check→ "All checks passed."Note
Medium Risk
Introduces state-writing delegatecall loops and a trusted ECM boundary whose full multicall semantics are documented but not machine-proven; mis-generated Yul or malformed calldata handling could affect contract behavior.
Overview
Adds
Compiler.Modules.Calls.selfDelegateMulticallBytesfor Solidity-stylemulticall(bytes[])on a named calldata parameter. Generated Yul walks the dynamicbytes[]offset table with bounds checks, copies each element, runsdelegatecall(gas(), address(), …)(same contract only—no caller-chosen implementation address), and bubbles revert returndata on failure.Trust reporting registers assumption
self_delegate_multicall_bytes_revert_bubblingunderabiBoundary, so usage does not appear innotModeledProxyUpgradeability. Docs (AXIOMS, TRUST_ASSUMPTIONS, EDSL/API, ECM guide) andCallsTestcompile/Yul/trust-report smoke tests cover the new surface; a smallselfDelegateMulticallBytesBody_empty_paramproof guards empty parameter names.Reviewed by Cursor Bugbot for commit 14684cd. Bugbot is set up for automated code reviews on this repo. Configure here.