Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
188 commits
Select commit Hold shift + click to select a range
5159714
[pallet-revive] Enforce weight limit on dry-run RPC calls
pgherveou Jan 30, 2026
0dbe292
Merge branch 'master' into pg/dry-run-limit
pgherveou Jan 30, 2026
9863ccf
Update from github-actions[bot] running command 'prdoc --audience run…
github-actions[bot] Jan 30, 2026
5a19887
fix clippy
pgherveou Feb 1, 2026
6958438
Merge branch 'master' into pg/dry-run-limit
pgherveou Feb 3, 2026
68f3951
wip: eip7702 in pallet-revive
Wizdave97 Jan 20, 2026
3256eb1
fix tests
Wizdave97 Jan 20, 2026
4fc3fd5
add signature verification tests
Wizdave97 Jan 20, 2026
a44c297
fix refunds
Wizdave97 Jan 20, 2026
a274d85
nit
Wizdave97 Jan 21, 2026
5800853
refactor exports
Wizdave97 Jan 21, 2026
a35da08
nit
Wizdave97 Jan 21, 2026
8dfa361
wip: eip7702 in pallet-revive
Wizdave97 Jan 20, 2026
2c307d2
fix tests
Wizdave97 Jan 20, 2026
90e6a45
nit
Wizdave97 Jan 21, 2026
5a62904
remove some unused items
Wizdave97 Jan 21, 2026
d4e0c1f
add some runtime tests
Wizdave97 Jan 21, 2026
73dc658
simple refactors
Wizdave97 Jan 23, 2026
f7bd7e9
consolidate rlp encoding of authorization list entry
Wizdave97 Jan 23, 2026
fff6f2f
use an enum variant to represent delegation target
Wizdave97 Jan 23, 2026
3d81cb9
refactor signature verification
Wizdave97 Jan 23, 2026
3953619
add benchmarks for authorization processing
Wizdave97 Jan 23, 2026
cf66d2c
revert fmt changes
Wizdave97 Jan 23, 2026
f81fe5d
final fmt fix
Wizdave97 Jan 23, 2026
8edf11d
remove remaining cosmetic changes
Wizdave97 Jan 23, 2026
2f439b5
rename some variables
Wizdave97 Jan 23, 2026
4d76cff
add benchmarks
Wizdave97 Jan 26, 2026
34962e3
improved benchmarks
Wizdave97 Jan 29, 2026
a4d8903
update weights
Wizdave97 Jan 29, 2026
69fbfd3
lint and refactor models
pgherveou Jan 29, 2026
59a7ccc
refactor: Add dry_run authorization processing and split eth_call ext…
pgherveou Jan 29, 2026
8ca231a
refactor: Rename SignedAuthorizationListEntry to AuthorizationListEntry
pgherveou Jan 29, 2026
2c3d4f4
refactor: Flatten AuthorizationListEntry and update sign_authorizatio…
pgherveou Jan 29, 2026
27fc298
fix: Use existential deposit instead of nonce increment to create acc…
pgherveou Jan 29, 2026
d51d4fd
refactor: Include account existence check in validate_authorization
pgherveou Jan 29, 2026
4d75508
style: Revert pure formatting changes to match master style
pgherveou Jan 29, 2026
d81628e
simplify comments
pgherveou Jan 29, 2026
84e8b32
refactor: Improve weight-to-gas conversion and simplify nonce validation
pgherveou Jan 29, 2026
2c3cb4c
rm comments
pgherveou Jan 29, 2026
288cd72
rm comments
pgherveou Jan 29, 2026
97f7fe1
wip
pgherveou Jan 30, 2026
6c35177
lint
pgherveou Jan 30, 2026
47ff398
wip
pgherveou Jan 30, 2026
a0d37e4
nit rm comments
pgherveou Jan 30, 2026
30bd087
import LOG_TARGET
pgherveou Jan 30, 2026
8fa0f69
nit
pgherveou Jan 30, 2026
529c89b
nit move nonce logic
pgherveou Feb 2, 2026
673a57d
rm diff
pgherveou Feb 2, 2026
e37c3b7
fix
pgherveou Feb 2, 2026
c7ece0c
fix
pgherveou Feb 2, 2026
0602921
fix
pgherveou Feb 2, 2026
cd12e95
nit
pgherveou Feb 2, 2026
583a803
fix
pgherveou Feb 2, 2026
a128bfc
simplify
pgherveou Feb 2, 2026
c0fcc8e
fix delegation
pgherveou Feb 2, 2026
2beddf9
fixes
pgherveou Feb 2, 2026
8163050
nit formatting
pgherveou Feb 2, 2026
0741593
fixes
pgherveou Feb 2, 2026
f420488
fixes
pgherveou Feb 2, 2026
70e7015
fix
pgherveou Feb 2, 2026
cb72784
add docstring
pgherveou Feb 2, 2026
c2dd498
refactor(revive): update process_authorizations to use TransactionMeter
pgherveou Feb 2, 2026
d6612fd
refactor(revive): consolidate dry_run_eth_transact metering
pgherveou Feb 2, 2026
bf6cfde
test(revive): update eip7702 tests for TransactionMeter signature
pgherveou Feb 2, 2026
45b733e
fix(revive): move meter creation into closure to fix OutOfGas errors
pgherveou Feb 2, 2026
a8c443f
fix(revive): process authorizations outside transaction context
pgherveou Feb 2, 2026
1ba9024
fix test
pgherveou Feb 3, 2026
1f39c1a
fixes
pgherveou Feb 3, 2026
55df8b2
wip
pgherveou Feb 4, 2026
be625e8
Merge branch 'master' into pg/eip-7702
pgherveou Feb 4, 2026
8157ad2
wip
pgherveou Feb 4, 2026
8813ce5
fix diff
pgherveou Feb 4, 2026
273b392
nit
pgherveou Feb 4, 2026
d756704
fixes
pgherveou Feb 4, 2026
ab4ca11
nit
pgherveou Feb 4, 2026
4bf38ed
wip
pgherveou Feb 4, 2026
36473df
wip
pgherveou Feb 4, 2026
c565eae
nit
pgherveou Feb 9, 2026
9db8ff4
wip
pgherveou Feb 9, 2026
f24545e
add assert
pgherveou Feb 9, 2026
ad18b3d
fix
pgherveou Feb 9, 2026
eda521b
review
pgherveou Feb 10, 2026
e7de8fd
cleanup
pgherveou Feb 10, 2026
96b932d
fixes
pgherveou Feb 10, 2026
03e841b
fix
pgherveou Feb 10, 2026
88231fe
fix
pgherveou Feb 10, 2026
ccfb527
wip
pgherveou Feb 10, 2026
eacec3b
add test
pgherveou Feb 10, 2026
a0dc78b
collapse tests
pgherveou Feb 10, 2026
a5e99f1
added migration
pgherveou Feb 10, 2026
2b26a0c
wip deposit fixes
pgherveou Feb 10, 2026
2185782
fixes
pgherveou Feb 11, 2026
9f7576d
nit
pgherveou Feb 11, 2026
3b9eb7b
wip
pgherveou Feb 11, 2026
3470741
fix
pgherveou Feb 11, 2026
0238632
Merge remote-tracking branch 'origin/master' into pg/eip-7702
pgherveou Feb 12, 2026
f4968d7
fix: pass ExecConfig by reference after merge
pgherveou Feb 12, 2026
aa8aeea
refactor(revive): use uint64 in Counter.sol fixture
pgherveou Feb 12, 2026
0f5171c
refactor(revive): replace k256 with sp_core::ecdsa in benchmarks
pgherveou Feb 12, 2026
b81801e
refactor(revive): add debug log for eth_transact substrate tx hash
pgherveou Feb 12, 2026
f855155
fix(revive): minor formatting fixes
pgherveou Feb 12, 2026
4f3da1f
fix(revive): fix Transaction7702Signed RLP decoding field order
pgherveou Feb 12, 2026
775d3a3
refactor(revive): upgrade to Rust 2024 edition
pgherveou Feb 12, 2026
6856ddf
Merge branch 'pg/revive-cleanup' into pg/eip-7702
pgherveou Feb 12, 2026
bbb49c7
refactor(revive): use uint64 in Counter.sol fixture
pgherveou Feb 12, 2026
baf2ce0
refactor(revive): replace k256 with sp_core::ecdsa in benchmarks
pgherveou Feb 12, 2026
24b89ef
refactor(revive): add debug log for eth_transact substrate tx hash
pgherveou Feb 12, 2026
30876c2
fix(revive): minor formatting fixes
pgherveou Feb 12, 2026
825d3b6
fix(revive): fix Transaction7702Signed RLP decoding field order
pgherveou Feb 12, 2026
490aa77
refactor(revive): upgrade to Rust 2024 edition
pgherveou Feb 12, 2026
bc56bb5
refactor(revive): use if-let chains from Rust 2024 edition
pgherveou Feb 13, 2026
6a6d944
Update from github-actions[bot] running command 'prdoc --audience run…
github-actions[bot] Feb 13, 2026
ee93148
Merge remote-tracking branch 'origin/pg/revive-cleanup' into pg/eip-7702
pgherveou Feb 13, 2026
63a7418
PR review
pgherveou Feb 13, 2026
4e98087
fixes
pgherveou Feb 13, 2026
211d333
Apply suggestion from @pgherveou
pgherveou Feb 13, 2026
cc9b2f9
fix(ci): revert accidental cargo update and bump parity-publish to 0.…
pgherveou Feb 13, 2026
2c79f83
Merge branch 'master' into pg/revive-cleanup
pgherveou Feb 13, 2026
b060e30
restore lock
pgherveou Feb 13, 2026
a35e2cc
fix(ci): downgrade parity-publish to 0.10.6 for Rust 1.88 compatibility
pgherveou Feb 13, 2026
309d83a
fix: add cargo-features for edition2024 in revive crates
pgherveou Feb 13, 2026
89b2ddc
fix(prdoc): add validate: false for Transaction7702 API change
pgherveou Feb 13, 2026
244be53
fix: remove unnecessary cargo-features for edition2024
pgherveou Feb 13, 2026
d2c0d41
Revert "fix: remove unnecessary cargo-features for edition2024"
pgherveou Feb 13, 2026
7c69b01
Merge branch 'master' into pg/revive-cleanup
pgherveou Feb 13, 2026
1d8b608
Merge remote-tracking branch 'origin/pg/revive-cleanup' into pg/eip-7702
pgherveou Feb 13, 2026
e9b41b3
Merge master and resolve conflicts
pgherveou Feb 14, 2026
a3c0177
pallet-revive: track EIP-7702 authorization deposits in TransactionMeter
pgherveou Mar 6, 2026
ee2e316
Merge master and resolve conflicts
pgherveou Mar 6, 2026
c1f4260
rm file
pgherveou Mar 6, 2026
ae3ed1f
fixes
pgherveou Mar 6, 2026
8ee1b51
Review
pgherveou Mar 6, 2026
470ba8b
pallet-revive: inline eth_call_impl into eth_call
pgherveou Mar 6, 2026
838070a
pallet-revive: fix EIP-7702 test seeds and type mismatch
pgherveou Mar 6, 2026
dee7ae1
pallet-revive: remove redundant empty check before process_authorizat…
pgherveou Mar 6, 2026
cba5f4c
refactor
pgherveou Mar 6, 2026
a916c93
fmt
pgherveou Mar 6, 2026
a7d4709
pallet-revive: skip delegation lookup for constructors
pgherveou Mar 6, 2026
3aab19f
pallet-revive: consolidate test_process_authorizations into Delegatio…
pgherveou Mar 6, 2026
e4de7cb
pallet-revive: fix benchmark compilation with runtime-benchmarks feature
pgherveou Mar 6, 2026
9013fa3
Update tests
pgherveou Mar 9, 2026
0a80b63
Update from github-actions[bot] running command 'prdoc --audience run…
github-actions[bot] Mar 9, 2026
4ae7ba7
fixes
pgherveou Mar 9, 2026
80e016e
fmt
pgherveou Mar 9, 2026
0d7a2f0
fix clippy
pgherveou Mar 9, 2026
3109b99
fix
pgherveou Mar 9, 2026
8964f13
nit fix set&clear_delegation
pgherveou Mar 9, 2026
c0efe74
fix test
pgherveou Mar 9, 2026
1ad94f3
fix bench compilation
pgherveou Mar 9, 2026
5e32993
fix
pgherveou Mar 9, 2026
8a38dd1
fix
pgherveou Mar 9, 2026
e8de9e2
update
pgherveou Mar 9, 2026
e2acd85
Update from github-actions[bot] running command 'bench --runtime dev …
github-actions[bot] Mar 9, 2026
f8020ab
update
pgherveou Mar 9, 2026
f194277
fix
pgherveou Mar 9, 2026
c774679
fix build
pgherveou Mar 9, 2026
1c156c1
CI fixes
pgherveou Mar 9, 2026
187c4e5
Merge branch 'master' into pg/eip-7702
pgherveou Mar 9, 2026
e10ca59
fix
pgherveou Mar 9, 2026
e3c5ca7
simplify
pgherveou Mar 9, 2026
dbde3de
nit
pgherveou Mar 9, 2026
a1774af
rollback
pgherveou Mar 9, 2026
622bbae
simplify
pgherveou Mar 9, 2026
b91690f
simplify
pgherveou Mar 9, 2026
9a9eb2d
fixes
pgherveou Mar 9, 2026
59d7962
use a new variant instead of updating the EOA
pgherveou Mar 10, 2026
f7b720e
fix fmt
pgherveou Mar 10, 2026
4159078
Skipping clear delegation for non-existent account
pgherveou Mar 10, 2026
4724849
nit
pgherveou Mar 10, 2026
704437b
clean up
pgherveou Mar 10, 2026
1254be7
fix
pgherveou Mar 10, 2026
f537d3e
fmt
pgherveou Mar 10, 2026
67193d8
fix tests
pgherveou Mar 10, 2026
aadeda5
fix breaking change in tracing
pgherveou Mar 10, 2026
0c11f70
attempt to fix differential tests
pgherveou Mar 11, 2026
9e6aaf0
Fix
pgherveou Mar 12, 2026
7bfab2f
PR review
pgherveou Mar 12, 2026
b066266
minor security reco
pgherveou Mar 20, 2026
6ea3c7a
delete prdoc
0xRVE May 29, 2026
c267f49
Merge remote-tracking branch 'origin/master' into rve/eip-7702
0xRVE May 29, 2026
6f94019
format
0xRVE May 29, 2026
360bf96
fix after merge
0xRVE May 29, 2026
44cc282
Update from github-actions[bot] running command 'prdoc --audience run…
github-actions[bot] May 29, 2026
6c7391f
fix weights
0xRVE May 29, 2026
220f125
Merge branch 'rve/eip-7702' of github.com-work:paritytech/polkadot-sd…
0xRVE May 29, 2026
93ef28d
Update from github-actions[bot] running command 'bench --runtime asse…
github-actions[bot] May 29, 2026
28978c6
empty
0xRVE May 31, 2026
e12bf10
empty
0xRVE Jun 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Large diffs are not rendered by default.

