Skip to main content

reth_e2e_test_utils/
transaction.rs

1use alloy_consensus::{
2    EnvKzgSettings, EthereumTxEnvelope, SidecarBuilder, SimpleCoder, TxEip4844Variant, TxEnvelope,
3};
4use alloy_eips::{eip7594::BlobTransactionSidecarVariant, eip7702::SignedAuthorization};
5use alloy_network::{
6    eip2718::Encodable2718, Ethereum, EthereumWallet, TransactionBuilder, TransactionBuilder4844,
7};
8use alloy_primitives::{hex, Address, Bytes, TxKind, B256, U256};
9use alloy_rpc_types_eth::{Authorization, TransactionInput, TransactionRequest};
10use alloy_signer::SignerSync;
11use alloy_signer_local::PrivateKeySigner;
12use eyre::Ok;
13
14/// Helper for transaction operations
15#[derive(Debug)]
16pub struct TransactionTestContext;
17
18impl TransactionTestContext {
19    /// Creates a static transfer and signs it, returning an envelope.
20    pub async fn transfer_tx(chain_id: u64, wallet: PrivateKeySigner) -> TxEnvelope {
21        let tx = tx(chain_id, 21000, None, None, 0, Some(20e9 as u128));
22        Self::sign_tx(wallet, tx).await
23    }
24
25    /// Same as `transfer_tx`, but could set max fee per gas.
26    pub async fn transfer_tx_with_gas_fee(
27        chain_id: u64,
28        max_fee_per_gas: Option<u128>,
29        wallet: PrivateKeySigner,
30    ) -> TxEnvelope {
31        let tx = tx(chain_id, 21000, None, None, 0, max_fee_per_gas);
32        Self::sign_tx(wallet, tx).await
33    }
34
35    /// Creates a static transfer and signs it, returning bytes.
36    pub async fn transfer_tx_bytes(chain_id: u64, wallet: PrivateKeySigner) -> Bytes {
37        let signed = Self::transfer_tx(chain_id, wallet).await;
38        signed.encoded_2718().into()
39    }
40
41    /// Creates a transfer with a specific nonce and signs it, returning bytes.
42    /// Uses high `max_fee_per_gas` (1000 gwei) to ensure tx acceptance regardless of basefee.
43    pub async fn transfer_tx_bytes_with_nonce(
44        chain_id: u64,
45        wallet: PrivateKeySigner,
46        nonce: u64,
47    ) -> Bytes {
48        let tx = tx(chain_id, 21000, None, None, nonce, Some(1000e9 as u128));
49        let signed = Self::sign_tx(wallet, tx).await;
50        signed.encoded_2718().into()
51    }
52
53    /// Creates a deployment transaction and signs it, returning an envelope.
54    pub async fn deploy_tx(
55        chain_id: u64,
56        gas: u64,
57        init_code: Bytes,
58        wallet: PrivateKeySigner,
59    ) -> TxEnvelope {
60        let tx = tx(chain_id, gas, Some(init_code), None, 0, Some(20e9 as u128));
61        Self::sign_tx(wallet, tx).await
62    }
63
64    /// Creates a deployment transaction and signs it, returning bytes.
65    pub async fn deploy_tx_bytes(
66        chain_id: u64,
67        gas: u64,
68        init_code: Bytes,
69        wallet: PrivateKeySigner,
70    ) -> Bytes {
71        let signed = Self::deploy_tx(chain_id, gas, init_code, wallet).await;
72        signed.encoded_2718().into()
73    }
74
75    /// Creates an EIP-7702 set code transaction and signs it, returning an envelope.
76    ///
77    /// The EIP-7702 will delegate the code of the signer to the contract at `delegate_to`.
78    pub async fn set_code_tx(
79        chain_id: u64,
80        delegate_to: Address,
81        wallet: PrivateKeySigner,
82    ) -> TxEnvelope {
83        let authorization =
84            Authorization { chain_id: U256::from(chain_id), address: delegate_to, nonce: 0 };
85        let signature = wallet
86            .sign_hash_sync(&authorization.signature_hash())
87            .expect("could not sign authorization");
88        let tx = tx(
89            chain_id,
90            48100,
91            None,
92            Some(authorization.into_signed(signature)),
93            0,
94            Some(20e9 as u128),
95        );
96        Self::sign_tx(wallet, tx).await
97    }
98
99    /// Creates an EIP-7702 set code transaction and signs it, returning bytes.
100    ///
101    /// The EIP-7702 will delegate the code of the signer to the contract at `delegate_to`.
102    pub async fn set_code_tx_bytes(
103        chain_id: u64,
104        delegate_to: Address,
105        wallet: PrivateKeySigner,
106    ) -> Bytes {
107        let signed = Self::set_code_tx(chain_id, delegate_to, wallet).await;
108        signed.encoded_2718().into()
109    }
110
111    /// Creates a tx with blob sidecar and sign it
112    pub async fn tx_with_blobs(
113        chain_id: u64,
114        wallet: PrivateKeySigner,
115    ) -> eyre::Result<TxEnvelope> {
116        let mut tx = tx(chain_id, 210000, None, None, 0, Some(20e9 as u128));
117
118        let mut builder = SidecarBuilder::<SimpleCoder>::new();
119        builder.ingest(b"dummy blob");
120        tx.set_blob_sidecar(builder.build()?);
121        tx.set_max_fee_per_blob_gas(15e9 as u128);
122
123        let signed = Self::sign_tx(wallet, tx).await;
124        Ok(signed)
125    }
126
127    /// Signs an arbitrary [`TransactionRequest`] using the provided wallet
128    pub async fn sign_tx(wallet: PrivateKeySigner, tx: TransactionRequest) -> TxEnvelope {
129        let signer = EthereumWallet::from(wallet);
130        <TransactionRequest as TransactionBuilder<Ethereum>>::build(tx, &signer).await.unwrap()
131    }
132
133    /// Creates a tx with blob sidecar and sign it, returning bytes
134    pub async fn tx_with_blobs_bytes(
135        chain_id: u64,
136        wallet: PrivateKeySigner,
137    ) -> eyre::Result<Bytes> {
138        let signed = Self::tx_with_blobs(chain_id, wallet).await?;
139
140        Ok(signed.encoded_2718().into())
141    }
142
143    /// Creates and encodes an Optimism L1 block information transaction.
144    pub async fn optimism_l1_block_info_tx(
145        chain_id: u64,
146        wallet: PrivateKeySigner,
147        nonce: u64,
148    ) -> Bytes {
149        let l1_block_info = Bytes::from_static(&hex!(
150            "7ef9015aa044bae9d41b8380d781187b426c6fe43df5fb2fb57bd4466ef6a701e1f01e015694deaddeaddeaddeaddeaddeaddeaddeaddead000194420000000000000000000000000000000000001580808408f0d18001b90104015d8eb900000000000000000000000000000000000000000000000000000000008057650000000000000000000000000000000000000000000000000000000063d96d10000000000000000000000000000000000000000000000000000000000009f35273d89754a1e0387b89520d989d3be9c37c1f32495a88faf1ea05c61121ab0d1900000000000000000000000000000000000000000000000000000000000000010000000000000000000000002d679b567db6187c0c8323fa982cfb88b74dbcc7000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240"
151        ));
152        let tx = tx(chain_id, 210000, Some(l1_block_info), None, nonce, Some(20e9 as u128));
153        let signer = EthereumWallet::from(wallet);
154        <TransactionRequest as TransactionBuilder<Ethereum>>::build(tx, &signer)
155            .await
156            .unwrap()
157            .encoded_2718()
158            .into()
159    }
160
161    /// Validates the sidecar of a given tx envelope and returns the versioned hashes
162    #[track_caller]
163    pub fn validate_sidecar(
164        tx: EthereumTxEnvelope<TxEip4844Variant<BlobTransactionSidecarVariant>>,
165    ) -> Vec<B256> {
166        let proof_setting = EnvKzgSettings::Default;
167
168        match tx {
169            EthereumTxEnvelope::Eip4844(signed) => match signed.tx() {
170                TxEip4844Variant::TxEip4844WithSidecar(tx) => {
171                    tx.validate_blob(proof_setting.get()).unwrap();
172                    tx.sidecar.versioned_hashes().collect()
173                }
174                _ => panic!("Expected Eip4844 transaction with sidecar"),
175            },
176            _ => panic!("Expected Eip4844 transaction"),
177        }
178    }
179}
180
181/// Creates a type 2 transaction
182fn tx(
183    chain_id: u64,
184    gas: u64,
185    data: Option<Bytes>,
186    delegate_to: Option<SignedAuthorization>,
187    nonce: u64,
188    max_fee_per_gas: Option<u128>,
189) -> TransactionRequest {
190    TransactionRequest {
191        nonce: Some(nonce),
192        value: Some(U256::from(100)),
193        to: Some(TxKind::Call(Address::random())),
194        gas: Some(gas),
195        max_fee_per_gas,
196        max_priority_fee_per_gas: Some(20e9 as u128),
197        chain_id: Some(chain_id),
198        input: TransactionInput { input: None, data },
199        authorization_list: delegate_to.map(|addr| vec![addr]),
200        ..Default::default()
201    }
202}