reth_e2e_test_utils/
transaction.rs

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