94 changes: 94 additions & 0 deletions prdoc/pr_12229.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
title: '[pallet-revive] EIP-7702 (continued)'
doc:
- audience: Runtime Dev
description: "Continuation of #10851.\nevm-test-suite PR: https://github.com/paritytech/evm-test-suite/pull/141\n\
\nThis PR implements [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) (\"Set\
\ EOA Account Code\") for `pallet-revive`, enabling Externally Owned Accounts\
\ (EOAs) to designate a contract whose code will be executed on their behalf when\
\ they are called.\n\n## What is EIP-7702?\n\nEIP-7702 lets an EOA sign an authorization\
\ that says \"when someone calls me, execute contract X's code\". When a transaction\
\ includes these authorizations, any subsequent calls to the delegated EOA will\
\ execute the target contract's code \u2014 but using the EOA's own storage and\
\ balance. This enables smart account patterns (batching, gas sponsorship, etc.)\
\ without requiring [account abstraction (ERC-4337)](https://eips.ethereum.org/EIPS/eip-4337)\
\ or proxy contracts.\n\nFor background:\n- [EIP-7702 spec](https://eips.ethereum.org/EIPS/eip-7702)\n\
- [Vitalik's \"Why we need wide adoption of account abstraction\"](https://vitalik.eth.limo/general/2023/06/09/three_transitions.html)\n\
- [EIP-7702 overview (ethereum.org)](https://ethereum.org/en/roadmap/account-abstraction/#eip-7702)\n\
\n## Changes\n\n### Pallet integration\n\nThe `eth_call` dispatchable now accepts\
\ and processes an authorization list before executing the call.\n\nThe `authorization_list`\
\ parameter is a `BoundedVec` capped at `limits::AUTHORIZATION_LIST_MAX` (256).\
\ In normal operation the size is already constrained by the gas-validation logic\
\ in `eth_transact` (each entry costs at least `worst_case_delegation_deposit`),\
\ but the `BoundedVec` is a defense-in-depth bound for any future call path that\
\ might bypass that check. Oversized lists are rejected in `into_call` with `InvalidTransaction::Call`.\n\
\n> **Note**: Updating the `eth_call` signature is safe \u2014 it is not dispatched\
\ directly by users but is the inner call of `eth_transact`, which is signed by\
\ an Ethereum wallet and submitted via `eth-rpc`.\n\n### Authorization processing\n\
\nThe `Transaction7702` type (with its `authorization_list`) was already defined\
\ and parsed; this PR adds the actual processing logic.\n\n`process_authorizations`\
\ validates each entry (chain ID, signature, nonce, account type), creates the\
\ authority account if needed, and sets or clears the delegation. Invalid authorizations\
\ are silently skipped per spec.\n\n**Key semantics:**\n- **Authorizations always\
\ succeed.** A type-4 transaction is only valid if the origin has enough gas to\
\ cover the worst case for every authorization entry. Processing itself cannot\
\ fail.\n- **Authorizations are not reverted.** If the transaction's call fails\
\ and its state changes revert, the delegation changes from authorization processing\
\ persist.\n\n**Revive-specific complexity.** In Revive the per-authorization\
\ pre-dispatch weight/gas is higher than in EVM because:\n- An authorization can\
\ touch a **new account**, so the gas must cover the existential deposit (ED)\
\ for account creation.\n- Code is shared across contracts (same blob for init\
\ and runtime), so delegating to a contract requires a **code lockup deposit**\
\ and increments a **refcount** on the code hash to prevent deletion while delegated.\n\
\n### Storage changes\n\nA new **`AccountType::DelegatedEOA`** variant is introduced:\n\
- `delegate_target: Option<H160>` \u2014 the contract address this EOA delegates\
\ to.\n- `contract_info: ContractInfo` \u2014 storage accounting (child trie,\
\ base deposit) for delegated EOAs.\n\n**Key behaviors:**\n- Once delegated, an\
\ account transitions from `EOA` to `DelegatedEOA` permanently.\n- Clearing delegation\
\ sets `delegate_target = None` but the account stays `DelegatedEOA`.\n- Delegated\
\ EOAs get their own child trie for storage, keyed by the EOA's address (not the\
\ target).\n- Re-delegating to a different target preserves the child trie \u2014\
\ storage survives across target changes.\n- Code refcounts are managed: setting\
\ delegation increments the target's code refcount, clearing it decrements.\n\n\
**Deposit lifecycle.** Clearing a delegation refunds the `storage_base_deposit`\
\ (the code-lockup portion) but preserves the child trie and per-item storage\
\ accounting. To recover the per-item deposit, the user must re-delegate to a\
\ contract that lets them clear the stored items through the normal storage path.\n\
\n### Execution changes\n\nDuring a call, the runtime resolves delegation:\n-\
\ If the callee is an EOA with a `delegate_target`, code is loaded from the target\
\ contract but execution uses the EOA's storage.\n- Constructors skip delegation\
\ lookup (cannot deploy via delegation).\n- **No chain following.** Delegation\
\ is resolved at most once. If A delegates to B and B delegates to C, calling\
\ A looks up code at B directly (without following B's delegation to C). If B\
\ is an EOA, calling A behaves like calling a plain EOA.\n\n### Benchmarks\n\n\
- `process_new_account_authorization(n)` \u2014 worst case: creates `n` new accounts\
\ with delegations.\n- `process_existing_account_authorization(n)` \u2014 best\
\ case: processes `n` authorizations for existing accounts (used for weight-refund\
\ calculation).\n\n### RPC integration\n\n- `eth-rpc` supports submitting and\
\ dry-running EIP-7702 transactions.\n- `eth_getCode` returns the `0xef0100 ||\
\ address` prefix for delegated EOAs (per EIP-7702 spec).\n\n## Tests\n\nTest\
\ coverage includes:\n- **Validation**: invalid chain_id, nonce mismatch, duplicate\
\ signers, chain_id 0 wildcard, contract accounts rejected.\n- **Delegation lifecycle**:\
\ set, clear, re-delegate, delegation resolution during calls.\n- **Storage persistence**:\
\ re-delegation preserves storage across different targets; clear-then-redelegate\
\ preserves the trie and recovers read/write access to previously written state\
\ (`storage_persists_across_clear_and_redelegate`).\n- **Deposit accounting**:\
\ delegation charges, clearing refunds, re-delegation adjustments, code-refcount\
\ tracking, EOA \u2192 EOA delegations charge no deposit, multiple authorities\
\ to the same target each get their own deposit.\n- **Edge cases**: delegation\
\ chains not followed, SELFDESTRUCT preserves delegation, constructors skip delegation,\
\ delegation to nonexistent addresses is a no-op."
crates:
- name: pallet-revive-eth-rpc
bump: major
- name: pallet-revive
bump: major
- name: assets-common
bump: major
- name: pallet-xcm-precompiles
bump: major
- name: pallet-assets-precompiles
bump: major
- name: pallet-revive-fixtures
bump: major
- name: asset-hub-westend-runtime
bump: major
2 changes: 2 additions & 0 deletions substrate/frame/revive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ alloy-consensus = { workspace = true, default-features = true }
array-bytes = { workspace = true, default-features = true }
assert_matches = { workspace = true }
itertools = { workspace = true }
k256 = { features = ["alloc", "ecdsa"], workspace = true }
pretty_assertions = { workspace = true }
secp256k1 = { workspace = true, features = ["recovery"] }
serde_json = { workspace = true }
Expand Down Expand Up @@ -104,6 +105,7 @@ std = [
"frame-support/std",
"frame-system/std",
"humantime-serde",
"k256",
"k256?/std",
"log/std",
"num-bigint/std",
Expand Down
1 change: 1 addition & 0 deletions substrate/frame/revive/rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ alloy-rpc-types = { workspace = true, default-features = true, features = ["trac
alloy-signer-local = { workspace = true, default-features = true }
env_logger = { workspace = true }
frame-system = { workspace = true, default-features = true }
k256 = { features = ["alloc", "ecdsa"], workspace = true }
pallet-revive-fixtures = { workspace = true, default-features = true }
pretty_assertions = { workspace = true }
revive-dev-node = { workspace = true }
Expand Down
55 changes: 53 additions & 2 deletions substrate/frame/revive/rpc/src/example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub enum TransactionType {
Eip2930,
Eip1559,
Eip4844,
Eip7702,
}

/// Transaction builder.
Expand All @@ -37,6 +38,7 @@ pub struct TransactionBuilder<Client: EthRpcClient + Sync + Send> {
input: Bytes,
to: Option<H160>,
nonce: Option<U256>,
authorization_list: Vec<AuthorizationListEntry>,
gas: Option<U256>,
mutate: Box<dyn FnOnce(&mut TransactionUnsigned)>,
}
Expand All @@ -49,6 +51,11 @@ pub struct SubmittedTransaction<Client: EthRpcClient + Sync + Send> {
}

impl<Client: EthRpcClient + Sync + Send> SubmittedTransaction<Client> {
/// Create from a raw transaction hash and gas limit.
pub fn from_raw(client: Arc<Client>, hash: H256, gas: U256) -> Self {
Self { tx: GenericTransaction { gas: Some(gas), ..Default::default() }, hash, client }
}

/// Get the hash of the transaction.
pub fn hash(&self) -> H256 {
self.hash
Expand Down Expand Up @@ -101,6 +108,7 @@ impl<Client: EthRpcClient + Send + Sync> TransactionBuilder<Client> {
input: Bytes::default(),
to: None,
nonce: None,
authorization_list: vec![],
gas: None,
mutate: Box::new(|_| {}),
}
Expand Down Expand Up @@ -135,6 +143,12 @@ impl<Client: EthRpcClient + Send + Sync> TransactionBuilder<Client> {
self
}

/// Set the authorization list (EIP-7702).
pub fn authorization_list(mut self, list: Vec<AuthorizationListEntry>) -> Self {
self.authorization_list = list;
self
}

/// Set the gas limit explicitly, skipping eth_estimateGas.
pub fn gas(mut self, gas: U256) -> Self {
self.gas = Some(gas);
Expand All @@ -149,7 +163,7 @@ impl<Client: EthRpcClient + Send + Sync> TransactionBuilder<Client> {

/// Call eth_call to get the result of a view function
pub async fn eth_call(self) -> anyhow::Result<Vec<u8>> {
let TransactionBuilder { client, signer, value, input, to, .. } = self;
let TransactionBuilder { client, signer, value, input, to, authorization_list, .. } = self;

let from = signer.address();
let result = client
Expand All @@ -159,6 +173,7 @@ impl<Client: EthRpcClient + Send + Sync> TransactionBuilder<Client> {
input: input.into(),
value: Some(value),
to,
authorization_list,
..Default::default()
},
None,
Expand All @@ -179,7 +194,17 @@ impl<Client: EthRpcClient + Send + Sync> TransactionBuilder<Client> {
self,
tx_type: TransactionType,
) -> anyhow::Result<SubmittedTransaction<Client>> {
let TransactionBuilder { client, signer, value, input, to, nonce, gas, mutate } = self;
let TransactionBuilder {
client,
signer,
value,
input,
to,
nonce,
authorization_list,
gas,
mutate,
} = self;

let from = signer.address();
let chain_id = client.chain_id().await?;
Expand All @@ -193,6 +218,13 @@ impl<Client: EthRpcClient + Send + Sync> TransactionBuilder<Client> {
.with_context(|| "Failed to fetch account nonce")?
};

let r#type = match tx_type {
TransactionType::Legacy => None,
TransactionType::Eip2930 => Some(Byte(1)),
TransactionType::Eip1559 => Some(Byte(2)),
TransactionType::Eip4844 => Some(Byte(3)),
TransactionType::Eip7702 => Some(Byte(4)),
};
let gas = if let Some(gas) = gas {
gas
} else {
Expand All @@ -204,6 +236,8 @@ impl<Client: EthRpcClient + Send + Sync> TransactionBuilder<Client> {
value: Some(value),
gas_price: Some(gas_price),
to,
r#type,
authorization_list: authorization_list.clone(),
..Default::default()
},
None,
Expand Down Expand Up @@ -275,6 +309,23 @@ impl<Client: EthRpcClient + Send + Sync> TransactionBuilder<Client> {
}
.into()
},
TransactionType::Eip7702 => {
let to = to.ok_or_else(|| {
anyhow::anyhow!("EIP-7702 transactions require a destination address")
})?;
Transaction7702Unsigned {
gas,
nonce,
to,
value,
input,
max_fee_per_gas: gas_price,
chain_id,
authorization_list,
..Default::default()
}
.into()
},
};
mutate(&mut unsigned_tx);

Expand Down
118 changes: 118 additions & 0 deletions substrate/frame/revive/rpc/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ async fn run_all_eth_rpc_tests_inner() -> anyhow::Result<()> {
test_multiple_transactions_in_block,
test_mixed_evm_substrate_transactions,
test_runtime_pallets_address_upload_code,
test_eip7702_delegation_flow,
test_subscribe_new_heads,
test_subscribe_new_heads_multiple_blocks,
test_subscribe_logs,
Expand Down Expand Up @@ -1126,6 +1127,123 @@ async fn test_runtime_pallets_address_upload_code() -> anyhow::Result<()> {
Ok(())
}

/// Full EIP-7702 integration test:
/// 1. Deploy Counter contract
/// 2. Delegate Alice → Counter via 7702 tx with authorization list
/// 3. Call setNumber(42) on Alice (writes to Alice's storage via Counter code)
/// 4. Read number() from Alice → returns 42
/// 5. Clear delegation (authorization with zero address)
/// 6. Read from Alice → returns empty (no code)
async fn test_eip7702_delegation_flow() -> anyhow::Result<()> {
use crate::example::TransactionType;
use pallet_revive::{evm::Account, precompiles::alloy::sol_types::SolCall};
use pallet_revive_fixtures::Counter;

let client = Arc::new(SharedResources::client().await);
let alith = Account::default();

// Deploy Counter contract
let (counter_code, _) = pallet_revive_fixtures::compile_module_with_type(
"Counter",
pallet_revive_fixtures::FixtureType::Solc,
)?;
let nonce = client.get_transaction_count(alith.address(), BlockTag::Latest.into()).await?;
let tx = TransactionBuilder::new(client.clone())
.input(counter_code.to_vec())
.send()
.await?;
tx.wait_for_receipt().await?;
let counter_addr = create1(&alith.address(), nonce.try_into().unwrap());

// Create authority account with known seed
let seed = [0xAA; 32];
let authority = Account::from_secret_key(seed);
let authority_key = k256::ecdsa::SigningKey::from_bytes(&seed.into()).unwrap();

// Fund the authority
let tx = TransactionBuilder::new(client.clone())
.value(U256::from(10_000_000_000_000_000_000u128))
.to(authority.address())
.send()
.await?;
tx.wait_for_receipt().await?;

let chain_id = client.chain_id().await?;

// --- Step 1: Delegate authority → Counter via 7702 tx ---
let auth_nonce = client
.get_transaction_count(authority.address(), BlockTag::Latest.into())
.await?;
let auth = pallet_revive::evm::eip7702::sign_authorization(
&authority_key,
chain_id,
counter_addr,
auth_nonce,
);
TransactionBuilder::new(client.clone())
.to(alith.address())
.authorization_list(vec![auth])
.send_with_type(TransactionType::Eip7702)
.await?
.wait_for_receipt()
.await?;

// Verify delegation is active: eth_getCode should return the delegation indicator
let code = client.get_code(authority.address(), BlockTag::Latest.into()).await?;
let mut expected_prefix = vec![0xef, 0x01, 0x00];
expected_prefix.extend_from_slice(counter_addr.as_bytes());
assert_eq!(code.0, expected_prefix, "authority should have delegation indicator code");

// --- Step 2: Call setNumber(42) on the authority address ---
let tx = TransactionBuilder::new(client.clone())
.to(authority.address())
.input(Counter::setNumberCall { newNumber: 42u64 }.abi_encode())
.send()
.await?;
tx.wait_for_receipt().await?;

// --- Step 3: Read number() from authority → should return 42 ---
let result = TransactionBuilder::new(client.clone())
.to(authority.address())
.input(Counter::numberCall {}.abi_encode())
.eth_call()
.await?;
let number = Counter::numberCall::abi_decode_returns(&result).unwrap();
assert_eq!(number, 42u64, "number() should return 42 after setNumber");

// --- Step 4: Clear delegation via 7702 tx with zero address ---
let auth_nonce = client
.get_transaction_count(authority.address(), BlockTag::Latest.into())
.await?;
let clear_auth = pallet_revive::evm::eip7702::sign_authorization(
&authority_key,
chain_id,
pallet_revive::evm::Address::zero(),
auth_nonce,
);
TransactionBuilder::new(client.clone())
.to(alith.address())
.authorization_list(vec![clear_auth])
.send_with_type(TransactionType::Eip7702)
.await?
.wait_for_receipt()
.await?;

// --- Step 5: Verify delegation is cleared ---
let code = client.get_code(authority.address(), BlockTag::Latest.into()).await?;
assert!(code.0.is_empty(), "authority should have no code after clearing delegation");

// Calling number() should return empty (no contract code)
let result = TransactionBuilder::new(client.clone())
.to(authority.address())
.input(Counter::numberCall {}.abi_encode())
.eth_call()
.await?;
assert!(result.is_empty(), "call to cleared delegation should return empty data");

Ok(())
}

/// Verify that subscribing to `newHeads` delivers a block header matching the
/// corresponding block fetched via `eth_getBlockByNumber` after a transaction
/// triggers a new block.
Expand Down
Loading
Loading