Add EIP-7702 SetCode transactions (type 0x04, closes #66)#93
Conversation
EIP-7702 (Pectra) lets an EOA delegate to contract code via a signed
authorization. alloy and viem ship it; this adds parity.
- transaction.Authorization { chain_id, address, nonce, y_parity, r, s }
- transaction.Eip7702Transaction (1559 base + access_list +
authorization_list, non-nullable ), wired into the Transaction
union and serializeForSigning/hashForSigning/serializeSigned at
type byte 0x04
- signer.signAuthorization / signer.hashAuthorization with the EIP's
signing hash keccak256(0x05 || rlp([chain_id, address, nonce])).
y_parity is the 0/1 recovery id (verified: secp256k1.sign returns
R.y parity, not 27/28)
- rpc_transaction.RpcAuthorization + optional
RpcTransaction.authorization_list, parsed best-effort in
parseSingleTransaction
Signing payload (type 0x04):
0x04 || rlp([chain_id, nonce, max_priority_fee_per_gas,
max_fee_per_gas, gas_limit, to, value, data, access_list,
authorization_list{, y_parity, r, s}])
each auth item: [chain_id, address, nonce, y_parity, r, s].
No official EIP-7702 signing vector exists; correctness is established
by sign-then-recover round trips (recovered signer == authority) plus
RLP-shape assertions independently cross-checked (rlp([1,addr,5]) ==
d70194<addr>05). 11 new tests; 811/811 pass on 0.16.0 and 0.17-dev.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughAdds EIP-7702 (type 0x04) SetCode transaction support: authorization hashing/signing (keccak256(0x05||rlp([chain_id,address,nonce]))), new Authorization/Eip7702Transaction types, RPC ChangesEIP-7702 SetCode Transaction Support
Sequence DiagramssequenceDiagram
participant App as Application
participant Signer
participant Hash as hashAuthorization
participant ECDSA
participant TxBuilder as Transaction Builder
participant Provider
App->>Signer: signAuthorization(chain_id, delegate, nonce)
Signer->>Hash: compute authorization digest
Hash->>Hash: RLP encode [chain_id, address, nonce]\nprepend 0x05 magic byte
Hash-->>Signer: [32]u8 digest
Signer->>ECDSA: sign digest
ECDSA-->>Signer: (r, s, y_parity)
Signer-->>App: Authorization
App->>TxBuilder: Eip7702Transaction{to, authorization_list, ...}
TxBuilder->>TxBuilder: serializeSigned()
TxBuilder->>TxBuilder: prefix 0x04\nRLP encode with auth_list\nappend signature fields
TxBuilder-->>App: signed bytes
App->>Provider: sendRawTransaction(bytes)
Provider-->>App: transaction hash
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/provider.zig (1)
886-914:⚠️ Potential issue | 🟠 Major | ⚡ Quick winEnforce the type-0x04 RPC invariants here.
This path currently attaches
authorization_listto any transaction that happens to carryauthorizationList, and it still acceptstype == 0x04withto == null. That producesRpcTransactionstates that contradict the new contracts in this PR.Suggested fix
const type_val: u8 = if (jsonGetString(obj, "type")) |t| try parseHexU8(t) else 0; const chain_id = try parseOptionalHexU64(jsonGetString(obj, "chainId")); - // EIP-7702 authorization list (best-effort: parse if the field is present - // and is an array). Present only on type-0x04 transactions. - const authorization_list = try parseAuthorizationList(allocator, obj); + if (type_val == 4 and to_addr == null) return error.InvalidResponse; + + // EIP-7702 authorization list (best-effort: parse if the field is present + // and is an array). Present only on type-0x04 transactions. + const authorization_list = if (type_val == 4) + try parseAuthorizationList(allocator, obj) + else + null; errdefer if (authorization_list) |list| allocator.free(list);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/provider.zig` around lines 886 - 914, When constructing RpcTransaction enforce the type-0x04 invariants: only attach authorization_list when type_val == 0x04 (call parseAuthorizationList conditionally) and reject/return an error if type_val == 0x04 and to_addr is null; otherwise ensure authorization_list is null for non-0x04 types. Locate parseAuthorizationList, type_val, authorization_list, to_addr and the RpcTransaction return and modify the parsing/validation so authorization_list is produced only for type 0x04 and a missing to_addr for type 0x04 triggers a failure instead of producing an invalid RpcTransaction.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/provider.zig`:
- Around line 942-945: The parsed yParity value (yp_str -> y_parity via
parseHexU8) must be validated to only allow 0 or 1 per EIP-7702; after obtaining
y_parity in the RpcAuthorization parsing path, add a check that returns
error.InvalidResponse if y_parity is not 0 and not 1 (reject values like 0x1b),
so RpcAuthorization entries only accept recovery IDs 0 or 1.
---
Outside diff comments:
In `@src/provider.zig`:
- Around line 886-914: When constructing RpcTransaction enforce the type-0x04
invariants: only attach authorization_list when type_val == 0x04 (call
parseAuthorizationList conditionally) and reject/return an error if type_val ==
0x04 and to_addr is null; otherwise ensure authorization_list is null for
non-0x04 types. Locate parseAuthorizationList, type_val, authorization_list,
to_addr and the RpcTransaction return and modify the parsing/validation so
authorization_list is produced only for type 0x04 and a missing to_addr for type
0x04 triggers a failure instead of producing an invalid RpcTransaction.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 21e07595-4279-421d-85a7-74b44c972c4f
📒 Files selected for processing (6)
CHANGELOG.mddocs/content/docs/transactions.mdxsrc/provider.zigsrc/rpc_transaction.zigsrc/signer.zigsrc/transaction.zig
…ng RPC
A malformed authorizationList entry with yParity outside {0,1} is now
rejected as error.InvalidResponse instead of silently accepted.
What
EIP-7702 (Pectra, live on mainnet) lets an EOA delegate to contract code via a signed authorization. alloy and viem both ship it -- this closes the parity gap.
transaction.Authorization { chain_id, address, nonce, y_parity, r, s }andEip7702Transaction(1559 base + access list +authorization_list, non-nullableto), wired into theTransactionunion and the serialize/hash dispatch at type byte 0x04.signer.signAuthorization/signer.hashAuthorizationimplementing the EIP's signing hash keccak256(0x05 || rlp([chain_id, address, nonce])) (MAGIC = 0x05).y_parityis the 0/1 recovery id.rpc_transaction.RpcAuthorization+ optionalRpcTransaction.authorization_list, parsed best-effort.Payload (type 0x04)
0x04 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to, value, data, access_list, authorization_list{, y_parity, r, s}]); each auth item[chain_id, address, nonce, y_parity, r, s].Correctness (security-critical, independently cross-checked)
EIP-7702 signing authorizes code on an EOA, so it must be exact. No official signing test vector exists in the EIP or execution-spec-tests. Correctness is established by:
secp256k1.recoverAddress(y_parity, r, s)over the auth hash returns the authority's address.rlp([1, addr, 5])==d70194<addr>05, andsecp256k1.signreturnsvas the 0/1 R.y parity (not 27/28), soy_parity = sig.vis correct.Verification
11 new tests (auth hash + shape, sign/recover for two accounts incl. chain_id=0, type-0x04 serialization, empty + 1-item lists, RPC parse). 811/811 pass, 0 skip on 0.16.0 and 0.17-dev. transactions.mdx updated with a signAuthorization example.
Closes #66
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
Tests