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 FoundryTxEnvelope::Celo(_) => unreachable!("CIP-64 transaction in base context"),
88 }
89 }
90}
91
92pub(super) fn simulation_request(
94 tx: TxEip8130,
95 from: Option<Address>,
96 sender_auth: Option<Bytes>,
97 payer_auth: Option<Bytes>,
98) -> BaseTransactionRequest {
99 let inner = TransactionRequest {
100 transaction_type: Some(EIP8130_TRANSACTION_TYPE),
101 chain_id: Some(tx.chain_id),
102 from,
103 nonce: Some(tx.nonce_sequence),
104 gas: Some(tx.gas_limit),
105 max_fee_per_gas: Some(tx.max_fee_per_gas),
106 max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas),
107 ..Default::default()
108 };
109 let fields = Eip8130RequestFields {
110 nonce_key: Some(tx.nonce_key),
111 account_changes: Some(tx.account_changes),
112 calls: Some(tx.calls),
113 valid_after: Some(tx.valid_after),
114 valid_before: Some(tx.valid_before),
115 metadata: Some(tx.metadata),
116 sender: tx.sender,
117 sender_auth,
118 payer: tx.payer,
119 payer_auth,
120 ..Default::default()
121 };
122 #[derive(Serialize)]
124 struct SimulationRequest {
125 #[serde(flatten)]
126 inner: TransactionRequest,
127 #[serde(flatten)]
128 fields: Eip8130RequestFields,
129 }
130 let value = serde_json::to_value(SimulationRequest { inner, fields })
131 .expect("serializable Base simulation request");
132 serde_json::from_value(value).expect("compatible Base simulation fields")
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138 use crate::{FoundryTransactionRequest, FoundryTypedTx};
139 use alloy_network::NetworkTransactionBuilder;
140 use alloy_primitives::U256;
141 use base_common_consensus::{AccountChange, Call, Delegation, Eip8130Signed};
142
143 #[test]
144 fn eip8130_simulation_projection() {
145 let body = TxEip8130 {
146 chain_id: 8453,
147 sender: Some(Address::repeat_byte(1)),
148 payer: Some(Address::repeat_byte(2)),
149 nonce_key: U256::from(7),
150 nonce_sequence: 9,
151 gas_limit: 100_000,
152 max_fee_per_gas: 20,
153 max_priority_fee_per_gas: 3,
154 valid_after: 100,
155 valid_before: 200,
156 calls: vec![vec![Call {
157 to: Address::repeat_byte(3),
158 data: Bytes::from_static(b"call"),
159 }]],
160 account_changes: vec![AccountChange::Delegation(Delegation {
161 target: Address::repeat_byte(4),
162 })],
163 metadata: Bytes::from_static(b"metadata"),
164 };
165 let signed = Eip8130Signed::new(
166 body.clone(),
167 Bytes::from_static(b"sender"),
168 Bytes::from_static(b"payer"),
169 );
170 let envelope = FoundryTxEnvelope::Eip8130(signed.clone());
171 for (request, from, sender_auth, payer_auth) in [
172 (
173 FoundryTransactionRequest::from(FoundryTypedTx::Eip8130(body.clone())),
174 None,
175 None,
176 None,
177 ),
178 (
179 FoundryTransactionRequest::from(envelope),
180 body.sender,
181 Some(signed.sender_auth().clone()),
182 Some(signed.payer_auth().clone()),
183 ),
184 ] {
185 assert!(!request.can_build());
186 let base = request.as_base().unwrap();
187 assert_eq!(
188 base.as_eip8130(),
189 Some(&Eip8130RequestFields {
190 nonce_key: Some(body.nonce_key),
191 account_changes: Some(body.account_changes.clone()),
192 calls: Some(body.calls.clone()),
193 valid_after: Some(body.valid_after),
194 valid_before: Some(body.valid_before),
195 metadata: Some(body.metadata.clone()),
196 sender: body.sender,
197 payer: body.payer,
198 sender_auth,
199 payer_auth,
200 ..Default::default()
201 })
202 );
203 assert_eq!(
204 base.as_ref(),
205 &TransactionRequest {
206 transaction_type: Some(EIP8130_TRANSACTION_TYPE),
207 chain_id: Some(body.chain_id),
208 from,
209 nonce: Some(body.nonce_sequence),
210 gas: Some(body.gas_limit),
211 max_fee_per_gas: Some(body.max_fee_per_gas),
212 max_priority_fee_per_gas: Some(body.max_priority_fee_per_gas),
213 ..Default::default()
214 }
215 );
216 let roundtrip: FoundryTransactionRequest =
217 serde_json::from_value(serde_json::to_value(&request).unwrap()).unwrap();
218 assert_eq!(request, roundtrip);
219 }
220 }
221 #[test]
222 fn eip8130_conversion_preserves_supplied_encoding() {
223 let envelope = FoundryTxEnvelope::Eip8130(Eip8130Signed::new(
224 TxEip8130::default(),
225 Bytes::new(),
226 Bytes::new(),
227 ));
228 let encoded = Bytes::from_static(b"already encoded");
229 let env =
230 BaseTransaction::<TxEnv>::from_encoded_tx(&envelope, Address::ZERO, encoded.clone());
231 assert_eq!(env.enveloped_tx, Some(encoded));
232 }
233}