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