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#[derive(Debug)]
16pub struct TransactionTestContext;
17
18impl TransactionTestContext {
19 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 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 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 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 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 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 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 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 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 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 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 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 #[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
181fn 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}