foundry_primitives/transaction/
base.rs1use super::FoundryTxEnvelope;
4use alloy_consensus::Typed2718;
5use alloy_evm::{FromRecoveredTx, FromTxWithEncoded};
6use alloy_network::eip2718::Encodable2718;
7use alloy_primitives::{Address, Bytes};
8use alloy_rpc_types::TransactionRequest;
9use base_common_consensus::{BaseTxEnvelope, TxEip8130};
10use base_common_evm::{BaseTransaction, DepositTransactionParts, EIP8130_TRANSACTION_TYPE};
11use base_common_rpc_types::{BaseTransactionRequest, Eip8130RequestFields};
12use revm::context::TxEnv;
13use serde::Serialize;
14
15impl FromRecoveredTx<FoundryTxEnvelope> for BaseTransaction<TxEnv> {
16 fn from_recovered_tx(tx: &FoundryTxEnvelope, caller: Address) -> Self {
17 let encoded = tx.encoded_2718().into();
18 Self::from_encoded_tx(tx, caller, encoded)
19 }
20}
21
22impl FromTxWithEncoded<FoundryTxEnvelope> for BaseTransaction<TxEnv> {
23 fn from_encoded_tx(tx: &FoundryTxEnvelope, caller: Address, encoded: Bytes) -> Self {
24 match tx {
25 FoundryTxEnvelope::Legacy(signed) => Self {
26 base: TxEnv::from_recovered_tx(signed, caller),
27 enveloped_tx: Some(encoded),
28 deposit: Default::default(),
29 eip8130: None,
30 },
31 FoundryTxEnvelope::Eip2930(signed) => Self {
32 base: TxEnv::from_recovered_tx(signed, caller),
33 enveloped_tx: Some(encoded),
34 deposit: Default::default(),
35 eip8130: None,
36 },
37 FoundryTxEnvelope::Eip1559(signed) => Self {
38 base: TxEnv::from_recovered_tx(signed, caller),
39 enveloped_tx: Some(encoded),
40 deposit: Default::default(),
41 eip8130: None,
42 },
43 FoundryTxEnvelope::Eip4844(signed) => Self {
44 base: TxEnv::from_recovered_tx(signed, caller),
45 enveloped_tx: Some(encoded),
46 deposit: Default::default(),
47 eip8130: None,
48 },
49 FoundryTxEnvelope::Eip7702(signed) => Self {
50 base: TxEnv::from_recovered_tx(signed, caller),
51 enveloped_tx: Some(encoded),
52 deposit: Default::default(),
53 eip8130: None,
54 },
55 #[cfg(any(feature = "base", feature = "optimism"))]
56 FoundryTxEnvelope::Deposit(sealed) => {
57 let deposit = sealed.inner();
58 let base = TxEnv {
59 tx_type: deposit.ty(),
60 caller,
61 gas_limit: deposit.gas_limit,
62 kind: deposit.to,
63 value: deposit.value,
64 data: deposit.input.clone(),
65 ..Default::default()
66 };
67 Self {
68 base,
69 enveloped_tx: None,
70 deposit: DepositTransactionParts {
71 source_hash: deposit.source_hash,
72 mint: Some(deposit.mint),
73 is_system_transaction: deposit.is_system_transaction,
74 },
75 eip8130: None,
76 }
77 }
78 #[cfg(feature = "optimism")]
79 FoundryTxEnvelope::PostExec(_) => {
80 unreachable!("post-execution transaction in Base context")
81 }
82 FoundryTxEnvelope::Eip8130(signed) => {
83 let envelope = BaseTxEnvelope::Eip8130(signed.clone());
84 Self::from_encoded_tx(&envelope, caller, encoded)
85 }
86 FoundryTxEnvelope::Tempo(_) => unreachable!("Tempo transaction in Base context"),
87 }
88 }
89}
90
91pub(super) fn simulation_request(
93 tx: TxEip8130,
94 from: Option<Address>,
95 sender_auth: Option<Bytes>,
96 payer_auth: Option<Bytes>,
97) -> BaseTransactionRequest {
98 let inner = TransactionRequest {
99 transaction_type: Some(EIP8130_TRANSACTION_TYPE),
100 chain_id: Some(tx.chain_id),
101 from,
102 nonce: Some(tx.nonce_sequence),
103 gas: Some(tx.gas_limit),
104 max_fee_per_gas: Some(tx.max_fee_per_gas),
105 max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas),
106 ..Default::default()
107 };
108 let fields = Eip8130RequestFields {
109 nonce_key: Some(tx.nonce_key),
110 account_changes: Some(tx.account_changes),
111 calls: Some(tx.calls),
112 valid_after: Some(tx.valid_after),
113 valid_before: Some(tx.valid_before),
114 metadata: Some(tx.metadata),
115 sender: tx.sender,
116 sender_auth,
117 payer: tx.payer,
118 payer_auth,
119 ..Default::default()
120 };
121 #[derive(Serialize)]
123 struct SimulationRequest {
124 #[serde(flatten)]
125 inner: TransactionRequest,
126 #[serde(flatten)]
127 fields: Eip8130RequestFields,
128 }
129 let value = serde_json::to_value(SimulationRequest { inner, fields })
130 .expect("serializable Base simulation request");
131 serde_json::from_value(value).expect("compatible Base simulation fields")
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137 use crate::{FoundryTransactionRequest, FoundryTypedTx};
138 use alloy_network::NetworkTransactionBuilder;
139 use alloy_primitives::U256;
140 use base_common_consensus::{AccountChange, Call, Delegation, Eip8130Signed};
141
142 #[test]
143 fn eip8130_simulation_projection() {
144 let body = TxEip8130 {
145 chain_id: 8453,
146 sender: Some(Address::repeat_byte(1)),
147 payer: Some(Address::repeat_byte(2)),
148 nonce_key: U256::from(7),
149 nonce_sequence: 9,
150 gas_limit: 100_000,
151 max_fee_per_gas: 20,
152 max_priority_fee_per_gas: 3,
153 valid_after: 100,
154 valid_before: 200,
155 calls: vec![vec![Call {
156 to: Address::repeat_byte(3),
157 data: Bytes::from_static(b"call"),
158 }]],
159 account_changes: vec![AccountChange::Delegation(Delegation {
160 target: Address::repeat_byte(4),
161 })],
162 metadata: Bytes::from_static(b"metadata"),
163 };
164 let signed = Eip8130Signed::new(
165 body.clone(),
166 Bytes::from_static(b"sender"),
167 Bytes::from_static(b"payer"),
168 );
169 let envelope = FoundryTxEnvelope::Eip8130(signed.clone());
170 for (request, from, sender_auth, payer_auth) in [
171 (
172 FoundryTransactionRequest::from(FoundryTypedTx::Eip8130(body.clone())),
173 None,
174 None,
175 None,
176 ),
177 (
178 FoundryTransactionRequest::from(envelope),
179 body.sender,
180 Some(signed.sender_auth().clone()),
181 Some(signed.payer_auth().clone()),
182 ),
183 ] {
184 assert!(!request.can_build());
185 let base = request.as_base().unwrap();
186 assert_eq!(
187 base.as_eip8130(),
188 Some(&Eip8130RequestFields {
189 nonce_key: Some(body.nonce_key),
190 account_changes: Some(body.account_changes.clone()),
191 calls: Some(body.calls.clone()),
192 valid_after: Some(body.valid_after),
193 valid_before: Some(body.valid_before),
194 metadata: Some(body.metadata.clone()),
195 sender: body.sender,
196 payer: body.payer,
197 sender_auth,
198 payer_auth,
199 ..Default::default()
200 })
201 );
202 assert_eq!(
203 base.as_ref(),
204 &TransactionRequest {
205 transaction_type: Some(EIP8130_TRANSACTION_TYPE),
206 chain_id: Some(body.chain_id),
207 from,
208 nonce: Some(body.nonce_sequence),
209 gas: Some(body.gas_limit),
210 max_fee_per_gas: Some(body.max_fee_per_gas),
211 max_priority_fee_per_gas: Some(body.max_priority_fee_per_gas),
212 ..Default::default()
213 }
214 );
215 let roundtrip: FoundryTransactionRequest =
216 serde_json::from_value(serde_json::to_value(&request).unwrap()).unwrap();
217 assert_eq!(request, roundtrip);
218 }
219 }
220 #[test]
221 fn eip8130_conversion_preserves_supplied_encoding() {
222 let envelope = FoundryTxEnvelope::Eip8130(Eip8130Signed::new(
223 TxEip8130::default(),
224 Bytes::new(),
225 Bytes::new(),
226 ));
227 let encoded = Bytes::from_static(b"already encoded");
228 let env =
229 BaseTransaction::<TxEnv>::from_encoded_tx(&envelope, Address::ZERO, encoded.clone());
230 assert_eq!(env.enveloped_tx, Some(encoded));
231 }
232}