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