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 deploy_tx(
43 chain_id: u64,
44 gas: u64,
45 init_code: Bytes,
46 wallet: PrivateKeySigner,
47 ) -> TxEnvelope {
48 let tx = tx(chain_id, gas, Some(init_code), None, 0, Some(20e9 as u128));
49 Self::sign_tx(wallet, tx).await
50 }
51
52 pub async fn deploy_tx_bytes(
54 chain_id: u64,
55 gas: u64,
56 init_code: Bytes,
57 wallet: PrivateKeySigner,
58 ) -> Bytes {
59 let signed = Self::deploy_tx(chain_id, gas, init_code, wallet).await;
60 signed.encoded_2718().into()
61 }
62
63 pub async fn set_code_tx(
67 chain_id: u64,
68 delegate_to: Address,
69 wallet: PrivateKeySigner,
70 ) -> TxEnvelope {
71 let authorization =
72 Authorization { chain_id: U256::from(chain_id), address: delegate_to, nonce: 0 };
73 let signature = wallet
74 .sign_hash_sync(&authorization.signature_hash())
75 .expect("could not sign authorization");
76 let tx = tx(
77 chain_id,
78 48100,
79 None,
80 Some(authorization.into_signed(signature)),
81 0,
82 Some(20e9 as u128),
83 );
84 Self::sign_tx(wallet, tx).await
85 }
86
87 pub async fn set_code_tx_bytes(
91 chain_id: u64,
92 delegate_to: Address,
93 wallet: PrivateKeySigner,
94 ) -> Bytes {
95 let signed = Self::set_code_tx(chain_id, delegate_to, wallet).await;
96 signed.encoded_2718().into()
97 }
98
99 pub async fn tx_with_blobs(
101 chain_id: u64,
102 wallet: PrivateKeySigner,
103 ) -> eyre::Result<TxEnvelope> {
104 let mut tx = tx(chain_id, 210000, None, None, 0, Some(20e9 as u128));
105
106 let mut builder = SidecarBuilder::<SimpleCoder>::new();
107 builder.ingest(b"dummy blob");
108 tx.set_blob_sidecar(builder.build()?);
109 tx.set_max_fee_per_blob_gas(15e9 as u128);
110
111 let signed = Self::sign_tx(wallet, tx).await;
112 Ok(signed)
113 }
114
115 pub async fn sign_tx(wallet: PrivateKeySigner, tx: TransactionRequest) -> TxEnvelope {
117 let signer = EthereumWallet::from(wallet);
118 <TransactionRequest as TransactionBuilder<Ethereum>>::build(tx, &signer).await.unwrap()
119 }
120
121 pub async fn tx_with_blobs_bytes(
123 chain_id: u64,
124 wallet: PrivateKeySigner,
125 ) -> eyre::Result<Bytes> {
126 let signed = Self::tx_with_blobs(chain_id, wallet).await?;
127
128 Ok(signed.encoded_2718().into())
129 }
130
131 pub async fn optimism_l1_block_info_tx(
133 chain_id: u64,
134 wallet: PrivateKeySigner,
135 nonce: u64,
136 ) -> Bytes {
137 let l1_block_info = Bytes::from_static(&hex!(
138 "7ef9015aa044bae9d41b8380d781187b426c6fe43df5fb2fb57bd4466ef6a701e1f01e015694deaddeaddeaddeaddeaddeaddeaddeaddead000194420000000000000000000000000000000000001580808408f0d18001b90104015d8eb900000000000000000000000000000000000000000000000000000000008057650000000000000000000000000000000000000000000000000000000063d96d10000000000000000000000000000000000000000000000000000000000009f35273d89754a1e0387b89520d989d3be9c37c1f32495a88faf1ea05c61121ab0d1900000000000000000000000000000000000000000000000000000000000000010000000000000000000000002d679b567db6187c0c8323fa982cfb88b74dbcc7000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240"
139 ));
140 let tx = tx(chain_id, 210000, Some(l1_block_info), None, nonce, Some(20e9 as u128));
141 let signer = EthereumWallet::from(wallet);
142 <TransactionRequest as TransactionBuilder<Ethereum>>::build(tx, &signer)
143 .await
144 .unwrap()
145 .encoded_2718()
146 .into()
147 }
148
149 #[track_caller]
151 pub fn validate_sidecar(
152 tx: EthereumTxEnvelope<TxEip4844Variant<BlobTransactionSidecarVariant>>,
153 ) -> Vec<B256> {
154 let proof_setting = EnvKzgSettings::Default;
155
156 match tx {
157 EthereumTxEnvelope::Eip4844(signed) => match signed.tx() {
158 TxEip4844Variant::TxEip4844WithSidecar(tx) => {
159 tx.validate_blob(proof_setting.get()).unwrap();
160 tx.sidecar.versioned_hashes().collect()
161 }
162 _ => panic!("Expected Eip4844 transaction with sidecar"),
163 },
164 _ => panic!("Expected Eip4844 transaction"),
165 }
166 }
167}
168
169fn tx(
171 chain_id: u64,
172 gas: u64,
173 data: Option<Bytes>,
174 delegate_to: Option<SignedAuthorization>,
175 nonce: u64,
176 max_fee_per_gas: Option<u128>,
177) -> TransactionRequest {
178 TransactionRequest {
179 nonce: Some(nonce),
180 value: Some(U256::from(100)),
181 to: Some(TxKind::Call(Address::random())),
182 gas: Some(gas),
183 max_fee_per_gas,
184 max_priority_fee_per_gas: Some(20e9 as u128),
185 chain_id: Some(chain_id),
186 input: TransactionInput { input: None, data },
187 authorization_list: delegate_to.map(|addr| vec![addr]),
188 ..Default::default()
189 }
190}