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, NetworkTransactionBuilder,
7 TransactionBuilder4844,
8};
9use alloy_primitives::{hex, Address, Bytes, TxKind, B256, U256};
10use alloy_rpc_types_eth::{Authorization, TransactionInput, TransactionRequest};
11use alloy_signer::SignerSync;
12use alloy_signer_local::PrivateKeySigner;
13use eyre::Ok;
14
15#[derive(Debug)]
17pub struct TransactionTestContext;
18
19impl TransactionTestContext {
20 pub async fn transfer_tx(chain_id: u64, wallet: PrivateKeySigner) -> TxEnvelope {
22 let tx = tx(chain_id, 21000, None, None, 0, Some(20e9 as u128));
23 Self::sign_tx(wallet, tx).await
24 }
25
26 pub async fn transfer_tx_with_gas_fee(
28 chain_id: u64,
29 max_fee_per_gas: Option<u128>,
30 wallet: PrivateKeySigner,
31 ) -> TxEnvelope {
32 let tx = tx(chain_id, 21000, None, None, 0, max_fee_per_gas);
33 Self::sign_tx(wallet, tx).await
34 }
35
36 pub async fn transfer_tx_bytes(chain_id: u64, wallet: PrivateKeySigner) -> Bytes {
38 let signed = Self::transfer_tx(chain_id, wallet).await;
39 signed.encoded_2718().into()
40 }
41
42 pub async fn transfer_tx_bytes_with_nonce(
45 chain_id: u64,
46 wallet: PrivateKeySigner,
47 nonce: u64,
48 ) -> Bytes {
49 let tx = tx(chain_id, 21000, None, None, nonce, Some(1000e9 as u128));
50 let signed = Self::sign_tx(wallet, tx).await;
51 signed.encoded_2718().into()
52 }
53
54 pub async fn deploy_tx(
56 chain_id: u64,
57 gas: u64,
58 init_code: Bytes,
59 wallet: PrivateKeySigner,
60 ) -> TxEnvelope {
61 let tx = tx(chain_id, gas, Some(init_code), None, 0, Some(20e9 as u128));
62 Self::sign_tx(wallet, tx).await
63 }
64
65 pub async fn deploy_tx_bytes(
67 chain_id: u64,
68 gas: u64,
69 init_code: Bytes,
70 wallet: PrivateKeySigner,
71 ) -> Bytes {
72 let signed = Self::deploy_tx(chain_id, gas, init_code, wallet).await;
73 signed.encoded_2718().into()
74 }
75
76 pub async fn set_code_tx(
80 chain_id: u64,
81 delegate_to: Address,
82 wallet: PrivateKeySigner,
83 ) -> TxEnvelope {
84 let authorization =
85 Authorization { chain_id: U256::from(chain_id), address: delegate_to, nonce: 0 };
86 let signature = wallet
87 .sign_hash_sync(&authorization.signature_hash())
88 .expect("could not sign authorization");
89 let tx = tx(
90 chain_id,
91 48100,
92 None,
93 Some(authorization.into_signed(signature)),
94 0,
95 Some(20e9 as u128),
96 );
97 Self::sign_tx(wallet, tx).await
98 }
99
100 pub async fn set_code_tx_bytes(
104 chain_id: u64,
105 delegate_to: Address,
106 wallet: PrivateKeySigner,
107 ) -> Bytes {
108 let signed = Self::set_code_tx(chain_id, delegate_to, wallet).await;
109 signed.encoded_2718().into()
110 }
111
112 pub async fn tx_with_blobs(
114 chain_id: u64,
115 wallet: PrivateKeySigner,
116 ) -> eyre::Result<TxEnvelope> {
117 let mut tx = tx(chain_id, 210000, None, None, 0, Some(20e9 as u128));
118
119 let mut builder = SidecarBuilder::<SimpleCoder>::new();
120 builder.ingest(b"dummy blob");
121 let sidecar: alloy_consensus::BlobTransactionSidecar = builder.build()?;
122 tx.set_blob_sidecar(alloy_eips::eip7594::BlobTransactionSidecarVariant::Eip4844(sidecar));
123 tx.set_max_fee_per_blob_gas(15e9 as u128);
124
125 let signed = Self::sign_tx(wallet, tx).await;
126 Ok(signed)
127 }
128
129 pub async fn sign_tx(wallet: PrivateKeySigner, tx: TransactionRequest) -> TxEnvelope {
131 let signer = EthereumWallet::from(wallet);
132 <TransactionRequest as NetworkTransactionBuilder<Ethereum>>::build(tx, &signer)
133 .await
134 .unwrap()
135 }
136
137 pub async fn sign_tx_bytes(wallet: PrivateKeySigner, tx: TransactionRequest) -> Bytes {
140 Self::sign_tx(wallet, tx).await.encoded_2718().into()
141 }
142
143 pub async fn tx_with_blobs_bytes(
145 chain_id: u64,
146 wallet: PrivateKeySigner,
147 ) -> eyre::Result<Bytes> {
148 let signed = Self::tx_with_blobs(chain_id, wallet).await?;
149
150 Ok(signed.encoded_2718().into())
151 }
152
153 pub async fn optimism_l1_block_info_tx(
155 chain_id: u64,
156 wallet: PrivateKeySigner,
157 nonce: u64,
158 ) -> Bytes {
159 let l1_block_info = Bytes::from_static(&hex!(
160 "7ef9015aa044bae9d41b8380d781187b426c6fe43df5fb2fb57bd4466ef6a701e1f01e015694deaddeaddeaddeaddeaddeaddeaddeaddead000194420000000000000000000000000000000000001580808408f0d18001b90104015d8eb900000000000000000000000000000000000000000000000000000000008057650000000000000000000000000000000000000000000000000000000063d96d10000000000000000000000000000000000000000000000000000000000009f35273d89754a1e0387b89520d989d3be9c37c1f32495a88faf1ea05c61121ab0d1900000000000000000000000000000000000000000000000000000000000000010000000000000000000000002d679b567db6187c0c8323fa982cfb88b74dbcc7000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240"
161 ));
162 let tx = tx(chain_id, 210000, Some(l1_block_info), None, nonce, Some(20e9 as u128));
163 let signer = EthereumWallet::from(wallet);
164 <TransactionRequest as NetworkTransactionBuilder<Ethereum>>::build(tx, &signer)
165 .await
166 .unwrap()
167 .encoded_2718()
168 .into()
169 }
170
171 #[track_caller]
173 pub fn validate_sidecar(
174 tx: EthereumTxEnvelope<TxEip4844Variant<BlobTransactionSidecarVariant>>,
175 ) -> Vec<B256> {
176 let proof_setting = EnvKzgSettings::Default;
177
178 match tx {
179 EthereumTxEnvelope::Eip4844(signed) => match signed.tx() {
180 TxEip4844Variant::TxEip4844WithSidecar(tx) => {
181 tx.validate_blob(proof_setting.get()).unwrap();
182 tx.sidecar.versioned_hashes().collect()
183 }
184 _ => panic!("Expected Eip4844 transaction with sidecar"),
185 },
186 _ => panic!("Expected Eip4844 transaction"),
187 }
188 }
189}
190
191fn tx(
193 chain_id: u64,
194 gas: u64,
195 data: Option<Bytes>,
196 delegate_to: Option<SignedAuthorization>,
197 nonce: u64,
198 max_fee_per_gas: Option<u128>,
199) -> TransactionRequest {
200 TransactionRequest {
201 nonce: Some(nonce),
202 value: Some(U256::from(100)),
203 to: Some(TxKind::Call(Address::random())),
204 gas: Some(gas),
205 max_fee_per_gas,
206 max_priority_fee_per_gas: Some(20e9 as u128),
207 chain_id: Some(chain_id),
208 input: TransactionInput { input: None, data },
209 authorization_list: delegate_to.map(|addr| vec![addr]),
210 ..Default::default()
211 }
212}