1use std::{
2 cmp::min,
3 fmt::Display,
4 num::NonZeroUsize,
5 sync::{LazyLock, Mutex},
6};
7
8use crate::utils::keccak;
9use bytes::Bytes;
10use ethereum_types::{Address, H256, U256};
11use ethrex_crypto::{Crypto, CryptoError, NativeCrypto};
12use lru::LruCache;
13pub use mempool::MempoolTransaction;
14
15const MAX_SIGNER_CACHE_ENTRIES: usize = 100_000;
16
17pub static GLOBAL_SIGNER_CACHE: LazyLock<Mutex<LruCache<H256, Address>>> = LazyLock::new(|| {
28 Mutex::new(LruCache::new(
29 NonZeroUsize::new(MAX_SIGNER_CACHE_ENTRIES).expect("MAX_SIGNER_CACHE_ENTRIES is non-zero"),
30 ))
31});
32use rkyv::{Archive, Deserialize as RDeserialize, Serialize as RSerialize};
33use serde::{Serialize, ser::SerializeStruct};
34pub use serde_impl::{
35 AccessListEntry, AuthorizationTupleEntry, FrameEntry, GenericTransaction,
36 GenericTransactionError,
37};
38
39pub const EIP1559_DEFAULT_SERIALIZED_LENGTH: usize = 15;
41
42use ethrex_rlp::{
43 constants::RLP_NULL,
44 decode::{RLPDecode, decode_rlp_item},
45 encode::{PayloadRLPEncode, RLPEncode},
46 error::RLPDecodeError,
47 structs::{Decoder, Encoder},
48};
49
50#[cfg(all(feature = "eip-8025", target_arch = "riscv64"))]
51use super::eip8025_cell::OnceCell;
52use crate::types::{AccessList, AuthorizationList, BlobsBundle};
53#[cfg(not(all(feature = "eip-8025", target_arch = "riscv64")))]
54use once_cell::sync::OnceCell;
55
56#[derive(Clone, Debug, PartialEq, Eq, Serialize, RSerialize, RDeserialize, Archive)]
65#[serde(untagged)]
66pub enum Transaction {
67 LegacyTransaction(LegacyTransaction),
68 EIP2930Transaction(EIP2930Transaction),
69 EIP1559Transaction(EIP1559Transaction),
70 EIP4844Transaction(EIP4844Transaction),
71 EIP7702Transaction(EIP7702Transaction),
72 PrivilegedL2Transaction(PrivilegedL2Transaction),
73 FeeTokenTransaction(FeeTokenTransaction),
74 FrameTransaction(FrameTransaction),
75}
76
77#[derive(Clone, Debug, PartialEq, Eq)]
81pub enum P2PTransaction {
82 LegacyTransaction(LegacyTransaction),
83 EIP2930Transaction(EIP2930Transaction),
84 EIP1559Transaction(EIP1559Transaction),
85 EIP4844TransactionWithBlobs(WrappedEIP4844Transaction),
86 EIP7702Transaction(EIP7702Transaction),
87 FeeTokenTransaction(FeeTokenTransaction),
88 FrameTransaction(FrameTransaction),
89}
90
91impl TryInto<Transaction> for P2PTransaction {
92 type Error = String;
93
94 fn try_into(self) -> Result<Transaction, Self::Error> {
95 match self {
96 P2PTransaction::LegacyTransaction(itx) => Ok(Transaction::LegacyTransaction(itx)),
97 P2PTransaction::EIP2930Transaction(itx) => Ok(Transaction::EIP2930Transaction(itx)),
98 P2PTransaction::EIP1559Transaction(itx) => Ok(Transaction::EIP1559Transaction(itx)),
99 P2PTransaction::EIP7702Transaction(itx) => Ok(Transaction::EIP7702Transaction(itx)),
100 P2PTransaction::FrameTransaction(itx) => Ok(Transaction::FrameTransaction(itx)),
101 _ => Err("Can't convert blob p2p transaction into regular transaction. Blob bundle would be lost.".to_string()),
102 }
103 }
104}
105
106impl RLPEncode for P2PTransaction {
107 fn encode(&self, buf: &mut dyn bytes::BufMut) {
108 match self {
109 P2PTransaction::LegacyTransaction(t) => t.encode(buf),
110 tx => <[u8] as RLPEncode>::encode(&tx.encode_canonical_to_vec(), buf),
111 };
112 }
113}
114
115impl RLPDecode for P2PTransaction {
116 fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
117 let (is_list, payload, remainder) = decode_rlp_item(rlp)?;
118 if !is_list {
119 let tx_type = payload.first().ok_or(RLPDecodeError::InvalidLength)?;
120 let tx_encoding = &payload.get(1..).ok_or(RLPDecodeError::InvalidLength)?;
121 match *tx_type {
123 0x0 => LegacyTransaction::decode(tx_encoding)
125 .map(|tx| (P2PTransaction::LegacyTransaction(tx), remainder)), 0x1 => EIP2930Transaction::decode(tx_encoding)
128 .map(|tx| (P2PTransaction::EIP2930Transaction(tx), remainder)),
129 0x2 => EIP1559Transaction::decode(tx_encoding)
131 .map(|tx| (P2PTransaction::EIP1559Transaction(tx), remainder)),
132 0x3 => WrappedEIP4844Transaction::decode(tx_encoding)
134 .map(|tx| (P2PTransaction::EIP4844TransactionWithBlobs(tx), remainder)),
135 0x4 => EIP7702Transaction::decode(tx_encoding)
137 .map(|tx| (P2PTransaction::EIP7702Transaction(tx), remainder)),
138 0x7d => FeeTokenTransaction::decode(tx_encoding)
140 .map(|tx| (P2PTransaction::FeeTokenTransaction(tx), remainder)),
141 0x06 => FrameTransaction::decode(tx_encoding)
143 .map(|tx| (P2PTransaction::FrameTransaction(tx), remainder)),
144 ty => Err(RLPDecodeError::Custom(format!(
145 "Invalid transaction type: {ty}"
146 ))),
147 }
148 } else {
149 LegacyTransaction::decode_unfinished(rlp)
151 .map(|(tx, rem)| (P2PTransaction::LegacyTransaction(tx), rem))
152 }
153 }
154}
155
156#[derive(Clone, Debug, PartialEq, Eq)]
157pub struct WrappedEIP4844Transaction {
158 pub tx: EIP4844Transaction,
159 pub wrapper_version: Option<u8>,
160 pub blobs_bundle: BlobsBundle,
161}
162
163impl RLPEncode for WrappedEIP4844Transaction {
164 fn encode(&self, buf: &mut dyn bytes::BufMut) {
165 let encoder = Encoder::new(buf);
166 encoder
167 .encode_field(&self.tx)
168 .encode_optional_field(&self.wrapper_version)
169 .encode_field(&self.blobs_bundle.blobs)
170 .encode_field(&self.blobs_bundle.commitments)
171 .encode_field(&self.blobs_bundle.proofs)
172 .finish();
173 }
174}
175
176impl RLPDecode for WrappedEIP4844Transaction {
177 fn decode_unfinished(rlp: &[u8]) -> Result<(WrappedEIP4844Transaction, &[u8]), RLPDecodeError> {
178 let decoder = Decoder::new(rlp)?;
179 let Ok((tx, decoder)) = decoder.decode_field("tx") else {
180 let (tx, rest) = EIP4844Transaction::decode_unfinished(rlp)?;
182 return Ok((
183 WrappedEIP4844Transaction {
184 tx,
185 wrapper_version: None,
186 blobs_bundle: BlobsBundle::empty(),
188 },
189 rest,
190 ));
191 };
192
193 let (wrapper_version, decoder) = decoder.decode_optional_field();
194 let (blobs, decoder) = decoder.decode_field("blobs")?;
195 let (commitments, decoder) = decoder.decode_field("commitments")?;
196 let (proofs, decoder) = decoder.decode_field("proofs")?;
197
198 let wrapped = WrappedEIP4844Transaction {
199 tx,
200 wrapper_version,
201 blobs_bundle: BlobsBundle {
202 blobs,
203 commitments,
204 proofs,
205 version: wrapper_version.unwrap_or_default(),
206 },
207 };
208 Ok((wrapped, decoder.finish()?))
209 }
210}
211
212#[derive(Clone, Debug, PartialEq, Eq, Default, RSerialize, RDeserialize, Archive)]
213pub struct LegacyTransaction {
214 pub nonce: u64,
215 #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
216 pub gas_price: U256,
217 pub gas: u64,
218 pub to: TxKind,
221 #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
222 pub value: U256,
223 #[rkyv(with=crate::rkyv_utils::BytesWrapper)]
224 pub data: Bytes,
225 #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
226 pub v: U256,
227 #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
228 pub r: U256,
229 #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
230 pub s: U256,
231 #[rkyv(with=rkyv::with::Skip)]
232 pub inner_hash: OnceCell<H256>,
233 #[rkyv(with=rkyv::with::Skip)]
234 pub sender_cache: OnceCell<Address>,
235}
236
237#[derive(Clone, Debug, PartialEq, Eq, Default, RSerialize, RDeserialize, Archive)]
238pub struct EIP2930Transaction {
239 pub chain_id: u64,
240 pub nonce: u64,
241 #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
242 pub gas_price: U256,
243 pub gas_limit: u64,
244 pub to: TxKind,
245 #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
246 pub value: U256,
247 #[rkyv(with=crate::rkyv_utils::BytesWrapper)]
248 pub data: Bytes,
249 #[rkyv(with=rkyv::with::Map<crate::rkyv_utils::AccessListItemWrapper>)]
250 pub access_list: AccessList,
251 pub signature_y_parity: bool,
252 #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
253 pub signature_r: U256,
254 #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
255 pub signature_s: U256,
256 #[rkyv(with=rkyv::with::Skip)]
257 pub inner_hash: OnceCell<H256>,
258 #[rkyv(with=rkyv::with::Skip)]
259 pub sender_cache: OnceCell<Address>,
260 #[rkyv(with=rkyv::with::Skip)]
261 pub cached_canonical: OnceCell<Vec<u8>>,
262}
263
264#[derive(Clone, Debug, PartialEq, Eq, Default, RSerialize, RDeserialize, Archive)]
265pub struct EIP1559Transaction {
266 pub chain_id: u64,
267 pub nonce: u64,
268 pub max_priority_fee_per_gas: u64,
269 pub max_fee_per_gas: u64,
270 pub gas_limit: u64,
271 pub to: TxKind,
272 #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
273 pub value: U256,
274 #[rkyv(with=crate::rkyv_utils::BytesWrapper)]
275 pub data: Bytes,
276 #[rkyv(with=rkyv::with::Map<crate::rkyv_utils::AccessListItemWrapper>)]
277 pub access_list: AccessList,
278 pub signature_y_parity: bool,
279 #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
280 pub signature_r: U256,
281 #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
282 pub signature_s: U256,
283 #[rkyv(with=rkyv::with::Skip)]
284 pub inner_hash: OnceCell<H256>,
285 #[rkyv(with=rkyv::with::Skip)]
286 pub sender_cache: OnceCell<Address>,
287 #[rkyv(with=rkyv::with::Skip)]
288 pub cached_canonical: OnceCell<Vec<u8>>,
289}
290
291#[derive(Clone, Debug, PartialEq, Eq, Default, RSerialize, RDeserialize, Archive)]
292pub struct EIP4844Transaction {
293 pub chain_id: u64,
294 pub nonce: u64,
295 pub max_priority_fee_per_gas: u64,
296 pub max_fee_per_gas: u64,
297 pub gas: u64,
298 #[rkyv(with=crate::rkyv_utils::H160Wrapper)]
299 pub to: Address,
300 #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
301 pub value: U256,
302 #[rkyv(with=crate::rkyv_utils::BytesWrapper)]
303 pub data: Bytes,
304 #[rkyv(with=rkyv::with::Map<crate::rkyv_utils::AccessListItemWrapper>)]
305 pub access_list: AccessList,
306 #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
307 pub max_fee_per_blob_gas: U256,
308 #[rkyv(with=rkyv::with::Map<crate::rkyv_utils::H256Wrapper>)]
309 pub blob_versioned_hashes: Vec<H256>,
310 pub signature_y_parity: bool,
311 #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
312 pub signature_r: U256,
313 #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
314 pub signature_s: U256,
315 #[rkyv(with=rkyv::with::Skip)]
316 pub inner_hash: OnceCell<H256>,
317 #[rkyv(with=rkyv::with::Skip)]
318 pub sender_cache: OnceCell<Address>,
319 #[rkyv(with=rkyv::with::Skip)]
320 pub cached_canonical: OnceCell<Vec<u8>>,
321}
322
323#[derive(Clone, Debug, PartialEq, Eq, Default, RSerialize, RDeserialize, Archive)]
324pub struct EIP7702Transaction {
325 pub chain_id: u64,
326 pub nonce: u64,
327 pub max_priority_fee_per_gas: u64,
328 pub max_fee_per_gas: u64,
329 pub gas_limit: u64,
330 #[rkyv(with=crate::rkyv_utils::H160Wrapper)]
331 pub to: Address,
332 #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
333 pub value: U256,
334 #[rkyv(with=crate::rkyv_utils::BytesWrapper)]
335 pub data: Bytes,
336 #[rkyv(with=rkyv::with::Map<crate::rkyv_utils::AccessListItemWrapper>)]
337 pub access_list: AccessList,
338 pub authorization_list: AuthorizationList,
339 pub signature_y_parity: bool,
340 #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
341 pub signature_r: U256,
342 #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
343 pub signature_s: U256,
344 #[rkyv(with=rkyv::with::Skip)]
345 pub inner_hash: OnceCell<H256>,
346 #[rkyv(with=rkyv::with::Skip)]
347 pub sender_cache: OnceCell<Address>,
348 #[rkyv(with=rkyv::with::Skip)]
349 pub cached_canonical: OnceCell<Vec<u8>>,
350}
351#[derive(Clone, Debug, PartialEq, Eq, Default, RSerialize, RDeserialize, Archive)]
352pub struct PrivilegedL2Transaction {
353 pub chain_id: u64,
354 pub nonce: u64,
355 pub max_priority_fee_per_gas: u64,
356 pub max_fee_per_gas: u64,
357 pub gas_limit: u64,
358 pub to: TxKind,
359 #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
360 pub value: U256,
361 #[rkyv(with=crate::rkyv_utils::BytesWrapper)]
362 pub data: Bytes,
363 #[rkyv(with=rkyv::with::Map<crate::rkyv_utils::AccessListItemWrapper>)]
364 pub access_list: AccessList,
365 #[rkyv(with=crate::rkyv_utils::H160Wrapper)]
366 pub from: Address,
367 #[rkyv(with=rkyv::with::Skip)]
368 pub inner_hash: OnceCell<H256>,
369 #[rkyv(with=rkyv::with::Skip)]
370 pub sender_cache: OnceCell<Address>,
371 #[rkyv(with=rkyv::with::Skip)]
372 pub cached_canonical: OnceCell<Vec<u8>>,
373}
374
375#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
376pub enum TxType {
377 #[default]
378 Legacy = 0x00,
379 EIP2930 = 0x01,
380 EIP1559 = 0x02,
381 EIP4844 = 0x03,
382 EIP7702 = 0x04,
383 Frame = 0x06,
384 FeeToken = 0x7d,
385 Privileged = 0x7e,
388}
389
390impl From<TxType> for u8 {
391 fn from(val: TxType) -> Self {
392 match val {
393 TxType::Legacy => 0x00,
394 TxType::EIP2930 => 0x01,
395 TxType::EIP1559 => 0x02,
396 TxType::EIP4844 => 0x03,
397 TxType::EIP7702 => 0x04,
398 TxType::Frame => 0x06,
399 TxType::FeeToken => 0x7d,
400 TxType::Privileged => 0x7e,
401 }
402 }
403}
404
405impl Display for TxType {
406 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
407 match self {
408 TxType::Legacy => write!(f, "Legacy"),
409 TxType::EIP2930 => write!(f, "EIP2930"),
410 TxType::EIP1559 => write!(f, "EIP1559"),
411 TxType::EIP4844 => write!(f, "EIP4844"),
412 TxType::EIP7702 => write!(f, "EIP7702"),
413 TxType::Frame => write!(f, "Frame"),
414 TxType::Privileged => write!(f, "Privileged"),
415 TxType::FeeToken => write!(f, "FeeToken"),
416 }
417 }
418}
419
420impl Transaction {
421 pub fn tx_type(&self) -> TxType {
422 match self {
423 Transaction::LegacyTransaction(_) => TxType::Legacy,
424 Transaction::EIP2930Transaction(_) => TxType::EIP2930,
425 Transaction::EIP1559Transaction(_) => TxType::EIP1559,
426 Transaction::EIP4844Transaction(_) => TxType::EIP4844,
427 Transaction::EIP7702Transaction(_) => TxType::EIP7702,
428 Transaction::FeeTokenTransaction(_) => TxType::FeeToken,
429 Transaction::PrivilegedL2Transaction(_) => TxType::Privileged,
430 Transaction::FrameTransaction(_) => TxType::Frame,
431 }
432 }
433
434 fn calc_effective_gas_price(&self, base_fee_per_gas: Option<u64>) -> Option<U256> {
435 let base_fee = base_fee_per_gas?;
436 let max_fee = self.max_fee_per_gas()?;
437 if max_fee < base_fee {
438 return None;
440 }
441
442 let priority_fee_per_gas = min(self.max_priority_fee()?, max_fee.saturating_sub(base_fee));
443 Some(U256::from(priority_fee_per_gas) + U256::from(base_fee))
444 }
445
446 pub fn effective_gas_price(&self, base_fee_per_gas: Option<u64>) -> Option<U256> {
447 match self.tx_type() {
448 TxType::Legacy => Some(self.gas_price()),
449 TxType::EIP2930 => Some(self.gas_price()),
450 TxType::EIP1559 => self.calc_effective_gas_price(base_fee_per_gas),
451 TxType::EIP4844 => self.calc_effective_gas_price(base_fee_per_gas),
452 TxType::EIP7702 => self.calc_effective_gas_price(base_fee_per_gas),
453 TxType::Frame => self.calc_effective_gas_price(base_fee_per_gas),
454 TxType::FeeToken => self.calc_effective_gas_price(base_fee_per_gas),
455 TxType::Privileged => Some(self.gas_price()),
456 }
457 }
458
459 pub fn cost_without_base_fee(&self) -> Option<U256> {
460 let price = match self.tx_type() {
461 TxType::Legacy => self.gas_price(),
462 TxType::EIP2930 => self.gas_price(),
463 TxType::EIP1559 => U256::from(self.max_fee_per_gas()?),
464 TxType::EIP4844 => U256::from(self.max_fee_per_gas()?),
465 TxType::EIP7702 => U256::from(self.max_fee_per_gas()?),
466 TxType::Frame => U256::from(self.max_fee_per_gas()?),
467 TxType::FeeToken => U256::from(self.max_fee_per_gas()?),
468 TxType::Privileged => self.gas_price(),
469 };
470
471 let base = U256::saturating_add(
472 U256::saturating_mul(price, self.gas_limit().into()),
473 self.value(),
474 );
475
476 if let Transaction::EIP4844Transaction(tx) = self {
480 let blob_gas = U256::from(crate::constants::GAS_PER_BLOB)
481 .saturating_mul(U256::from(tx.blob_versioned_hashes.len() as u64));
482 let blob_cost = blob_gas.saturating_mul(tx.max_fee_per_blob_gas);
483 return Some(base.saturating_add(blob_cost));
484 }
485
486 Some(base)
487 }
488
489 pub fn fee_token(&self) -> Option<Address> {
490 if let Transaction::FeeTokenTransaction(tx) = self {
491 Some(tx.fee_token)
492 } else {
493 None
494 }
495 }
496
497 fn cached_canonical_cell(&self) -> Option<&OnceCell<Vec<u8>>> {
500 match self {
501 Transaction::LegacyTransaction(_) => None,
502 Transaction::EIP2930Transaction(t) => Some(&t.cached_canonical),
503 Transaction::EIP1559Transaction(t) => Some(&t.cached_canonical),
504 Transaction::EIP4844Transaction(t) => Some(&t.cached_canonical),
505 Transaction::EIP7702Transaction(t) => Some(&t.cached_canonical),
506 Transaction::PrivilegedL2Transaction(t) => Some(&t.cached_canonical),
507 Transaction::FeeTokenTransaction(t) => Some(&t.cached_canonical),
508 Transaction::FrameTransaction(t) => Some(&t.cached_canonical),
509 }
510 }
511}
512
513impl RLPEncode for Transaction {
514 fn encode(&self, buf: &mut dyn bytes::BufMut) {
519 match self {
520 Transaction::LegacyTransaction(t) => t.encode(buf),
521 _ => {
522 let canonical = self.encode_canonical_to_vec();
523 <[u8] as RLPEncode>::encode(canonical.as_slice(), buf)
524 }
525 };
526 }
527}
528
529impl RLPDecode for Transaction {
530 fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
535 let (is_list, payload, remainder) = decode_rlp_item(rlp)?;
536 if !is_list {
537 let tx_type = payload.first().ok_or(RLPDecodeError::InvalidLength)?;
538 let tx_encoding = &payload.get(1..).ok_or(RLPDecodeError::InvalidLength)?;
539 match *tx_type {
541 0x0 => LegacyTransaction::decode(tx_encoding)
543 .map(|tx| (Transaction::LegacyTransaction(tx), remainder)), 0x1 => EIP2930Transaction::decode(tx_encoding)
546 .map(|tx| (Transaction::EIP2930Transaction(tx), remainder)),
547 0x2 => EIP1559Transaction::decode(tx_encoding)
549 .map(|tx| (Transaction::EIP1559Transaction(tx), remainder)),
550 0x3 => EIP4844Transaction::decode(tx_encoding)
552 .map(|tx| (Transaction::EIP4844Transaction(tx), remainder)),
553 0x4 => EIP7702Transaction::decode(tx_encoding)
555 .map(|tx| (Transaction::EIP7702Transaction(tx), remainder)),
556 0x7d => FeeTokenTransaction::decode(tx_encoding)
558 .map(|tx| (Transaction::FeeTokenTransaction(tx), remainder)),
559 0x6 => FrameTransaction::decode(tx_encoding)
561 .map(|tx| (Transaction::FrameTransaction(tx), remainder)),
562 0x7e => PrivilegedL2Transaction::decode(tx_encoding)
564 .map(|tx| (Transaction::PrivilegedL2Transaction(tx), remainder)),
565 ty => Err(RLPDecodeError::Custom(format!(
566 "Invalid transaction type: {ty}"
567 ))),
568 }
569 } else {
570 LegacyTransaction::decode_unfinished(rlp)
572 .map(|(tx, rem)| (Transaction::LegacyTransaction(tx), rem))
573 }
574 }
575}
576
577#[derive(Clone, Debug, PartialEq, Eq, Default, RSerialize, RDeserialize, Archive)]
579pub enum TxKind {
580 Call(#[rkyv(with=crate::rkyv_utils::H160Wrapper)] Address),
581 #[default]
582 Create,
583}
584
585impl RLPEncode for TxKind {
586 fn encode(&self, buf: &mut dyn bytes::BufMut) {
587 match self {
588 Self::Call(address) => address.encode(buf),
589 Self::Create => buf.put_u8(RLP_NULL),
590 }
591 }
592}
593
594impl RLPDecode for TxKind {
595 fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
596 let first_byte = rlp.first().ok_or(RLPDecodeError::InvalidLength)?;
597 if *first_byte == RLP_NULL {
598 return Ok((Self::Create, &rlp[1..]));
599 }
600 Address::decode_unfinished(rlp).map(|(t, rest)| (Self::Call(t), rest))
601 }
602}
603
604impl RLPEncode for LegacyTransaction {
605 fn encode(&self, buf: &mut dyn bytes::BufMut) {
606 Encoder::new(buf)
607 .encode_field(&self.nonce)
608 .encode_field(&self.gas_price)
609 .encode_field(&self.gas)
610 .encode_field(&self.to)
611 .encode_field(&self.value)
612 .encode_field(&self.data)
613 .encode_field(&self.v)
614 .encode_field(&self.r)
615 .encode_field(&self.s)
616 .finish();
617 }
618}
619
620impl RLPEncode for EIP2930Transaction {
621 fn encode(&self, buf: &mut dyn bytes::BufMut) {
622 Encoder::new(buf)
623 .encode_field(&self.chain_id)
624 .encode_field(&self.nonce)
625 .encode_field(&self.gas_price)
626 .encode_field(&self.gas_limit)
627 .encode_field(&self.to)
628 .encode_field(&self.value)
629 .encode_field(&self.data)
630 .encode_field(&self.access_list)
631 .encode_field(&self.signature_y_parity)
632 .encode_field(&self.signature_r)
633 .encode_field(&self.signature_s)
634 .finish()
635 }
636}
637
638impl RLPEncode for EIP1559Transaction {
639 fn encode(&self, buf: &mut dyn bytes::BufMut) {
640 Encoder::new(buf)
641 .encode_field(&self.chain_id)
642 .encode_field(&self.nonce)
643 .encode_field(&self.max_priority_fee_per_gas)
644 .encode_field(&self.max_fee_per_gas)
645 .encode_field(&self.gas_limit)
646 .encode_field(&self.to)
647 .encode_field(&self.value)
648 .encode_field(&self.data)
649 .encode_field(&self.access_list)
650 .encode_field(&self.signature_y_parity)
651 .encode_field(&self.signature_r)
652 .encode_field(&self.signature_s)
653 .finish()
654 }
655}
656
657impl RLPEncode for EIP4844Transaction {
658 fn encode(&self, buf: &mut dyn bytes::BufMut) {
659 Encoder::new(buf)
660 .encode_field(&self.chain_id)
661 .encode_field(&self.nonce)
662 .encode_field(&self.max_priority_fee_per_gas)
663 .encode_field(&self.max_fee_per_gas)
664 .encode_field(&self.gas)
665 .encode_field(&self.to)
666 .encode_field(&self.value)
667 .encode_field(&self.data)
668 .encode_field(&self.access_list)
669 .encode_field(&self.max_fee_per_blob_gas)
670 .encode_field(&self.blob_versioned_hashes)
671 .encode_field(&self.signature_y_parity)
672 .encode_field(&self.signature_r)
673 .encode_field(&self.signature_s)
674 .finish()
675 }
676}
677
678impl RLPEncode for EIP7702Transaction {
679 fn encode(&self, buf: &mut dyn bytes::BufMut) {
680 Encoder::new(buf)
681 .encode_field(&self.chain_id)
682 .encode_field(&self.nonce)
683 .encode_field(&self.max_priority_fee_per_gas)
684 .encode_field(&self.max_fee_per_gas)
685 .encode_field(&self.gas_limit)
686 .encode_field(&self.to)
687 .encode_field(&self.value)
688 .encode_field(&self.data)
689 .encode_field(&self.access_list)
690 .encode_field(&self.authorization_list)
691 .encode_field(&self.signature_y_parity)
692 .encode_field(&self.signature_r)
693 .encode_field(&self.signature_s)
694 .finish()
695 }
696}
697
698impl RLPEncode for PrivilegedL2Transaction {
699 fn encode(&self, buf: &mut dyn bytes::BufMut) {
700 Encoder::new(buf)
701 .encode_field(&self.chain_id)
702 .encode_field(&self.nonce)
703 .encode_field(&self.max_priority_fee_per_gas)
704 .encode_field(&self.max_fee_per_gas)
705 .encode_field(&self.gas_limit)
706 .encode_field(&self.to)
707 .encode_field(&self.value)
708 .encode_field(&self.data)
709 .encode_field(&self.access_list)
710 .encode_field(&self.from)
711 .finish()
712 }
713}
714
715impl RLPEncode for FeeTokenTransaction {
716 fn encode(&self, buf: &mut dyn bytes::BufMut) {
717 Encoder::new(buf)
718 .encode_field(&self.chain_id)
719 .encode_field(&self.nonce)
720 .encode_field(&self.max_priority_fee_per_gas)
721 .encode_field(&self.max_fee_per_gas)
722 .encode_field(&self.gas_limit)
723 .encode_field(&self.to)
724 .encode_field(&self.value)
725 .encode_field(&self.data)
726 .encode_field(&self.access_list)
727 .encode_field(&self.fee_token)
728 .encode_field(&self.signature_y_parity)
729 .encode_field(&self.signature_r)
730 .encode_field(&self.signature_s)
731 .finish()
732 }
733}
734
735impl PayloadRLPEncode for Transaction {
736 fn encode_payload(&self, buf: &mut dyn bytes::BufMut) {
737 match self {
738 Transaction::LegacyTransaction(tx) => tx.encode_payload(buf),
739 Transaction::EIP1559Transaction(tx) => tx.encode_payload(buf),
740 Transaction::EIP2930Transaction(tx) => tx.encode_payload(buf),
741 Transaction::EIP4844Transaction(tx) => tx.encode_payload(buf),
742 Transaction::EIP7702Transaction(tx) => tx.encode_payload(buf),
743 Transaction::PrivilegedL2Transaction(tx) => tx.encode_payload(buf),
744 Transaction::FeeTokenTransaction(tx) => tx.encode_payload(buf),
745 Transaction::FrameTransaction(tx) => tx.encode_payload(buf),
746 }
747 }
748}
749
750impl PayloadRLPEncode for LegacyTransaction {
751 fn encode_payload(&self, buf: &mut dyn bytes::BufMut) {
752 Encoder::new(buf)
753 .encode_field(&self.nonce)
754 .encode_field(&self.gas_price)
755 .encode_field(&self.gas)
756 .encode_field(&self.to)
757 .encode_field(&self.value)
758 .encode_field(&self.data)
759 .finish();
760 }
761}
762
763impl PayloadRLPEncode for EIP1559Transaction {
764 fn encode_payload(&self, buf: &mut dyn bytes::BufMut) {
765 Encoder::new(buf)
766 .encode_field(&self.chain_id)
767 .encode_field(&self.nonce)
768 .encode_field(&self.max_priority_fee_per_gas)
769 .encode_field(&self.max_fee_per_gas)
770 .encode_field(&self.gas_limit)
771 .encode_field(&self.to)
772 .encode_field(&self.value)
773 .encode_field(&self.data)
774 .encode_field(&self.access_list)
775 .finish();
776 }
777}
778
779impl PayloadRLPEncode for EIP2930Transaction {
780 fn encode_payload(&self, buf: &mut dyn bytes::BufMut) {
781 Encoder::new(buf)
782 .encode_field(&self.chain_id)
783 .encode_field(&self.nonce)
784 .encode_field(&self.gas_price)
785 .encode_field(&self.gas_limit)
786 .encode_field(&self.to)
787 .encode_field(&self.value)
788 .encode_field(&self.data)
789 .encode_field(&self.access_list)
790 .finish();
791 }
792}
793
794impl PayloadRLPEncode for EIP4844Transaction {
795 fn encode_payload(&self, buf: &mut dyn bytes::BufMut) {
796 Encoder::new(buf)
797 .encode_field(&self.chain_id)
798 .encode_field(&self.nonce)
799 .encode_field(&self.max_priority_fee_per_gas)
800 .encode_field(&self.max_fee_per_gas)
801 .encode_field(&self.gas)
802 .encode_field(&self.to)
803 .encode_field(&self.value)
804 .encode_field(&self.data)
805 .encode_field(&self.access_list)
806 .encode_field(&self.max_fee_per_blob_gas)
807 .encode_field(&self.blob_versioned_hashes)
808 .finish();
809 }
810}
811
812impl PayloadRLPEncode for EIP7702Transaction {
813 fn encode_payload(&self, buf: &mut dyn bytes::BufMut) {
814 Encoder::new(buf)
815 .encode_field(&self.chain_id)
816 .encode_field(&self.nonce)
817 .encode_field(&self.max_priority_fee_per_gas)
818 .encode_field(&self.max_fee_per_gas)
819 .encode_field(&self.gas_limit)
820 .encode_field(&self.to)
821 .encode_field(&self.value)
822 .encode_field(&self.data)
823 .encode_field(&self.access_list)
824 .encode_field(&self.authorization_list)
825 .finish();
826 }
827}
828
829impl PayloadRLPEncode for PrivilegedL2Transaction {
830 fn encode_payload(&self, buf: &mut dyn bytes::BufMut) {
831 Encoder::new(buf)
832 .encode_field(&self.chain_id)
833 .encode_field(&self.nonce)
834 .encode_field(&self.max_priority_fee_per_gas)
835 .encode_field(&self.max_fee_per_gas)
836 .encode_field(&self.gas_limit)
837 .encode_field(&self.to)
838 .encode_field(&self.value)
839 .encode_field(&self.data)
840 .encode_field(&self.access_list)
841 .encode_field(&self.from)
842 .finish();
843 }
844}
845
846impl PayloadRLPEncode for FeeTokenTransaction {
847 fn encode_payload(&self, buf: &mut dyn bytes::BufMut) {
848 Encoder::new(buf)
849 .encode_field(&self.chain_id)
850 .encode_field(&self.nonce)
851 .encode_field(&self.max_priority_fee_per_gas)
852 .encode_field(&self.max_fee_per_gas)
853 .encode_field(&self.gas_limit)
854 .encode_field(&self.to)
855 .encode_field(&self.value)
856 .encode_field(&self.data)
857 .encode_field(&self.access_list)
858 .encode_field(&self.fee_token)
859 .finish();
860 }
861}
862
863impl RLPDecode for LegacyTransaction {
864 fn decode_unfinished(rlp: &[u8]) -> Result<(LegacyTransaction, &[u8]), RLPDecodeError> {
865 let decoder = Decoder::new(rlp)?;
866 let (nonce, decoder) = decoder.decode_field("nonce")?;
867 let (gas_price, decoder) = decoder.decode_field("gas_price")?;
868 let (gas, decoder) = decoder.decode_field("gas")?;
869 let (to, decoder) = decoder.decode_field("to")?;
870 let (value, decoder) = decoder.decode_field("value")?;
871 let (data, decoder) = decoder.decode_field("data")?;
872 let (v, decoder) = decoder.decode_field("v")?;
873 let (r, decoder) = decoder.decode_field("r")?;
874 let (s, decoder) = decoder.decode_field("s")?;
875 let inner_hash = OnceCell::new();
876 let sender_cache = OnceCell::new();
877
878 let tx = LegacyTransaction {
879 nonce,
880 gas_price,
881 gas,
882 to,
883 value,
884 data,
885 v,
886 r,
887 s,
888 inner_hash,
889 sender_cache,
890 };
891 Ok((tx, decoder.finish()?))
892 }
893}
894
895impl RLPDecode for EIP2930Transaction {
896 fn decode_unfinished(rlp: &[u8]) -> Result<(EIP2930Transaction, &[u8]), RLPDecodeError> {
897 let decoder = Decoder::new(rlp)?;
898 let (chain_id, decoder) = decoder.decode_field("chain_id")?;
899 let (nonce, decoder) = decoder.decode_field("nonce")?;
900 let (gas_price, decoder) = decoder.decode_field("gas_price")?;
901 let (gas_limit, decoder) = decoder.decode_field("gas_limit")?;
902 let (to, decoder) = decoder.decode_field("to")?;
903 let (value, decoder) = decoder.decode_field("value")?;
904 let (data, decoder) = decoder.decode_field("data")?;
905 let (access_list, decoder) = decoder.decode_field("access_list")?;
906 let (signature_y_parity, decoder) = decoder.decode_field("signature_y_parity")?;
907 let (signature_r, decoder) = decoder.decode_field("signature_r")?;
908 let (signature_s, decoder) = decoder.decode_field("signature_s")?;
909 let inner_hash = OnceCell::new();
910 let sender_cache = OnceCell::new();
911 let cached_canonical = OnceCell::new();
912
913 let tx = EIP2930Transaction {
914 chain_id,
915 nonce,
916 gas_price,
917 gas_limit,
918 to,
919 value,
920 data,
921 access_list,
922 signature_y_parity,
923 signature_r,
924 signature_s,
925 inner_hash,
926 sender_cache,
927 cached_canonical,
928 };
929 Ok((tx, decoder.finish()?))
930 }
931}
932
933impl RLPDecode for EIP1559Transaction {
934 fn decode_unfinished(rlp: &[u8]) -> Result<(EIP1559Transaction, &[u8]), RLPDecodeError> {
935 let decoder = Decoder::new(rlp)?;
936 let (chain_id, decoder) = decoder.decode_field("chain_id")?;
937 let (nonce, decoder) = decoder.decode_field("nonce")?;
938 let (max_priority_fee_per_gas, decoder) =
939 decoder.decode_field("max_priority_fee_per_gas")?;
940 let (max_fee_per_gas, decoder) = decoder.decode_field("max_fee_per_gas")?;
941 let (gas_limit, decoder) = decoder.decode_field("gas_limit")?;
942 let (to, decoder) = decoder.decode_field("to")?;
943 let (value, decoder) = decoder.decode_field("value")?;
944 let (data, decoder) = decoder.decode_field("data")?;
945 let (access_list, decoder) = decoder.decode_field("access_list")?;
946 let (signature_y_parity, decoder) = decoder.decode_field("signature_y_parity")?;
947 let (signature_r, decoder) = decoder.decode_field("signature_r")?;
948 let (signature_s, decoder) = decoder.decode_field("signature_s")?;
949 let inner_hash = OnceCell::new();
950 let sender_cache = OnceCell::new();
951 let cached_canonical = OnceCell::new();
952
953 let tx = EIP1559Transaction {
954 chain_id,
955 nonce,
956 max_priority_fee_per_gas,
957 max_fee_per_gas,
958 gas_limit,
959 to,
960 value,
961 data,
962 access_list,
963 signature_y_parity,
964 signature_r,
965 signature_s,
966 inner_hash,
967 sender_cache,
968 cached_canonical,
969 };
970 Ok((tx, decoder.finish()?))
971 }
972}
973
974impl RLPDecode for EIP4844Transaction {
975 fn decode_unfinished(rlp: &[u8]) -> Result<(EIP4844Transaction, &[u8]), RLPDecodeError> {
976 let decoder = Decoder::new(rlp)?;
977 let (chain_id, decoder) = decoder.decode_field("chain_id")?;
978 let (nonce, decoder) = decoder.decode_field("nonce")?;
979 let (max_priority_fee_per_gas, decoder) =
980 decoder.decode_field("max_priority_fee_per_gas")?;
981 let (max_fee_per_gas, decoder) = decoder.decode_field("max_fee_per_gas")?;
982 let (gas, decoder) = decoder.decode_field("gas")?;
983 let (to, decoder) = decoder.decode_field("to")?;
984 let (value, decoder) = decoder.decode_field("value")?;
985 let (data, decoder) = decoder.decode_field("data")?;
986 let (access_list, decoder) = decoder.decode_field("access_list")?;
987 let (max_fee_per_blob_gas, decoder) = decoder.decode_field("max_fee_per_blob_gas")?;
988 let (blob_versioned_hashes, decoder) = decoder.decode_field("blob_versioned_hashes")?;
989 let (signature_y_parity, decoder) = decoder.decode_field("signature_y_parity")?;
990 let (signature_r, decoder) = decoder.decode_field("signature_r")?;
991 let (signature_s, decoder) = decoder.decode_field("signature_s")?;
992 let inner_hash = OnceCell::new();
993 let sender_cache = OnceCell::new();
994 let cached_canonical = OnceCell::new();
995
996 let tx = EIP4844Transaction {
997 chain_id,
998 nonce,
999 max_priority_fee_per_gas,
1000 max_fee_per_gas,
1001 gas,
1002 to,
1003 value,
1004 data,
1005 access_list,
1006 max_fee_per_blob_gas,
1007 blob_versioned_hashes,
1008 signature_y_parity,
1009 signature_r,
1010 signature_s,
1011 inner_hash,
1012 sender_cache,
1013 cached_canonical,
1014 };
1015 Ok((tx, decoder.finish()?))
1016 }
1017}
1018
1019impl RLPDecode for EIP7702Transaction {
1020 fn decode_unfinished(rlp: &[u8]) -> Result<(EIP7702Transaction, &[u8]), RLPDecodeError> {
1021 let decoder = Decoder::new(rlp)?;
1022 let (chain_id, decoder) = decoder.decode_field("chain_id")?;
1023 let (nonce, decoder) = decoder.decode_field("nonce")?;
1024 let (max_priority_fee_per_gas, decoder) =
1025 decoder.decode_field("max_priority_fee_per_gas")?;
1026 let (max_fee_per_gas, decoder) = decoder.decode_field("max_fee_per_gas")?;
1027 let (gas_limit, decoder) = decoder.decode_field("gas_limit")?;
1028 let (to, decoder) = decoder.decode_field("to")?;
1029 let (value, decoder) = decoder.decode_field("value")?;
1030 let (data, decoder) = decoder.decode_field("data")?;
1031 let (access_list, decoder) = decoder.decode_field("access_list")?;
1032 let (authorization_list, decoder) = decoder.decode_field("authorization_list")?;
1033 let (signature_y_parity, decoder) = decoder.decode_field("signature_y_parity")?;
1034 let (signature_r, decoder) = decoder.decode_field("signature_r")?;
1035 let (signature_s, decoder) = decoder.decode_field("signature_s")?;
1036 let inner_hash = OnceCell::new();
1037 let sender_cache = OnceCell::new();
1038 let cached_canonical = OnceCell::new();
1039
1040 let tx = EIP7702Transaction {
1041 chain_id,
1042 nonce,
1043 max_priority_fee_per_gas,
1044 max_fee_per_gas,
1045 gas_limit,
1046 to,
1047 value,
1048 data,
1049 access_list,
1050 authorization_list,
1051 signature_y_parity,
1052 signature_r,
1053 signature_s,
1054 inner_hash,
1055 sender_cache,
1056 cached_canonical,
1057 };
1058 Ok((tx, decoder.finish()?))
1059 }
1060}
1061
1062impl RLPDecode for PrivilegedL2Transaction {
1063 fn decode_unfinished(rlp: &[u8]) -> Result<(PrivilegedL2Transaction, &[u8]), RLPDecodeError> {
1064 let decoder = Decoder::new(rlp)?;
1065 let (chain_id, decoder) = decoder.decode_field("chain_id")?;
1066 let (nonce, decoder) = decoder.decode_field("nonce")?;
1067 let (max_priority_fee_per_gas, decoder) =
1068 decoder.decode_field("max_priority_fee_per_gas")?;
1069 let (max_fee_per_gas, decoder) = decoder.decode_field("max_fee_per_gas")?;
1070 let (gas_limit, decoder) = decoder.decode_field::<u64>("gas_limit")?;
1071 let (to, decoder) = decoder.decode_field("to")?;
1072 let (value, decoder) = decoder.decode_field("value")?;
1073 let (data, decoder) = decoder.decode_field("data")?;
1074 let (access_list, decoder) = decoder.decode_field("access_list")?;
1075 let (from, decoder) = decoder.decode_field("from")?;
1076 let inner_hash = OnceCell::new();
1077 let sender_cache = OnceCell::new();
1078 let cached_canonical = OnceCell::new();
1079
1080 let tx = PrivilegedL2Transaction {
1081 chain_id,
1082 nonce,
1083 max_priority_fee_per_gas,
1084 max_fee_per_gas,
1085 gas_limit,
1086 to,
1087 value,
1088 data,
1089 access_list,
1090 from,
1091 inner_hash,
1092 sender_cache,
1093 cached_canonical,
1094 };
1095 Ok((tx, decoder.finish()?))
1096 }
1097}
1098
1099impl RLPDecode for FeeTokenTransaction {
1100 fn decode_unfinished(rlp: &[u8]) -> Result<(FeeTokenTransaction, &[u8]), RLPDecodeError> {
1101 let decoder = Decoder::new(rlp)?;
1102 let (chain_id, decoder) = decoder.decode_field("chain_id")?;
1103 let (nonce, decoder) = decoder.decode_field("nonce")?;
1104 let (max_priority_fee_per_gas, decoder) =
1105 decoder.decode_field("max_priority_fee_per_gas")?;
1106 let (max_fee_per_gas, decoder) = decoder.decode_field("max_fee_per_gas")?;
1107 let (gas_limit, decoder) = decoder.decode_field("gas_limit")?;
1108 let (to, decoder) = decoder.decode_field("to")?;
1109 let (value, decoder) = decoder.decode_field("value")?;
1110 let (data, decoder) = decoder.decode_field("data")?;
1111 let (access_list, decoder) = decoder.decode_field("access_list")?;
1112 let (fee_token, decoder) = decoder.decode_field("fee_token")?;
1113 let (signature_y_parity, decoder) = decoder.decode_field("signature_y_parity")?;
1114 let (signature_r, decoder) = decoder.decode_field("signature_r")?;
1115 let (signature_s, decoder) = decoder.decode_field("signature_s")?;
1116 let inner_hash = OnceCell::new();
1117 let sender_cache = OnceCell::new();
1118 let cached_canonical = OnceCell::new();
1119
1120 let tx = FeeTokenTransaction {
1121 chain_id,
1122 nonce,
1123 max_priority_fee_per_gas,
1124 max_fee_per_gas,
1125 gas_limit,
1126 to,
1127 value,
1128 data,
1129 access_list,
1130 fee_token,
1131 signature_y_parity,
1132 signature_r,
1133 signature_s,
1134 inner_hash,
1135 sender_cache,
1136 cached_canonical,
1137 };
1138 Ok((tx, decoder.finish()?))
1139 }
1140}
1141
1142impl Transaction {
1143 pub fn sender(&self, crypto: &dyn Crypto) -> Result<Address, CryptoError> {
1144 if let Transaction::FrameTransaction(tx) = self {
1146 return Ok(tx.sender);
1147 }
1148 let sender_cache = match self {
1149 Transaction::LegacyTransaction(tx) => &tx.sender_cache,
1150 Transaction::EIP2930Transaction(tx) => &tx.sender_cache,
1151 Transaction::EIP1559Transaction(tx) => &tx.sender_cache,
1152 Transaction::EIP4844Transaction(tx) => &tx.sender_cache,
1153 Transaction::EIP7702Transaction(tx) => &tx.sender_cache,
1154 Transaction::PrivilegedL2Transaction(tx) => &tx.sender_cache,
1155 Transaction::FeeTokenTransaction(tx) => &tx.sender_cache,
1156 Transaction::FrameTransaction(_) => unreachable!(),
1157 };
1158 sender_cache
1159 .get_or_try_init(|| {
1160 let tx_hash = self.hash(crypto);
1161 let mut cache = GLOBAL_SIGNER_CACHE
1163 .lock()
1164 .unwrap_or_else(|e| e.into_inner());
1165 if let Some(&addr) = cache.get(&tx_hash) {
1166 return Ok(addr);
1167 }
1168 drop(cache);
1169 let sender = self.compute_sender(crypto)?;
1171 GLOBAL_SIGNER_CACHE
1173 .lock()
1174 .unwrap_or_else(|e| e.into_inner())
1175 .put(tx_hash, sender);
1176 Ok(sender)
1177 })
1178 .copied()
1179 }
1180
1181 fn compute_sender(&self, crypto: &dyn Crypto) -> Result<Address, CryptoError> {
1182 let (buf, sig) = match self {
1183 Transaction::LegacyTransaction(tx) => {
1184 let v = u64::try_from(tx.v).map_err(|_| CryptoError::InvalidSignature)?;
1185 let signature_y_parity = match self.chain_id() {
1190 Some(chain_id) => match v.checked_sub(35 + chain_id * 2) {
1191 Some(0) => false,
1192 Some(1) => true,
1193 _ => return Err(CryptoError::InvalidSignature),
1194 },
1195 None => match v {
1196 27 => false,
1197 28 => true,
1198 _ => return Err(CryptoError::InvalidSignature),
1199 },
1200 };
1201 let mut buf = vec![];
1202 match self.chain_id() {
1203 None => Encoder::new(&mut buf)
1204 .encode_field(&tx.nonce)
1205 .encode_field(&tx.gas_price)
1206 .encode_field(&tx.gas)
1207 .encode_field(&tx.to)
1208 .encode_field(&tx.value)
1209 .encode_field(&tx.data)
1210 .finish(),
1211 Some(chain_id) => Encoder::new(&mut buf)
1212 .encode_field(&tx.nonce)
1213 .encode_field(&tx.gas_price)
1214 .encode_field(&tx.gas)
1215 .encode_field(&tx.to)
1216 .encode_field(&tx.value)
1217 .encode_field(&tx.data)
1218 .encode_field(&chain_id)
1219 .encode_field(&0u8)
1220 .encode_field(&0u8)
1221 .finish(),
1222 }
1223 let mut sig = [0u8; 65];
1224 sig[..32].copy_from_slice(&tx.r.to_big_endian());
1225 sig[32..64].copy_from_slice(&tx.s.to_big_endian());
1226 sig[64] = signature_y_parity as u8;
1227 (buf, sig)
1228 }
1229 Transaction::EIP2930Transaction(tx) => {
1230 let mut buf = vec![self.tx_type() as u8];
1231 Encoder::new(&mut buf)
1232 .encode_field(&tx.chain_id)
1233 .encode_field(&tx.nonce)
1234 .encode_field(&tx.gas_price)
1235 .encode_field(&tx.gas_limit)
1236 .encode_field(&tx.to)
1237 .encode_field(&tx.value)
1238 .encode_field(&tx.data)
1239 .encode_field(&tx.access_list)
1240 .finish();
1241 let mut sig = [0u8; 65];
1242 sig[..32].copy_from_slice(&tx.signature_r.to_big_endian());
1243 sig[32..64].copy_from_slice(&tx.signature_s.to_big_endian());
1244 sig[64] = tx.signature_y_parity as u8;
1245 (buf, sig)
1246 }
1247 Transaction::EIP1559Transaction(tx) => {
1248 let mut buf = vec![self.tx_type() as u8];
1249 Encoder::new(&mut buf)
1250 .encode_field(&tx.chain_id)
1251 .encode_field(&tx.nonce)
1252 .encode_field(&tx.max_priority_fee_per_gas)
1253 .encode_field(&tx.max_fee_per_gas)
1254 .encode_field(&tx.gas_limit)
1255 .encode_field(&tx.to)
1256 .encode_field(&tx.value)
1257 .encode_field(&tx.data)
1258 .encode_field(&tx.access_list)
1259 .finish();
1260 let mut sig = [0u8; 65];
1261 sig[..32].copy_from_slice(&tx.signature_r.to_big_endian());
1262 sig[32..64].copy_from_slice(&tx.signature_s.to_big_endian());
1263 sig[64] = tx.signature_y_parity as u8;
1264 (buf, sig)
1265 }
1266 Transaction::EIP4844Transaction(tx) => {
1267 let mut buf = vec![self.tx_type() as u8];
1268 Encoder::new(&mut buf)
1269 .encode_field(&tx.chain_id)
1270 .encode_field(&tx.nonce)
1271 .encode_field(&tx.max_priority_fee_per_gas)
1272 .encode_field(&tx.max_fee_per_gas)
1273 .encode_field(&tx.gas)
1274 .encode_field(&tx.to)
1275 .encode_field(&tx.value)
1276 .encode_field(&tx.data)
1277 .encode_field(&tx.access_list)
1278 .encode_field(&tx.max_fee_per_blob_gas)
1279 .encode_field(&tx.blob_versioned_hashes)
1280 .finish();
1281 let mut sig = [0u8; 65];
1282 sig[..32].copy_from_slice(&tx.signature_r.to_big_endian());
1283 sig[32..64].copy_from_slice(&tx.signature_s.to_big_endian());
1284 sig[64] = tx.signature_y_parity as u8;
1285 (buf, sig)
1286 }
1287 Transaction::EIP7702Transaction(tx) => {
1288 let mut buf = vec![self.tx_type() as u8];
1289 Encoder::new(&mut buf)
1290 .encode_field(&tx.chain_id)
1291 .encode_field(&tx.nonce)
1292 .encode_field(&tx.max_priority_fee_per_gas)
1293 .encode_field(&tx.max_fee_per_gas)
1294 .encode_field(&tx.gas_limit)
1295 .encode_field(&tx.to)
1296 .encode_field(&tx.value)
1297 .encode_field(&tx.data)
1298 .encode_field(&tx.access_list)
1299 .encode_field(&tx.authorization_list)
1300 .finish();
1301 let mut sig = [0u8; 65];
1302 sig[..32].copy_from_slice(&tx.signature_r.to_big_endian());
1303 sig[32..64].copy_from_slice(&tx.signature_s.to_big_endian());
1304 sig[64] = tx.signature_y_parity as u8;
1305 (buf, sig)
1306 }
1307 Transaction::PrivilegedL2Transaction(tx) => return Ok(tx.from),
1308 Transaction::FrameTransaction(tx) => return Ok(tx.sender),
1309 Transaction::FeeTokenTransaction(tx) => {
1310 let mut buf = vec![self.tx_type() as u8];
1311 Encoder::new(&mut buf)
1312 .encode_field(&tx.chain_id)
1313 .encode_field(&tx.nonce)
1314 .encode_field(&tx.max_priority_fee_per_gas)
1315 .encode_field(&tx.max_fee_per_gas)
1316 .encode_field(&tx.gas_limit)
1317 .encode_field(&tx.to)
1318 .encode_field(&tx.value)
1319 .encode_field(&tx.data)
1320 .encode_field(&tx.access_list)
1321 .encode_field(&tx.fee_token)
1322 .finish();
1323 let mut sig = [0u8; 65];
1324 sig[..32].copy_from_slice(&tx.signature_r.to_big_endian());
1325 sig[32..64].copy_from_slice(&tx.signature_s.to_big_endian());
1326 sig[64] = tx.signature_y_parity as u8;
1327 (buf, sig)
1328 }
1329 };
1330 let msg = crypto.keccak256(&buf);
1331 crypto.recover_signer(&sig, &msg)
1332 }
1333
1334 pub fn gas_limit(&self) -> u64 {
1335 match self {
1336 Transaction::LegacyTransaction(tx) => tx.gas,
1337 Transaction::EIP2930Transaction(tx) => tx.gas_limit,
1338 Transaction::EIP1559Transaction(tx) => tx.gas_limit,
1339 Transaction::EIP7702Transaction(tx) => tx.gas_limit,
1340 Transaction::EIP4844Transaction(tx) => tx.gas,
1341 Transaction::PrivilegedL2Transaction(tx) => tx.gas_limit,
1342 Transaction::FeeTokenTransaction(tx) => tx.gas_limit,
1343 Transaction::FrameTransaction(tx) => tx.total_gas_limit(),
1344 }
1345 }
1346
1347 pub fn gas_price(&self) -> U256 {
1349 match self {
1350 Transaction::LegacyTransaction(tx) => tx.gas_price,
1351 Transaction::EIP2930Transaction(tx) => tx.gas_price,
1352 Transaction::EIP1559Transaction(tx) => U256::from(tx.max_fee_per_gas),
1353 Transaction::EIP7702Transaction(tx) => U256::from(tx.max_fee_per_gas),
1354 Transaction::EIP4844Transaction(tx) => U256::from(tx.max_fee_per_gas),
1355 Transaction::PrivilegedL2Transaction(tx) => U256::from(tx.max_fee_per_gas),
1356 Transaction::FeeTokenTransaction(tx) => U256::from(tx.max_fee_per_gas),
1357 Transaction::FrameTransaction(tx) => U256::from(tx.max_fee_per_gas),
1358 }
1359 }
1360
1361 pub fn to(&self) -> TxKind {
1362 match self {
1363 Transaction::LegacyTransaction(tx) => tx.to.clone(),
1364 Transaction::EIP2930Transaction(tx) => tx.to.clone(),
1365 Transaction::EIP1559Transaction(tx) => tx.to.clone(),
1366 Transaction::EIP4844Transaction(tx) => TxKind::Call(tx.to),
1367 Transaction::EIP7702Transaction(tx) => TxKind::Call(tx.to),
1368 Transaction::PrivilegedL2Transaction(tx) => tx.to.clone(),
1369 Transaction::FeeTokenTransaction(tx) => tx.to.clone(),
1370 Transaction::FrameTransaction(tx) => TxKind::Call(tx.sender),
1371 }
1372 }
1373
1374 pub fn value(&self) -> U256 {
1375 match self {
1376 Transaction::LegacyTransaction(tx) => tx.value,
1377 Transaction::EIP2930Transaction(tx) => tx.value,
1378 Transaction::EIP1559Transaction(tx) => tx.value,
1379 Transaction::EIP4844Transaction(tx) => tx.value,
1380 Transaction::EIP7702Transaction(tx) => tx.value,
1381 Transaction::PrivilegedL2Transaction(tx) => tx.value,
1382 Transaction::FeeTokenTransaction(tx) => tx.value,
1383 Transaction::FrameTransaction(_) => U256::zero(),
1384 }
1385 }
1386
1387 pub fn max_priority_fee(&self) -> Option<u64> {
1388 match self {
1389 Transaction::LegacyTransaction(_tx) => None,
1390 Transaction::EIP2930Transaction(_tx) => None,
1391 Transaction::EIP1559Transaction(tx) => Some(tx.max_priority_fee_per_gas),
1392 Transaction::EIP4844Transaction(tx) => Some(tx.max_priority_fee_per_gas),
1393 Transaction::EIP7702Transaction(tx) => Some(tx.max_priority_fee_per_gas),
1394 Transaction::PrivilegedL2Transaction(tx) => Some(tx.max_priority_fee_per_gas),
1395 Transaction::FeeTokenTransaction(tx) => Some(tx.max_priority_fee_per_gas),
1396 Transaction::FrameTransaction(tx) => Some(tx.max_priority_fee_per_gas),
1397 }
1398 }
1399
1400 pub fn chain_id(&self) -> Option<u64> {
1401 match self {
1402 Transaction::LegacyTransaction(tx) => derive_legacy_chain_id(tx.v),
1403 Transaction::EIP2930Transaction(tx) => Some(tx.chain_id),
1404 Transaction::EIP1559Transaction(tx) => Some(tx.chain_id),
1405 Transaction::EIP4844Transaction(tx) => Some(tx.chain_id),
1406 Transaction::EIP7702Transaction(tx) => Some(tx.chain_id),
1407 Transaction::PrivilegedL2Transaction(tx) => Some(tx.chain_id),
1408 Transaction::FeeTokenTransaction(tx) => Some(tx.chain_id),
1409 Transaction::FrameTransaction(tx) => Some(tx.chain_id),
1410 }
1411 }
1412
1413 pub fn access_list(&self) -> &AccessList {
1414 static EMPTY_ACCESS_LIST: AccessList = Vec::new();
1415 match self {
1416 Transaction::LegacyTransaction(_tx) => &EMPTY_ACCESS_LIST,
1417 Transaction::EIP2930Transaction(tx) => &tx.access_list,
1418 Transaction::EIP1559Transaction(tx) => &tx.access_list,
1419 Transaction::EIP4844Transaction(tx) => &tx.access_list,
1420 Transaction::EIP7702Transaction(tx) => &tx.access_list,
1421 Transaction::PrivilegedL2Transaction(tx) => &tx.access_list,
1422 Transaction::FeeTokenTransaction(tx) => &tx.access_list,
1423 Transaction::FrameTransaction(_) => &EMPTY_ACCESS_LIST,
1424 }
1425 }
1426 pub fn authorization_list(&self) -> Option<&AuthorizationList> {
1427 match self {
1428 Transaction::LegacyTransaction(_) => None,
1429 Transaction::EIP2930Transaction(_) => None,
1430 Transaction::EIP1559Transaction(_) => None,
1431 Transaction::EIP4844Transaction(_) => None,
1432 Transaction::EIP7702Transaction(tx) => Some(&tx.authorization_list),
1433 Transaction::PrivilegedL2Transaction(_) => None,
1434 Transaction::FeeTokenTransaction(_) => None,
1435 Transaction::FrameTransaction(_) => None,
1436 }
1437 }
1438
1439 pub fn nonce(&self) -> u64 {
1440 match self {
1441 Transaction::LegacyTransaction(tx) => tx.nonce,
1442 Transaction::EIP2930Transaction(tx) => tx.nonce,
1443 Transaction::EIP1559Transaction(tx) => tx.nonce,
1444 Transaction::EIP4844Transaction(tx) => tx.nonce,
1445 Transaction::EIP7702Transaction(tx) => tx.nonce,
1446 Transaction::PrivilegedL2Transaction(tx) => tx.nonce,
1447 Transaction::FeeTokenTransaction(tx) => tx.nonce,
1448 Transaction::FrameTransaction(tx) => tx.nonce,
1449 }
1450 }
1451
1452 pub fn data(&self) -> &Bytes {
1453 match self {
1454 Transaction::LegacyTransaction(tx) => &tx.data,
1455 Transaction::EIP2930Transaction(tx) => &tx.data,
1456 Transaction::EIP1559Transaction(tx) => &tx.data,
1457 Transaction::EIP4844Transaction(tx) => &tx.data,
1458 Transaction::EIP7702Transaction(tx) => &tx.data,
1459 Transaction::PrivilegedL2Transaction(tx) => &tx.data,
1460 Transaction::FeeTokenTransaction(tx) => &tx.data,
1461 Transaction::FrameTransaction(_) => {
1462 static EMPTY_DATA: Bytes = Bytes::new();
1463 &EMPTY_DATA
1464 }
1465 }
1466 }
1467
1468 pub fn blob_versioned_hashes(&self) -> Vec<H256> {
1469 match self {
1470 Transaction::LegacyTransaction(_) => Vec::new(),
1471 Transaction::EIP2930Transaction(_) => Vec::new(),
1472 Transaction::EIP1559Transaction(_) => Vec::new(),
1473 Transaction::EIP4844Transaction(tx) => tx.blob_versioned_hashes.clone(),
1474 Transaction::EIP7702Transaction(_) => Vec::new(),
1475 Transaction::PrivilegedL2Transaction(_) => Vec::new(),
1476 Transaction::FeeTokenTransaction(_) => Vec::new(),
1477 Transaction::FrameTransaction(tx) => tx.blob_versioned_hashes.clone(),
1478 }
1479 }
1480
1481 pub fn max_fee_per_blob_gas(&self) -> Option<U256> {
1482 match self {
1483 Transaction::LegacyTransaction(_) => None,
1484 Transaction::EIP2930Transaction(_) => None,
1485 Transaction::EIP1559Transaction(_) => None,
1486 Transaction::EIP4844Transaction(tx) => Some(tx.max_fee_per_blob_gas),
1487 Transaction::EIP7702Transaction(_) => None,
1488 Transaction::PrivilegedL2Transaction(_) => None,
1489 Transaction::FeeTokenTransaction(_) => None,
1490 Transaction::FrameTransaction(tx) => {
1491 if tx.blob_versioned_hashes.is_empty() {
1492 None
1493 } else {
1494 Some(tx.max_fee_per_blob_gas)
1495 }
1496 }
1497 }
1498 }
1499
1500 pub fn is_contract_creation(&self) -> bool {
1501 match &self {
1502 Transaction::LegacyTransaction(t) => matches!(t.to, TxKind::Create),
1503 Transaction::EIP2930Transaction(t) => matches!(t.to, TxKind::Create),
1504 Transaction::EIP1559Transaction(t) => matches!(t.to, TxKind::Create),
1505 Transaction::EIP4844Transaction(_) => false,
1506 Transaction::EIP7702Transaction(_) => false,
1507 Transaction::PrivilegedL2Transaction(t) => matches!(t.to, TxKind::Create),
1508 Transaction::FeeTokenTransaction(t) => matches!(t.to, TxKind::Create),
1509 Transaction::FrameTransaction(_) => false,
1510 }
1511 }
1512
1513 pub fn is_privileged(&self) -> bool {
1514 matches!(self, Transaction::PrivilegedL2Transaction(_))
1515 }
1516
1517 pub fn max_fee_per_gas(&self) -> Option<u64> {
1518 match self {
1519 Transaction::LegacyTransaction(_tx) => None,
1520 Transaction::EIP2930Transaction(_tx) => None,
1521 Transaction::EIP1559Transaction(tx) => Some(tx.max_fee_per_gas),
1522 Transaction::EIP4844Transaction(tx) => Some(tx.max_fee_per_gas),
1523 Transaction::EIP7702Transaction(tx) => Some(tx.max_fee_per_gas),
1524 Transaction::PrivilegedL2Transaction(tx) => Some(tx.max_fee_per_gas),
1525 Transaction::FeeTokenTransaction(tx) => Some(tx.max_fee_per_gas),
1526 Transaction::FrameTransaction(tx) => Some(tx.max_fee_per_gas),
1527 }
1528 }
1529
1530 fn compute_hash(&self, crypto: &dyn Crypto) -> H256 {
1531 if let Transaction::PrivilegedL2Transaction(tx) = self {
1532 return tx.get_privileged_hash().unwrap_or_default();
1533 }
1534 H256(crypto.keccak256(&self.encode_canonical_to_vec()))
1535 }
1536
1537 pub fn hash(&self, crypto: &dyn Crypto) -> H256 {
1538 let inner_hash = match self {
1539 Transaction::LegacyTransaction(tx) => &tx.inner_hash,
1540 Transaction::EIP2930Transaction(tx) => &tx.inner_hash,
1541 Transaction::EIP1559Transaction(tx) => &tx.inner_hash,
1542 Transaction::EIP4844Transaction(tx) => &tx.inner_hash,
1543 Transaction::EIP7702Transaction(tx) => &tx.inner_hash,
1544 Transaction::PrivilegedL2Transaction(tx) => &tx.inner_hash,
1545 Transaction::FeeTokenTransaction(tx) => &tx.inner_hash,
1546 Transaction::FrameTransaction(tx) => &tx.inner_hash,
1547 };
1548
1549 *inner_hash.get_or_init(|| self.compute_hash(crypto))
1550 }
1551
1552 pub fn gas_tip_cap(&self) -> U256 {
1553 self.max_priority_fee()
1554 .map(U256::from)
1555 .unwrap_or_else(|| self.gas_price())
1556 }
1557
1558 pub fn gas_fee_cap(&self) -> U256 {
1559 self.max_fee_per_gas()
1560 .map(U256::from)
1561 .unwrap_or_else(|| self.gas_price())
1562 }
1563
1564 pub fn effective_gas_tip(&self, base_fee: Option<u64>) -> Option<U256> {
1568 let tip_cap = self.gas_tip_cap();
1569 let Some(base_fee) = base_fee else {
1570 return Some(tip_cap);
1571 };
1572 let base_fee = U256::from(base_fee);
1573 let fee_cap = self.gas_fee_cap();
1574 let tip = fee_cap.checked_sub(base_fee)?;
1575 Some(min(tip, tip_cap))
1576 }
1577
1578 pub fn protected(&self) -> bool {
1581 match self {
1582 Transaction::LegacyTransaction(tx) if tx.v.bits() <= 8 => {
1583 let v = tx.v.low_u64();
1584 v != 27 && v != 28 && v != 1 && v != 0
1585 }
1586 _ => true,
1587 }
1588 }
1589}
1590
1591fn derive_legacy_chain_id(v: U256) -> Option<u64> {
1592 let v = u64::try_from(v).ok()?;
1593 if v < 35 { None } else { Some((v - 35) / 2) }
1598}
1599
1600impl TxType {
1601 pub fn from_u8(value: u8) -> Option<Self> {
1602 match value {
1603 0x00 => Some(Self::Legacy),
1604 0x01 => Some(Self::EIP2930),
1605 0x02 => Some(Self::EIP1559),
1606 0x03 => Some(Self::EIP4844),
1607 0x04 => Some(Self::EIP7702),
1608 0x06 => Some(Self::Frame),
1609 0x7d => Some(Self::FeeToken),
1610 0x7e => Some(Self::Privileged),
1611 _ => None,
1612 }
1613 }
1614
1615 pub fn is_l2_only(self) -> bool {
1623 match self {
1624 Self::Legacy
1625 | Self::EIP2930
1626 | Self::EIP1559
1627 | Self::EIP4844
1628 | Self::EIP7702
1629 | Self::Frame => false,
1630 Self::FeeToken | Self::Privileged => true,
1631 }
1632 }
1633}
1634
1635impl PrivilegedL2Transaction {
1636 pub fn get_privileged_hash(&self) -> Option<H256> {
1640 let to = match self.to {
1642 TxKind::Call(to) => to,
1643 _ => return None,
1644 };
1645
1646 let value = self.value.to_big_endian();
1647
1648 let u256_nonce = U256::from(self.nonce);
1651 let nonce = u256_nonce.to_big_endian();
1652
1653 Some(crate::utils::keccak(
1654 [
1655 U256::from(self.chain_id).to_big_endian().as_ref(),
1656 self.from.as_bytes(),
1657 to.as_bytes(),
1658 &nonce,
1659 &value,
1660 &U256::from(self.gas_limit).to_big_endian(),
1661 keccak(&self.data).as_bytes(),
1662 ]
1663 .concat(),
1664 ))
1665 }
1666}
1667
1668#[derive(Clone, Debug, PartialEq, Eq, Default, RSerialize, RDeserialize, Archive)]
1669pub struct FeeTokenTransaction {
1670 pub chain_id: u64,
1671 pub nonce: u64,
1672 pub max_priority_fee_per_gas: u64,
1673 pub max_fee_per_gas: u64,
1674 pub gas_limit: u64,
1675 pub to: TxKind,
1676 #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
1677 pub value: U256,
1678 #[rkyv(with=crate::rkyv_utils::BytesWrapper)]
1679 pub data: Bytes,
1680 #[rkyv(with=rkyv::with::Map<crate::rkyv_utils::AccessListItemWrapper>)]
1681 pub access_list: AccessList,
1682 #[rkyv(with=crate::rkyv_utils::H160Wrapper)]
1683 pub fee_token: Address,
1684 pub signature_y_parity: bool,
1685 #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
1686 pub signature_r: U256,
1687 #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
1688 pub signature_s: U256,
1689 #[rkyv(with=rkyv::with::Skip)]
1690 pub inner_hash: OnceCell<H256>,
1691 #[rkyv(with=rkyv::with::Skip)]
1692 pub sender_cache: OnceCell<Address>,
1693 #[rkyv(with=rkyv::with::Skip)]
1694 pub cached_canonical: OnceCell<Vec<u8>>,
1695}
1696
1697#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, RSerialize, RDeserialize, Archive)]
1699#[repr(u8)]
1700pub enum FrameMode {
1701 #[default]
1702 Default = 0,
1703 Verify = 1,
1704 Sender = 2,
1705}
1706
1707impl FrameMode {
1708 pub fn from_u8(val: u8) -> Option<Self> {
1711 match val {
1712 0 => Some(FrameMode::Default),
1713 1 => Some(FrameMode::Verify),
1714 2 => Some(FrameMode::Sender),
1715 _ => None,
1716 }
1717 }
1718}
1719
1720impl From<FrameMode> for u8 {
1721 fn from(mode: FrameMode) -> u8 {
1722 match mode {
1723 FrameMode::Default => 0,
1724 FrameMode::Verify => 1,
1725 FrameMode::Sender => 2,
1726 }
1727 }
1728}
1729
1730#[derive(Clone, Debug, PartialEq, Eq, Default, RSerialize, RDeserialize, Archive)]
1736pub struct Frame {
1737 pub mode: u8,
1738 pub flags: u8,
1739 #[rkyv(with=rkyv::with::Map<crate::rkyv_utils::H160Wrapper>)]
1740 pub target: Option<Address>,
1741 pub gas_limit: u64,
1742 #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
1746 pub value: U256,
1747 #[rkyv(with=crate::rkyv_utils::BytesWrapper)]
1748 pub data: Bytes,
1749}
1750
1751impl Frame {
1752 pub fn execution_mode(&self) -> FrameMode {
1754 FrameMode::from_u8(self.mode).unwrap_or_default()
1755 }
1756
1757 pub fn scope_restriction(&self) -> u8 {
1759 self.flags & 0x03
1760 }
1761
1762 pub fn is_atomic_batch(&self) -> bool {
1764 (self.flags >> 2) & 1 == 1
1765 }
1766
1767 pub fn is_expiry_verifier(&self) -> bool {
1770 self.execution_mode() == FrameMode::Verify
1771 && self.target == Some(frame_tx_expiry_verifier())
1772 }
1773}
1774
1775impl RLPEncode for Frame {
1776 fn encode(&self, buf: &mut dyn bytes::BufMut) {
1777 let target_kind = match self.target {
1779 Some(addr) => TxKind::Call(addr),
1780 None => TxKind::Create, };
1782 Encoder::new(buf)
1783 .encode_field(&(self.mode as u64))
1784 .encode_field(&(self.flags as u64))
1785 .encode_field(&target_kind)
1786 .encode_field(&self.gas_limit)
1787 .encode_field(&self.value)
1788 .encode_field(&self.data)
1789 .finish();
1790 }
1791}
1792
1793impl RLPDecode for Frame {
1794 fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
1795 let decoder = Decoder::new(rlp)?;
1796 let (mode_u64, decoder): (u64, _) = decoder.decode_field("mode")?;
1797 let mode = u8::try_from(mode_u64)
1798 .map_err(|_| RLPDecodeError::Custom(format!("Frame mode too large: {mode_u64}")))?;
1799 let (flags_u64, decoder): (u64, _) = decoder.decode_field("flags")?;
1800 let flags = u8::try_from(flags_u64)
1801 .map_err(|_| RLPDecodeError::Custom(format!("Frame flags too large: {flags_u64}")))?;
1802 let (target_kind, decoder): (TxKind, _) = decoder.decode_field("target")?;
1803 let target = match target_kind {
1804 TxKind::Call(addr) => Some(addr),
1805 TxKind::Create => None,
1806 };
1807 let (gas_limit, decoder) = decoder.decode_field("gas_limit")?;
1808 let (value, decoder): (U256, _) = decoder.decode_field("value")?;
1809 let (data, decoder) = decoder.decode_field("data")?;
1810 let frame = Frame {
1811 mode,
1812 flags,
1813 target,
1814 gas_limit,
1815 value,
1816 data,
1817 };
1818 Ok((frame, decoder.finish()?))
1819 }
1820}
1821
1822#[derive(Clone, Debug, PartialEq, Eq, Default, RSerialize, RDeserialize, Archive)]
1827pub struct FrameSignature {
1828 pub scheme: u8,
1829 #[rkyv(with=crate::rkyv_utils::H160Wrapper)]
1830 pub signer: Address,
1831 #[rkyv(with=crate::rkyv_utils::BytesWrapper)]
1832 pub msg: Bytes,
1833 #[rkyv(with=crate::rkyv_utils::BytesWrapper)]
1834 pub signature: Bytes,
1835}
1836
1837impl RLPEncode for FrameSignature {
1838 fn encode(&self, buf: &mut dyn bytes::BufMut) {
1839 Encoder::new(buf)
1840 .encode_field(&self.scheme)
1841 .encode_field(&self.signer)
1842 .encode_field(&self.msg)
1843 .encode_field(&self.signature)
1844 .finish();
1845 }
1846}
1847
1848impl RLPDecode for FrameSignature {
1849 fn decode_unfinished(rlp: &[u8]) -> Result<(FrameSignature, &[u8]), RLPDecodeError> {
1850 let decoder = Decoder::new(rlp)?;
1851 let (scheme, decoder) = decoder.decode_field("scheme")?;
1852 let (signer, decoder) = decoder.decode_field("signer")?;
1853 let (msg, decoder) = decoder.decode_field("msg")?;
1854 let (signature, decoder) = decoder.decode_field("signature")?;
1855 Ok((
1856 FrameSignature {
1857 scheme,
1858 signer,
1859 msg,
1860 signature,
1861 },
1862 decoder.finish()?,
1863 ))
1864 }
1865}
1866
1867#[derive(Clone, Debug, PartialEq, Eq, Default, RSerialize, RDeserialize, Archive)]
1871pub struct FrameTransaction {
1872 pub chain_id: u64,
1873 pub nonce: u64,
1874 #[rkyv(with=crate::rkyv_utils::H160Wrapper)]
1875 pub sender: Address,
1876 pub frames: Vec<Frame>,
1877 pub signatures: Vec<FrameSignature>,
1880 pub max_priority_fee_per_gas: u64,
1881 pub max_fee_per_gas: u64,
1882 #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
1883 pub max_fee_per_blob_gas: U256,
1884 #[rkyv(with=rkyv::with::Map<crate::rkyv_utils::H256Wrapper>)]
1885 pub blob_versioned_hashes: Vec<H256>,
1886 #[rkyv(with=rkyv::with::Skip)]
1887 pub inner_hash: OnceCell<H256>,
1888 #[rkyv(with=rkyv::with::Skip)]
1889 pub cached_canonical: OnceCell<Vec<u8>>,
1890}
1891
1892pub const FRAME_TX_INTRINSIC_COST: u64 = 15000;
1894pub const FRAME_TX_PER_FRAME_COST: u64 = 475;
1896pub const FRAME_TX_ENTRY_POINT_U64: u64 = 0xaa;
1898pub const FRAME_TX_MAX_FRAMES: usize = 64;
1900pub const FRAME_SIG_SCHEME_SECP256K1: u8 = 0;
1902pub const FRAME_SIG_SCHEME_P256: u8 = 1;
1903pub const FRAME_TX_MAX_VERIFY_GAS: u64 = 100_000;
1909pub const APPROVE_PAYMENT: u8 = 0x1;
1912pub const APPROVE_EXECUTION: u8 = 0x2;
1913pub const APPROVE_EXECUTION_AND_PAYMENT: u8 = 0x3;
1914pub const FRAME_TX_MAX_PENDING_NONCANONICAL_PAYMASTER: u8 = 1;
1919
1920pub fn frame_tx_entry_point() -> Address {
1923 Address::from_low_u64_be(FRAME_TX_ENTRY_POINT_U64)
1924}
1925
1926pub const FRAME_TX_EXPIRY_VERIFIER_U64: u64 = 0x8141;
1928pub const FRAME_TX_EXPIRY_DATA_LENGTH: usize = 8;
1930
1931pub fn frame_tx_expiry_verifier() -> Address {
1933 Address::from_low_u64_be(FRAME_TX_EXPIRY_VERIFIER_U64)
1934}
1935
1936impl FrameTransaction {
1937 pub fn compute_sig_hash(&self) -> H256 {
1943 let mut buf = vec![TxType::Frame as u8];
1944 let elided_signatures: Vec<FrameSignature> = self
1945 .signatures
1946 .iter()
1947 .map(|s| {
1948 if s.msg.is_empty() {
1949 FrameSignature {
1950 scheme: s.scheme,
1951 signer: s.signer,
1952 msg: s.msg.clone(),
1953 signature: Bytes::new(),
1954 }
1955 } else {
1956 s.clone()
1957 }
1958 })
1959 .collect();
1960 Encoder::new(&mut buf)
1962 .encode_field(&self.chain_id)
1963 .encode_field(&self.nonce)
1964 .encode_field(&self.sender)
1965 .encode_field(&self.frames)
1966 .encode_field(&elided_signatures)
1967 .encode_field(&self.max_priority_fee_per_gas)
1968 .encode_field(&self.max_fee_per_gas)
1969 .encode_field(&self.max_fee_per_blob_gas)
1970 .encode_field(&self.blob_versioned_hashes)
1971 .finish();
1972 keccak(&buf)
1973 }
1974
1975 pub fn signature_verification_cost(&self) -> u64 {
1979 self.signatures
1980 .iter()
1981 .map(|s| match s.scheme {
1982 FRAME_SIG_SCHEME_SECP256K1 => 2800u64,
1983 FRAME_SIG_SCHEME_P256 => 6700u64,
1984 _ => 0,
1985 })
1986 .sum()
1987 }
1988
1989 pub fn total_gas_limit(&self) -> u64 {
1992 let mut calldata_gas: u64 = 0;
1993 let mut frames_buf = Vec::new();
1995 self.frames.encode(&mut frames_buf);
1996 for byte in &frames_buf {
1997 calldata_gas = calldata_gas.saturating_add(if *byte == 0 { 4 } else { 16 });
1998 }
1999 let mut sigs_buf = Vec::new();
2001 self.signatures.encode(&mut sigs_buf);
2002 for byte in &sigs_buf {
2003 calldata_gas = calldata_gas.saturating_add(if *byte == 0 { 4 } else { 16 });
2004 }
2005 FRAME_TX_INTRINSIC_COST
2006 .saturating_add((self.frames.len() as u64).saturating_mul(FRAME_TX_PER_FRAME_COST))
2007 .saturating_add(calldata_gas)
2008 .saturating_add(self.signature_verification_cost())
2009 .saturating_add(
2010 self.frames
2011 .iter()
2012 .map(|f| f.gas_limit)
2013 .fold(0u64, |acc, g| acc.saturating_add(g)),
2014 )
2015 }
2016
2017 pub fn expiry_deadline(&self) -> Option<u64> {
2020 self.frames
2021 .iter()
2022 .find(|f| f.is_expiry_verifier())
2023 .and_then(|f| {
2024 let bytes: [u8; FRAME_TX_EXPIRY_DATA_LENGTH] = f.data.as_ref().try_into().ok()?;
2025 Some(u64::from_be_bytes(bytes))
2026 })
2027 }
2028
2029 pub fn validate_static_constraints(&self) -> Result<(), String> {
2032 if self.sender == Address::zero() {
2034 return Err("tx.sender must not be zero address".to_string());
2035 }
2036 if self.frames.is_empty() || self.frames.len() > FRAME_TX_MAX_FRAMES {
2037 return Err(format!(
2038 "Frame count must be between 1 and {FRAME_TX_MAX_FRAMES}"
2039 ));
2040 }
2041 for (i, sig) in self.signatures.iter().enumerate() {
2044 if sig.scheme != FRAME_SIG_SCHEME_SECP256K1 && sig.scheme != FRAME_SIG_SCHEME_P256 {
2045 return Err(format!("Signature {i}: unsupported scheme {}", sig.scheme));
2046 }
2047 match sig.msg.len() {
2048 0 => {}
2049 32 => {
2050 if sig.msg.iter().all(|&b| b == 0) {
2051 return Err(format!(
2052 "Signature {i}: explicit msg must not be zero digest"
2053 ));
2054 }
2055 }
2056 other => {
2057 return Err(format!(
2058 "Signature {i}: msg must be empty or 32 bytes, got {other}"
2059 ));
2060 }
2061 }
2062 }
2063 let mut total_frame_gas: u128 = 0;
2066 let mut expiry_frame_count: usize = 0;
2067 for (i, frame) in self.frames.iter().enumerate() {
2068 if frame.mode >= 3 {
2070 return Err(format!("Frame {i}: reserved execution mode {}", frame.mode));
2071 }
2072 if frame.flags >= 8 {
2074 return Err(format!(
2075 "Frame {i}: reserved flag bits must be zero (flags={:#04x})",
2076 frame.flags
2077 ));
2078 }
2079 if frame.is_expiry_verifier() {
2084 expiry_frame_count = expiry_frame_count.saturating_add(1);
2085 if expiry_frame_count > 1 {
2086 return Err(format!("Frame {i}: more than one expiry verifier frame"));
2087 }
2088 if frame.flags != 0 {
2089 return Err(format!(
2090 "Frame {i}: expiry verifier frame must have flags == 0"
2091 ));
2092 }
2093 if frame.data.len() != FRAME_TX_EXPIRY_DATA_LENGTH {
2094 return Err(format!(
2095 "Frame {i}: expiry verifier frame data must be {FRAME_TX_EXPIRY_DATA_LENGTH} bytes"
2096 ));
2097 }
2098 }
2099 if frame.mode != FrameMode::Sender as u8 && !frame.value.is_zero() {
2103 return Err(format!(
2104 "Frame {i}: non-zero value only allowed in SENDER mode (mode={}, value={})",
2105 frame.mode, frame.value
2106 ));
2107 }
2108 if frame.gas_limit > i64::MAX as u64 {
2116 return Err(format!(
2117 "Frame {i}: gas_limit {} exceeds 2**63-1",
2118 frame.gas_limit
2119 ));
2120 }
2121 total_frame_gas = total_frame_gas
2122 .checked_add(frame.gas_limit as u128)
2123 .ok_or_else(|| format!("Frame {i}: cumulative gas_limit overflow"))?;
2124 if total_frame_gas > i64::MAX as u128 {
2125 return Err(format!(
2126 "Frame {i}: cumulative frame gas_limit {} exceeds 2**63-1",
2127 total_frame_gas
2128 ));
2129 }
2130 if frame.is_atomic_batch() && i + 1 >= self.frames.len() {
2134 return Err(format!("Frame {i}: atomic batch flag on last frame"));
2135 }
2136 }
2137 Ok(())
2138 }
2139
2140 pub fn validation_prefix(&self) -> Result<ValidationPrefix, FrameValidationError> {
2156 let non_expiry: Vec<usize> = self
2158 .frames
2159 .iter()
2160 .enumerate()
2161 .filter(|(_, f)| !f.is_expiry_verifier())
2162 .map(|(i, _)| i)
2163 .collect();
2164
2165 let frame = |pos: usize| -> Option<&Frame> {
2167 non_expiry.get(pos).and_then(|&idx| self.frames.get(idx))
2168 };
2169
2170 let is_default = |pos: usize| -> bool {
2171 frame(pos).is_some_and(|f| f.execution_mode() == FrameMode::Default)
2172 };
2173 let is_verify = |pos: usize| -> bool {
2174 frame(pos).is_some_and(|f| f.execution_mode() == FrameMode::Verify)
2175 };
2176 let scope_of = |pos: usize| -> u8 { frame(pos).map_or(0, |f| f.scope_restriction()) };
2177
2178 if non_expiry.is_empty() {
2179 return Err(FrameValidationError::UnrecognizedPrefix);
2180 }
2181
2182 if is_default(0)
2186 && is_verify(1)
2187 && scope_of(1) == APPROVE_EXECUTION
2188 && is_verify(2)
2189 && scope_of(2) == APPROVE_PAYMENT
2190 {
2191 return Ok(ValidationPrefix {
2192 shape: PrefixShape::DeployOnlyVerifyPay,
2193 frame_indices: vec![non_expiry[0], non_expiry[1], non_expiry[2]],
2194 deploy_index: Some(non_expiry[0]),
2195 pay_index: Some(non_expiry[2]),
2196 });
2197 }
2198
2199 if is_default(0) && is_verify(1) && scope_of(1) == APPROVE_EXECUTION_AND_PAYMENT {
2201 return Ok(ValidationPrefix {
2202 shape: PrefixShape::DeploySelfVerify,
2203 frame_indices: vec![non_expiry[0], non_expiry[1]],
2204 deploy_index: Some(non_expiry[0]),
2205 pay_index: Some(non_expiry[1]),
2206 });
2207 }
2208
2209 if is_verify(0)
2211 && scope_of(0) == APPROVE_EXECUTION
2212 && is_verify(1)
2213 && scope_of(1) == APPROVE_PAYMENT
2214 {
2215 return Ok(ValidationPrefix {
2216 shape: PrefixShape::OnlyVerifyPay,
2217 frame_indices: vec![non_expiry[0], non_expiry[1]],
2218 deploy_index: None,
2219 pay_index: Some(non_expiry[1]),
2220 });
2221 }
2222
2223 if is_verify(0) && scope_of(0) == APPROVE_EXECUTION_AND_PAYMENT {
2225 return Ok(ValidationPrefix {
2226 shape: PrefixShape::SelfVerify,
2227 frame_indices: vec![non_expiry[0]],
2228 deploy_index: None,
2229 pay_index: Some(non_expiry[0]),
2230 });
2231 }
2232
2233 Err(FrameValidationError::UnrecognizedPrefix)
2234 }
2235
2236 pub fn validate_prefix_structure(
2250 &self,
2251 prefix: &ValidationPrefix,
2252 ) -> Result<(), FrameValidationError> {
2253 let mut deploy_count = 0usize;
2254
2255 for &idx in &prefix.frame_indices {
2256 let frame = &self.frames[idx];
2257
2258 if frame.is_atomic_batch() {
2259 return Err(FrameValidationError::AtomicBatchInPrefix { frame_index: idx });
2260 }
2261
2262 match prefix.deploy_index {
2263 Some(deploy_idx) if deploy_idx == idx => {
2264 deploy_count += 1;
2266 if deploy_count > 1 {
2267 return Err(FrameValidationError::MultipleDeploys { frame_index: idx });
2268 }
2269 if prefix.frame_indices.first() != Some(&idx) {
2274 return Err(FrameValidationError::DeployNotFirst { frame_index: idx });
2275 }
2276 if frame.execution_mode() != FrameMode::Default {
2277 return Err(FrameValidationError::DeployNotDefaultMode {
2278 frame_index: idx,
2279 });
2280 }
2281 }
2282 _ => {
2283 if frame.execution_mode() != FrameMode::Verify {
2285 return Err(FrameValidationError::VerifyFrameNotVerifyMode {
2286 frame_index: idx,
2287 });
2288 }
2289
2290 let target_ok = match frame.target {
2292 None => true,
2293 Some(addr) => addr == self.sender,
2294 };
2295 if !target_ok {
2296 return Err(FrameValidationError::VerifyTargetNotSender {
2297 frame_index: idx,
2298 });
2299 }
2300
2301 let expected_scope = match prefix.shape {
2303 PrefixShape::SelfVerify | PrefixShape::DeploySelfVerify => {
2304 APPROVE_EXECUTION_AND_PAYMENT
2305 }
2306 PrefixShape::OnlyVerifyPay | PrefixShape::DeployOnlyVerifyPay => {
2307 if prefix.pay_index == Some(idx) {
2309 APPROVE_PAYMENT
2310 } else {
2311 APPROVE_EXECUTION
2312 }
2313 }
2314 };
2315 if frame.scope_restriction() != expected_scope {
2316 return Err(FrameValidationError::WrongScopeRestriction {
2317 frame_index: idx,
2318 expected: expected_scope,
2319 actual: frame.scope_restriction(),
2320 });
2321 }
2322 }
2323 }
2324 }
2325
2326 let prefix_gas: u64 = prefix
2328 .frame_indices
2329 .iter()
2330 .map(|&i| self.frames[i].gas_limit)
2331 .fold(0u64, |acc, g| acc.saturating_add(g));
2332 let total_verify_gas = prefix_gas.saturating_add(self.signature_verification_cost());
2333 if total_verify_gas > FRAME_TX_MAX_VERIFY_GAS {
2334 return Err(FrameValidationError::VerifyGasBudgetExceeded {
2335 actual: total_verify_gas,
2336 limit: FRAME_TX_MAX_VERIFY_GAS,
2337 });
2338 }
2339
2340 Ok(())
2341 }
2342}
2343
2344#[derive(Clone, Debug, PartialEq, Eq)]
2346pub enum PrefixShape {
2347 SelfVerify,
2349 DeploySelfVerify,
2351 OnlyVerifyPay,
2353 DeployOnlyVerifyPay,
2355}
2356
2357#[derive(Clone, Debug, PartialEq, Eq)]
2359pub struct ValidationPrefix {
2360 pub shape: PrefixShape,
2362 pub frame_indices: Vec<usize>,
2365 pub deploy_index: Option<usize>,
2367 pub pay_index: Option<usize>,
2369}
2370
2371#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
2374pub enum FrameValidationError {
2375 #[error("frame transaction validation prefix does not match any recognized shape")]
2376 UnrecognizedPrefix,
2377 #[error("frame {frame_index}: prefix has more than one DEFAULT deploy frame")]
2378 MultipleDeploys { frame_index: usize },
2379 #[error("frame {frame_index}: deploy frame must be at index 0")]
2380 DeployNotFirst { frame_index: usize },
2381 #[error("frame {frame_index}: deploy frame must use DEFAULT execution mode")]
2382 DeployNotDefaultMode { frame_index: usize },
2383 #[error("frame {frame_index}: prefix VERIFY frame does not use VERIFY execution mode")]
2384 VerifyFrameNotVerifyMode { frame_index: usize },
2385 #[error("frame {frame_index}: VERIFY frame target is not tx.sender")]
2386 VerifyTargetNotSender { frame_index: usize },
2387 #[error("frame {frame_index}: scope restriction is {actual:#04x}, expected {expected:#04x}")]
2388 WrongScopeRestriction {
2389 frame_index: usize,
2390 expected: u8,
2391 actual: u8,
2392 },
2393 #[error("frame {frame_index}: prefix frame has atomic-batch flag set")]
2394 AtomicBatchInPrefix { frame_index: usize },
2395 #[error("prefix gas budget exceeded: {actual} > {limit} (MAX_VERIFY_GAS)")]
2396 VerifyGasBudgetExceeded { actual: u64, limit: u64 },
2397}
2398
2399impl RLPEncode for FrameTransaction {
2400 fn encode(&self, buf: &mut dyn bytes::BufMut) {
2401 Encoder::new(buf)
2402 .encode_field(&self.chain_id)
2403 .encode_field(&self.nonce)
2404 .encode_field(&self.sender)
2405 .encode_field(&self.frames)
2406 .encode_field(&self.signatures)
2407 .encode_field(&self.max_priority_fee_per_gas)
2408 .encode_field(&self.max_fee_per_gas)
2409 .encode_field(&self.max_fee_per_blob_gas)
2410 .encode_field(&self.blob_versioned_hashes)
2411 .finish();
2412 }
2413}
2414
2415impl RLPDecode for FrameTransaction {
2416 fn decode_unfinished(rlp: &[u8]) -> Result<(FrameTransaction, &[u8]), RLPDecodeError> {
2417 let decoder = Decoder::new(rlp)?;
2418 let (chain_id, decoder) = decoder.decode_field("chain_id")?;
2419 let (nonce, decoder) = decoder.decode_field("nonce")?;
2420 let (sender, decoder) = decoder.decode_field("sender")?;
2421 let (frames, decoder) = decoder.decode_field("frames")?;
2422 let (signatures, decoder) = decoder.decode_field("signatures")?;
2423 let (max_priority_fee_per_gas, decoder) =
2424 decoder.decode_field("max_priority_fee_per_gas")?;
2425 let (max_fee_per_gas, decoder) = decoder.decode_field("max_fee_per_gas")?;
2426 let (max_fee_per_blob_gas, decoder) = decoder.decode_field("max_fee_per_blob_gas")?;
2427 let (blob_versioned_hashes, decoder) = decoder.decode_field("blob_versioned_hashes")?;
2428 let tx = FrameTransaction {
2429 chain_id,
2430 nonce,
2431 sender,
2432 frames,
2433 signatures,
2434 max_priority_fee_per_gas,
2435 max_fee_per_gas,
2436 max_fee_per_blob_gas,
2437 blob_versioned_hashes,
2438 inner_hash: OnceCell::new(),
2439 cached_canonical: OnceCell::new(),
2440 };
2441 Ok((tx, decoder.finish()?))
2442 }
2443}
2444
2445impl PayloadRLPEncode for FrameTransaction {
2446 fn encode_payload(&self, buf: &mut dyn bytes::BufMut) {
2447 buf.put_u8(TxType::Frame as u8);
2448 self.encode(buf);
2449 }
2450}
2451
2452mod canonic_encoding {
2458 use super::*;
2459
2460 impl Transaction {
2461 pub fn decode_canonical(bytes: &[u8]) -> Result<Self, RLPDecodeError> {
2467 match bytes.first() {
2469 Some(tx_type) if *tx_type < 0x7f => {
2471 let tx_bytes = &bytes[1..];
2473 match *tx_type {
2474 0x0 => {
2476 LegacyTransaction::decode(tx_bytes).map(Transaction::LegacyTransaction)
2477 } 0x1 => EIP2930Transaction::decode(tx_bytes)
2480 .map(Transaction::EIP2930Transaction),
2481 0x2 => EIP1559Transaction::decode(tx_bytes)
2483 .map(Transaction::EIP1559Transaction),
2484 0x3 => EIP4844Transaction::decode(tx_bytes)
2486 .map(Transaction::EIP4844Transaction),
2487 0x4 => EIP7702Transaction::decode(tx_bytes)
2489 .map(Transaction::EIP7702Transaction),
2490 0x6 => {
2492 FrameTransaction::decode(tx_bytes).map(Transaction::FrameTransaction)
2493 }
2494 0x7d => FeeTokenTransaction::decode(tx_bytes)
2496 .map(Transaction::FeeTokenTransaction),
2497 0x7e => PrivilegedL2Transaction::decode(tx_bytes)
2499 .map(Transaction::PrivilegedL2Transaction),
2500 ty => Err(RLPDecodeError::Custom(format!(
2501 "Invalid transaction type: {ty}"
2502 ))),
2503 }
2504 }
2505 _ => LegacyTransaction::decode(bytes).map(Transaction::LegacyTransaction),
2507 }
2508 }
2509
2510 pub fn encode_canonical(&self, buf: &mut dyn bytes::BufMut) {
2516 match self {
2517 Transaction::LegacyTransaction(_) => {}
2519 _ => buf.put_u8(self.tx_type() as u8),
2520 }
2521 match self {
2522 Transaction::LegacyTransaction(t) => t.encode(buf),
2523 Transaction::EIP2930Transaction(t) => t.encode(buf),
2524 Transaction::EIP1559Transaction(t) => t.encode(buf),
2525 Transaction::EIP4844Transaction(t) => t.encode(buf),
2526 Transaction::EIP7702Transaction(t) => t.encode(buf),
2527 Transaction::FeeTokenTransaction(t) => t.encode(buf),
2528 Transaction::PrivilegedL2Transaction(t) => t.encode(buf),
2529 Transaction::FrameTransaction(t) => t.encode(buf),
2530 };
2531 }
2532
2533 pub fn encode_canonical_to_vec(&self) -> Vec<u8> {
2539 if let Some(cell) = self.cached_canonical_cell() {
2540 return cell
2541 .get_or_init(|| {
2542 let mut buf = Vec::new();
2543 self.encode_canonical(&mut buf);
2544 buf
2545 })
2546 .clone();
2547 }
2548 let mut buf = Vec::new();
2549 self.encode_canonical(&mut buf);
2550 buf
2551 }
2552
2553 pub fn encode_canonical_len(&self) -> usize {
2558 let prefix_len = match self {
2559 Transaction::LegacyTransaction(_) => 0,
2560 _ => 1,
2561 };
2562 let inner_len = match self {
2563 Transaction::LegacyTransaction(t) => t.length(),
2564 Transaction::EIP2930Transaction(t) => t.length(),
2565 Transaction::EIP1559Transaction(t) => t.length(),
2566 Transaction::EIP4844Transaction(t) => t.length(),
2567 Transaction::EIP7702Transaction(t) => t.length(),
2568 Transaction::FeeTokenTransaction(t) => t.length(),
2569 Transaction::PrivilegedL2Transaction(t) => t.length(),
2570 Transaction::FrameTransaction(t) => t.length(),
2571 };
2572 prefix_len + inner_len
2573 }
2574 }
2575
2576 impl P2PTransaction {
2577 pub fn tx_type(&self) -> TxType {
2578 match self {
2579 P2PTransaction::LegacyTransaction(_) => TxType::Legacy,
2580 P2PTransaction::EIP2930Transaction(_) => TxType::EIP2930,
2581 P2PTransaction::EIP1559Transaction(_) => TxType::EIP1559,
2582 P2PTransaction::EIP4844TransactionWithBlobs(_) => TxType::EIP4844,
2583 P2PTransaction::EIP7702Transaction(_) => TxType::EIP7702,
2584 P2PTransaction::FeeTokenTransaction(_) => TxType::FeeToken,
2585 P2PTransaction::FrameTransaction(_) => TxType::Frame,
2586 }
2587 }
2588
2589 pub fn encode_canonical(&self, buf: &mut dyn bytes::BufMut) {
2590 match self {
2591 P2PTransaction::LegacyTransaction(_) => {}
2593 _ => buf.put_u8(self.tx_type() as u8),
2594 }
2595 match self {
2596 P2PTransaction::LegacyTransaction(t) => t.encode(buf),
2597 P2PTransaction::EIP2930Transaction(t) => t.encode(buf),
2598 P2PTransaction::EIP1559Transaction(t) => t.encode(buf),
2599 P2PTransaction::EIP4844TransactionWithBlobs(t) => t.encode(buf),
2600 P2PTransaction::EIP7702Transaction(t) => t.encode(buf),
2601 P2PTransaction::FeeTokenTransaction(t) => t.encode(buf),
2602 P2PTransaction::FrameTransaction(t) => t.encode(buf),
2603 };
2604 }
2605
2606 pub fn encode_canonical_to_vec(&self) -> Vec<u8> {
2607 let mut buf = Vec::new();
2608 self.encode_canonical(&mut buf);
2609 buf
2610 }
2611
2612 pub fn compute_hash(&self) -> H256 {
2613 match self {
2614 P2PTransaction::LegacyTransaction(t) => {
2615 Transaction::LegacyTransaction(t.clone()).compute_hash(&NativeCrypto)
2616 }
2617 P2PTransaction::EIP2930Transaction(t) => {
2618 Transaction::EIP2930Transaction(t.clone()).compute_hash(&NativeCrypto)
2619 }
2620 P2PTransaction::EIP1559Transaction(t) => {
2621 Transaction::EIP1559Transaction(t.clone()).compute_hash(&NativeCrypto)
2622 }
2623 P2PTransaction::EIP4844TransactionWithBlobs(t) => {
2624 Transaction::EIP4844Transaction(t.tx.clone()).compute_hash(&NativeCrypto)
2625 }
2626 P2PTransaction::EIP7702Transaction(t) => {
2627 Transaction::EIP7702Transaction(t.clone()).compute_hash(&NativeCrypto)
2628 }
2629 P2PTransaction::FeeTokenTransaction(t) => {
2630 Transaction::FeeTokenTransaction(t.clone()).compute_hash(&NativeCrypto)
2631 }
2632 P2PTransaction::FrameTransaction(t) => {
2633 Transaction::FrameTransaction(t.clone()).compute_hash(&NativeCrypto)
2634 }
2635 }
2636 }
2637 }
2638}
2639
2640mod serde_impl {
2644 use ethereum_types::H160;
2645 use serde::Deserialize;
2646 use serde::{Deserializer, de::Error};
2647 use serde_json::Value;
2648 use std::{collections::HashMap, str::FromStr};
2649
2650 #[cfg(feature = "c-kzg")]
2651 use crate::types::BYTES_PER_BLOB;
2652 use crate::types::{AccessListItem, AuthorizationTuple, BlobsBundleError};
2653
2654 use super::*;
2655
2656 impl Serialize for TxKind {
2657 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
2658 where
2659 S: serde::Serializer,
2660 {
2661 match self {
2662 TxKind::Call(address) => serializer.serialize_str(&format!("{address:#x}")),
2663 TxKind::Create => serializer.serialize_none(),
2664 }
2665 }
2666 }
2667
2668 impl<'de> Deserialize<'de> for TxKind {
2669 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2670 where
2671 D: serde::Deserializer<'de>,
2672 {
2673 let str_option = Option::<String>::deserialize(deserializer)?;
2674 match str_option {
2675 Some(str) if !str.is_empty() => Ok(TxKind::Call(
2676 Address::from_str(str.trim_start_matches("0x")).map_err(|_| {
2677 serde::de::Error::custom(format!("Failed to deserialize hex value {str}"))
2678 })?,
2679 )),
2680 _ => Ok(TxKind::Create),
2681 }
2682 }
2683 }
2684
2685 impl Serialize for TxType {
2686 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
2687 where
2688 S: serde::Serializer,
2689 {
2690 serializer.serialize_str(&format!("{:#x}", *self as u8))
2691 }
2692 }
2693
2694 impl<'de> Deserialize<'de> for TxType {
2695 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2696 where
2697 D: serde::Deserializer<'de>,
2698 {
2699 let str = String::deserialize(deserializer)?;
2700 let tx_num = u8::from_str_radix(str.trim_start_matches("0x"), 16).map_err(|_| {
2701 serde::de::Error::custom(format!("Failed to deserialize hex value {str}"))
2702 })?;
2703 TxType::from_u8(tx_num).ok_or_else(|| {
2704 serde::de::Error::custom(format!("Invalid transaction type {tx_num}"))
2705 })
2706 }
2707 }
2708
2709 #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
2710 #[serde(rename_all = "camelCase")]
2711 pub struct AccessListEntry {
2712 pub address: Address,
2713 pub storage_keys: Vec<H256>,
2714 }
2715
2716 impl From<&AccessListItem> for AccessListEntry {
2717 fn from(value: &AccessListItem) -> AccessListEntry {
2718 AccessListEntry {
2719 address: value.0,
2720 storage_keys: value.1.clone(),
2721 }
2722 }
2723 }
2724
2725 #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
2726 #[serde(rename_all = "camelCase")]
2727 pub struct AuthorizationTupleEntry {
2728 pub chain_id: U256,
2729 pub address: Address,
2730 #[serde(default, with = "crate::serde_utils::u64::hex_str")]
2731 pub nonce: u64,
2732 pub y_parity: U256,
2733 pub r: U256,
2734 pub s: U256,
2735 }
2736
2737 impl From<&AuthorizationTuple> for AuthorizationTupleEntry {
2738 fn from(value: &AuthorizationTuple) -> AuthorizationTupleEntry {
2739 AuthorizationTupleEntry {
2740 chain_id: value.chain_id,
2741 address: value.address,
2742 nonce: value.nonce,
2743 y_parity: value.y_parity,
2744 r: value.r_signature,
2745 s: value.s_signature,
2746 }
2747 }
2748 }
2749
2750 impl From<AuthorizationTupleEntry> for AuthorizationTuple {
2751 fn from(entry: AuthorizationTupleEntry) -> AuthorizationTuple {
2752 AuthorizationTuple {
2753 chain_id: entry.chain_id,
2754 address: entry.address,
2755 nonce: entry.nonce,
2756 y_parity: entry.y_parity,
2757 r_signature: entry.r,
2758 s_signature: entry.s,
2759 }
2760 }
2761 }
2762
2763 #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
2764 #[serde(rename_all = "camelCase")]
2765 pub struct FrameEntry {
2766 #[serde(with = "crate::serde_utils::u64::hex_str")]
2767 pub mode: u64,
2768 #[serde(with = "crate::serde_utils::u64::hex_str")]
2769 pub flags: u64,
2770 pub to: Option<Address>,
2771 #[serde(with = "crate::serde_utils::u64::hex_str")]
2772 pub gas_limit: u64,
2773 #[serde(
2774 default,
2775 serialize_with = "serialize_u256_hex",
2776 deserialize_with = "crate::serde_utils::u256::deser_hex_str"
2777 )]
2778 pub value: U256,
2779 #[serde(with = "crate::serde_utils::bytes")]
2780 pub data: Bytes,
2781 }
2782
2783 #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
2785 #[serde(rename_all = "camelCase")]
2786 pub struct SignatureEntry {
2787 #[serde(with = "crate::serde_utils::u64::hex_str")]
2788 pub scheme: u64,
2789 pub signer: Address,
2790 #[serde(with = "crate::serde_utils::bytes")]
2791 pub msg: Bytes,
2792 #[serde(with = "crate::serde_utils::bytes")]
2793 pub signature: Bytes,
2794 }
2795
2796 impl From<&FrameSignature> for SignatureEntry {
2797 fn from(value: &FrameSignature) -> SignatureEntry {
2798 SignatureEntry {
2799 scheme: value.scheme as u64,
2800 signer: value.signer,
2801 msg: value.msg.clone(),
2802 signature: value.signature.clone(),
2803 }
2804 }
2805 }
2806
2807 fn serialize_u256_hex<S: serde::Serializer>(value: &U256, s: S) -> Result<S::Ok, S::Error> {
2808 s.serialize_str(&format!("{value:#x}"))
2809 }
2810
2811 impl From<&Frame> for FrameEntry {
2812 fn from(value: &Frame) -> FrameEntry {
2813 FrameEntry {
2814 mode: value.mode as u64,
2815 flags: value.flags as u64,
2816 to: value.target,
2817 gas_limit: value.gas_limit,
2818 value: value.value,
2819 data: value.data.clone(),
2820 }
2821 }
2822 }
2823
2824 impl From<FrameEntry> for Frame {
2825 fn from(entry: FrameEntry) -> Frame {
2826 Frame {
2827 mode: entry.mode as u8,
2828 flags: entry.flags as u8,
2829 target: entry.to,
2830 gas_limit: entry.gas_limit,
2831 value: entry.value,
2832 data: entry.data,
2833 }
2834 }
2835 }
2836
2837 impl Serialize for LegacyTransaction {
2838 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
2839 where
2840 S: serde::Serializer,
2841 {
2842 let mut struct_serializer = serializer.serialize_struct("LegacyTransaction", 11)?;
2843 struct_serializer.serialize_field("type", &TxType::Legacy)?;
2844 struct_serializer.serialize_field("nonce", &format!("{:#x}", self.nonce))?;
2845 struct_serializer.serialize_field("to", &self.to)?;
2846 struct_serializer.serialize_field("gas", &format!("{:#x}", self.gas))?;
2847 struct_serializer.serialize_field("value", &self.value)?;
2848 struct_serializer.serialize_field("input", &format!("0x{:x}", self.data))?;
2849 struct_serializer.serialize_field("gasPrice", &format!("{:#x}", self.gas_price))?;
2850 struct_serializer.serialize_field(
2851 "chainId",
2852 &format!("{:#x}", derive_legacy_chain_id(self.v).unwrap_or_default()),
2853 )?;
2854 struct_serializer.serialize_field("v", &self.v)?;
2855 struct_serializer.serialize_field("r", &self.r)?;
2856 struct_serializer.serialize_field("s", &self.s)?;
2857 struct_serializer.end()
2858 }
2859 }
2860
2861 impl Serialize for EIP2930Transaction {
2862 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
2863 where
2864 S: serde::Serializer,
2865 {
2866 let mut struct_serializer = serializer.serialize_struct("Eip2930Transaction", 12)?;
2867 struct_serializer.serialize_field("type", &TxType::EIP2930)?;
2868 struct_serializer.serialize_field("nonce", &format!("{:#x}", self.nonce))?;
2869 struct_serializer.serialize_field("to", &self.to)?;
2870 struct_serializer.serialize_field("gas", &format!("{:#x}", self.gas_limit))?;
2871 struct_serializer.serialize_field("value", &self.value)?;
2872 struct_serializer.serialize_field("input", &format!("0x{:x}", self.data))?;
2873 struct_serializer.serialize_field("gasPrice", &format!("{:#x}", self.gas_price))?;
2874 struct_serializer.serialize_field(
2875 "accessList",
2876 &self
2877 .access_list
2878 .iter()
2879 .map(AccessListEntry::from)
2880 .collect::<Vec<_>>(),
2881 )?;
2882 struct_serializer.serialize_field("chainId", &format!("{:#x}", self.chain_id))?;
2883 struct_serializer
2884 .serialize_field("yParity", &format!("{:#x}", self.signature_y_parity as u8))?;
2885 struct_serializer
2886 .serialize_field("v", &format!("{:#x}", self.signature_y_parity as u8))?; struct_serializer.serialize_field("r", &self.signature_r)?;
2888 struct_serializer.serialize_field("s", &self.signature_s)?;
2889 struct_serializer.end()
2890 }
2891 }
2892
2893 impl Serialize for EIP1559Transaction {
2894 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
2895 where
2896 S: serde::Serializer,
2897 {
2898 let mut struct_serializer = serializer.serialize_struct("Eip1559Transaction", 14)?;
2899 struct_serializer.serialize_field("type", &TxType::EIP1559)?;
2900 struct_serializer.serialize_field("nonce", &format!("{:#x}", self.nonce))?;
2901 struct_serializer.serialize_field("to", &self.to)?;
2902 struct_serializer.serialize_field("gas", &format!("{:#x}", self.gas_limit))?;
2903 struct_serializer.serialize_field("value", &self.value)?;
2904 struct_serializer.serialize_field("input", &format!("0x{:x}", self.data))?;
2905 struct_serializer.serialize_field(
2906 "maxPriorityFeePerGas",
2907 &format!("{:#x}", self.max_priority_fee_per_gas),
2908 )?;
2909 struct_serializer
2910 .serialize_field("maxFeePerGas", &format!("{:#x}", self.max_fee_per_gas))?;
2911 struct_serializer
2912 .serialize_field("gasPrice", &format!("{:#x}", self.max_fee_per_gas))?;
2913 struct_serializer.serialize_field(
2914 "accessList",
2915 &self
2916 .access_list
2917 .iter()
2918 .map(AccessListEntry::from)
2919 .collect::<Vec<_>>(),
2920 )?;
2921 struct_serializer.serialize_field("chainId", &format!("{:#x}", self.chain_id))?;
2922 struct_serializer
2923 .serialize_field("yParity", &format!("{:#x}", self.signature_y_parity as u8))?;
2924 struct_serializer
2925 .serialize_field("v", &format!("{:#x}", self.signature_y_parity as u8))?; struct_serializer.serialize_field("r", &self.signature_r)?;
2927 struct_serializer.serialize_field("s", &self.signature_s)?;
2928 struct_serializer.end()
2929 }
2930 }
2931
2932 impl Serialize for EIP4844Transaction {
2933 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
2934 where
2935 S: serde::Serializer,
2936 {
2937 let mut struct_serializer = serializer.serialize_struct("Eip4844Transaction", 15)?;
2938 struct_serializer.serialize_field("type", &TxType::EIP4844)?;
2939 struct_serializer.serialize_field("nonce", &format!("{:#x}", self.nonce))?;
2940 struct_serializer.serialize_field("to", &self.to)?;
2941 struct_serializer.serialize_field("gas", &format!("{:#x}", self.gas))?;
2942 struct_serializer.serialize_field("value", &self.value)?;
2943 struct_serializer.serialize_field("input", &format!("0x{:x}", self.data))?;
2944 struct_serializer.serialize_field(
2945 "maxPriorityFeePerGas",
2946 &format!("{:#x}", self.max_priority_fee_per_gas),
2947 )?;
2948 struct_serializer
2949 .serialize_field("maxFeePerGas", &format!("{:#x}", self.max_fee_per_gas))?;
2950 struct_serializer
2951 .serialize_field("gasPrice", &format!("{:#x}", self.max_fee_per_gas))?;
2952 struct_serializer.serialize_field(
2953 "maxFeePerBlobGas",
2954 &format!("{:#x}", self.max_fee_per_blob_gas),
2955 )?;
2956 struct_serializer.serialize_field(
2957 "accessList",
2958 &self
2959 .access_list
2960 .iter()
2961 .map(AccessListEntry::from)
2962 .collect::<Vec<_>>(),
2963 )?;
2964 struct_serializer
2965 .serialize_field("blobVersionedHashes", &self.blob_versioned_hashes)?;
2966 struct_serializer.serialize_field("chainId", &format!("{:#x}", self.chain_id))?;
2967 struct_serializer
2968 .serialize_field("yParity", &format!("{:#x}", self.signature_y_parity as u8))?;
2969 struct_serializer
2970 .serialize_field("v", &format!("{:#x}", self.signature_y_parity as u8))?; struct_serializer.serialize_field("r", &self.signature_r)?;
2972 struct_serializer.serialize_field("s", &self.signature_s)?;
2973 struct_serializer.end()
2974 }
2975 }
2976
2977 impl Serialize for EIP7702Transaction {
2978 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
2979 where
2980 S: serde::Serializer,
2981 {
2982 let mut struct_serializer = serializer.serialize_struct("Eip7702Transaction", 15)?;
2983 struct_serializer.serialize_field("type", &TxType::EIP7702)?;
2984 struct_serializer.serialize_field("nonce", &format!("{:#x}", self.nonce))?;
2985 struct_serializer.serialize_field("to", &self.to)?;
2986 struct_serializer.serialize_field("gas", &format!("{:#x}", self.gas_limit))?;
2987 struct_serializer.serialize_field("value", &self.value)?;
2988 struct_serializer.serialize_field("input", &format!("0x{:x}", self.data))?;
2989 struct_serializer.serialize_field(
2990 "maxPriorityFeePerGas",
2991 &format!("{:#x}", self.max_priority_fee_per_gas),
2992 )?;
2993 struct_serializer
2994 .serialize_field("maxFeePerGas", &format!("{:#x}", self.max_fee_per_gas))?;
2995 struct_serializer
2996 .serialize_field("gasPrice", &format!("{:#x}", self.max_fee_per_gas))?;
2997 struct_serializer.serialize_field(
2998 "accessList",
2999 &self
3000 .access_list
3001 .iter()
3002 .map(AccessListEntry::from)
3003 .collect::<Vec<_>>(),
3004 )?;
3005 struct_serializer.serialize_field(
3006 "authorizationList",
3007 &self
3008 .authorization_list
3009 .iter()
3010 .map(AuthorizationTupleEntry::from)
3011 .collect::<Vec<_>>(),
3012 )?;
3013 struct_serializer.serialize_field("chainId", &format!("{:#x}", self.chain_id))?;
3014 struct_serializer
3015 .serialize_field("yParity", &format!("{:#x}", self.signature_y_parity as u8))?;
3016 struct_serializer
3017 .serialize_field("v", &format!("{:#x}", self.signature_y_parity as u8))?; struct_serializer.serialize_field("r", &self.signature_r)?;
3019 struct_serializer.serialize_field("s", &self.signature_s)?;
3020 struct_serializer.end()
3021 }
3022 }
3023
3024 impl Serialize for PrivilegedL2Transaction {
3025 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
3026 where
3027 S: serde::Serializer,
3028 {
3029 let mut struct_serializer = serializer.serialize_struct("Eip1559Transaction", 14)?;
3030 struct_serializer.serialize_field("type", &TxType::Privileged)?;
3031 struct_serializer.serialize_field("nonce", &format!("{:#x}", self.nonce))?;
3032 struct_serializer.serialize_field("to", &self.to)?;
3033 struct_serializer.serialize_field("gas", &format!("{:#x}", self.gas_limit))?;
3034 struct_serializer.serialize_field("value", &self.value)?;
3035 struct_serializer.serialize_field("input", &format!("0x{:x}", self.data))?;
3036 struct_serializer.serialize_field(
3037 "maxPriorityFeePerGas",
3038 &format!("{:#x}", self.max_priority_fee_per_gas),
3039 )?;
3040 struct_serializer
3041 .serialize_field("maxFeePerGas", &format!("{:#x}", self.max_fee_per_gas))?;
3042 struct_serializer
3043 .serialize_field("gasPrice", &format!("{:#x}", self.max_fee_per_gas))?;
3044 struct_serializer.serialize_field(
3045 "accessList",
3046 &self
3047 .access_list
3048 .iter()
3049 .map(AccessListEntry::from)
3050 .collect::<Vec<_>>(),
3051 )?;
3052 struct_serializer.serialize_field("chainId", &format!("{:#x}", self.chain_id))?;
3053 struct_serializer.serialize_field("sender", &self.from)?;
3054 struct_serializer.end()
3055 }
3056 }
3057
3058 impl Serialize for FeeTokenTransaction {
3059 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
3060 where
3061 S: serde::Serializer,
3062 {
3063 let mut struct_serializer = serializer.serialize_struct("FeeTokenTransaction", 15)?;
3064 struct_serializer.serialize_field("type", &TxType::FeeToken)?;
3065 struct_serializer.serialize_field("nonce", &format!("{:#x}", self.nonce))?;
3066 struct_serializer.serialize_field("to", &self.to)?;
3067 struct_serializer.serialize_field("gas", &format!("{:#x}", self.gas_limit))?;
3068 struct_serializer.serialize_field("value", &self.value)?;
3069 struct_serializer.serialize_field("input", &format!("0x{:x}", self.data))?;
3070 struct_serializer.serialize_field(
3071 "maxPriorityFeePerGas",
3072 &format!("{:#x}", self.max_priority_fee_per_gas),
3073 )?;
3074 struct_serializer
3075 .serialize_field("maxFeePerGas", &format!("{:#x}", self.max_fee_per_gas))?;
3076 struct_serializer
3077 .serialize_field("gasPrice", &format!("{:#x}", self.max_fee_per_gas))?;
3078 struct_serializer.serialize_field(
3079 "accessList",
3080 &self
3081 .access_list
3082 .iter()
3083 .map(AccessListEntry::from)
3084 .collect::<Vec<_>>(),
3085 )?;
3086 struct_serializer.serialize_field("feeToken", &format!("{:#x}", self.fee_token))?;
3087 struct_serializer.serialize_field("chainId", &format!("{:#x}", self.chain_id))?;
3088 struct_serializer
3089 .serialize_field("yParity", &format!("{:#x}", self.signature_y_parity as u8))?;
3090 struct_serializer
3091 .serialize_field("v", &format!("{:#x}", self.signature_y_parity as u8))?;
3092 struct_serializer.serialize_field("r", &self.signature_r)?;
3093 struct_serializer.serialize_field("s", &self.signature_s)?;
3094 struct_serializer.end()
3095 }
3096 }
3097
3098 impl Serialize for FrameTransaction {
3099 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
3100 where
3101 S: serde::Serializer,
3102 {
3103 let mut s = serializer.serialize_struct("FrameTransaction", 10)?;
3104 s.serialize_field("type", &TxType::Frame)?;
3105 s.serialize_field("chainId", &format!("{:#x}", self.chain_id))?;
3106 s.serialize_field("nonce", &format!("{:#x}", self.nonce))?;
3107 s.serialize_field("sender", &format!("{:#x}", self.sender))?;
3108 s.serialize_field(
3109 "frames",
3110 &self.frames.iter().map(FrameEntry::from).collect::<Vec<_>>(),
3111 )?;
3112 s.serialize_field(
3113 "signatures",
3114 &self
3115 .signatures
3116 .iter()
3117 .map(SignatureEntry::from)
3118 .collect::<Vec<_>>(),
3119 )?;
3120 s.serialize_field(
3121 "maxPriorityFeePerGas",
3122 &format!("{:#x}", self.max_priority_fee_per_gas),
3123 )?;
3124 s.serialize_field("maxFeePerGas", &format!("{:#x}", self.max_fee_per_gas))?;
3125 s.serialize_field("maxFeePerBlobGas", &self.max_fee_per_blob_gas)?;
3126 s.serialize_field("blobVersionedHashes", &self.blob_versioned_hashes)?;
3127 s.end()
3128 }
3129 }
3130
3131 impl<'de> Deserialize<'de> for Transaction {
3132 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3133 where
3134 D: serde::Deserializer<'de>,
3135 {
3136 let mut map = <HashMap<String, serde_json::Value>>::deserialize(deserializer)?;
3137 let tx_type =
3138 serde_json::from_value::<TxType>(map.remove("type").unwrap_or(Value::default()))
3139 .unwrap_or_else(|_| {
3140 if map.contains_key("tx_type") {
3141 return TxType::Privileged;
3142 }
3143 TxType::EIP1559
3144 });
3145
3146 let iter = map.into_iter();
3147 match tx_type {
3148 TxType::Legacy => {
3149 LegacyTransaction::deserialize(serde::de::value::MapDeserializer::new(iter))
3150 .map(Transaction::LegacyTransaction)
3151 .map_err(|e| {
3152 serde::de::Error::custom(format!("Couldn't Deserialize Legacy {e}"))
3153 })
3154 }
3155 TxType::EIP2930 => {
3156 EIP2930Transaction::deserialize(serde::de::value::MapDeserializer::new(iter))
3157 .map(Transaction::EIP2930Transaction)
3158 .map_err(|e| {
3159 serde::de::Error::custom(format!("Couldn't Deserialize EIP2930 {e}"))
3160 })
3161 }
3162 TxType::EIP1559 => {
3163 EIP1559Transaction::deserialize(serde::de::value::MapDeserializer::new(iter))
3164 .map(Transaction::EIP1559Transaction)
3165 .map_err(|e| {
3166 serde::de::Error::custom(format!("Couldn't Deserialize EIP1559 {e}"))
3167 })
3168 }
3169 TxType::EIP4844 => {
3170 EIP4844Transaction::deserialize(serde::de::value::MapDeserializer::new(iter))
3171 .map(Transaction::EIP4844Transaction)
3172 .map_err(|e| {
3173 serde::de::Error::custom(format!("Couldn't Deserialize EIP4844 {e}"))
3174 })
3175 }
3176 TxType::EIP7702 => {
3177 EIP7702Transaction::deserialize(serde::de::value::MapDeserializer::new(iter))
3178 .map(Transaction::EIP7702Transaction)
3179 .map_err(|e| {
3180 serde::de::Error::custom(format!("Couldn't Deserialize EIP7702 {e}"))
3181 })
3182 }
3183 TxType::Privileged => PrivilegedL2Transaction::deserialize(
3184 serde::de::value::MapDeserializer::new(iter),
3185 )
3186 .map(Transaction::PrivilegedL2Transaction)
3187 .map_err(|e| {
3188 serde::de::Error::custom(format!("Couldn't Deserialize Privileged: {e}"))
3189 }),
3190 TxType::FeeToken => {
3191 FeeTokenTransaction::deserialize(serde::de::value::MapDeserializer::new(iter))
3192 .map(Transaction::FeeTokenTransaction)
3193 .map_err(|e| {
3194 serde::de::Error::custom(format!("Couldn't Deserialize FeeToken {e}"))
3195 })
3196 }
3197 TxType::Frame => Err(serde::de::Error::custom(
3198 "Frame transaction JSON deserialization not supported",
3199 )),
3200 }
3201 }
3202 }
3203
3204 fn deserialize_input_field(
3205 map: &mut std::collections::HashMap<String, Value>,
3206 ) -> Result<Bytes, serde_json::Error> {
3207 let data_str: String = serde_json::from_value(
3208 map.remove("input")
3209 .ok_or_else(|| serde::de::Error::missing_field("input"))?,
3210 )
3211 .map_err(serde::de::Error::custom)?;
3212 if let Some(stripped) = data_str.strip_prefix("0x") {
3213 match hex::decode(stripped) {
3214 Ok(decoded_bytes) => Ok(Bytes::from(decoded_bytes)),
3215 Err(_) => Err(serde::de::Error::custom(
3216 "Invalid hex format in 'input' field",
3217 ))?,
3218 }
3219 } else {
3220 Err(serde::de::Error::custom(
3221 "'input' field must start with '0x'",
3222 ))?
3223 }
3224 }
3225
3226 fn deserialize_field<'de, T, D>(
3227 map: &mut HashMap<String, serde_json::Value>,
3228 key: &str,
3229 ) -> Result<T, D::Error>
3230 where
3231 D: serde::Deserializer<'de>,
3232 T: serde::de::DeserializeOwned,
3233 {
3234 map.remove(key)
3235 .ok_or_else(|| D::Error::custom(format!("Missing field: {key}")))
3236 .and_then(|value| {
3237 serde_json::from_value(value).map_err(|err| D::Error::custom(err.to_string()))
3238 })
3239 }
3240
3241 fn deserialize_u64_field<'de, D>(
3242 map: &mut HashMap<String, serde_json::Value>,
3243 key: &str,
3244 ) -> Result<u64, D::Error>
3245 where
3246 D: serde::Deserializer<'de>,
3247 {
3248 let value = deserialize_field::<U256, D>(map, key)?;
3249 u64::try_from(value).map_err(|_| D::Error::custom(format!("{key} value overflows u64")))
3250 }
3251
3252 impl<'de> Deserialize<'de> for LegacyTransaction {
3253 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3254 where
3255 D: serde::Deserializer<'de>,
3256 {
3257 let mut map = <HashMap<String, serde_json::Value>>::deserialize(deserializer)?;
3258
3259 Ok(LegacyTransaction {
3260 nonce: deserialize_u64_field::<D>(&mut map, "nonce")?,
3261 gas_price: deserialize_field::<U256, D>(&mut map, "gasPrice")?,
3262 gas: deserialize_u64_field::<D>(&mut map, "gas")?,
3263 to: deserialize_field::<TxKind, D>(&mut map, "to")?,
3264 value: deserialize_field::<U256, D>(&mut map, "value")?,
3265 data: deserialize_input_field(&mut map).map_err(serde::de::Error::custom)?,
3266 v: deserialize_field::<U256, D>(&mut map, "v")?,
3267 r: deserialize_field::<U256, D>(&mut map, "r")?,
3268 s: deserialize_field::<U256, D>(&mut map, "s")?,
3269 ..Default::default()
3270 })
3271 }
3272 }
3273
3274 impl<'de> Deserialize<'de> for EIP2930Transaction {
3275 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3276 where
3277 D: serde::Deserializer<'de>,
3278 {
3279 let mut map = <HashMap<String, serde_json::Value>>::deserialize(deserializer)?;
3280
3281 Ok(EIP2930Transaction {
3282 chain_id: deserialize_u64_field::<D>(&mut map, "chainId")?,
3283 nonce: deserialize_u64_field::<D>(&mut map, "nonce")?,
3284 gas_price: deserialize_field::<U256, D>(&mut map, "gasPrice")?,
3285 gas_limit: deserialize_u64_field::<D>(&mut map, "gas")?,
3286 to: deserialize_field::<TxKind, D>(&mut map, "to")?,
3287 value: deserialize_field::<U256, D>(&mut map, "value")?,
3288 data: deserialize_input_field(&mut map).map_err(serde::de::Error::custom)?,
3289 access_list: deserialize_field::<Vec<AccessListEntry>, D>(&mut map, "accessList")?
3290 .into_iter()
3291 .map(|v| (v.address, v.storage_keys))
3292 .collect::<Vec<_>>(),
3293 signature_y_parity: u8::from_str_radix(
3294 deserialize_field::<String, D>(&mut map, "yParity")?.trim_start_matches("0x"),
3295 16,
3296 )
3297 .map_err(serde::de::Error::custom)?
3298 != 0,
3299 signature_r: deserialize_field::<U256, D>(&mut map, "r")?,
3300 signature_s: deserialize_field::<U256, D>(&mut map, "s")?,
3301 ..Default::default()
3302 })
3303 }
3304 }
3305
3306 impl<'de> Deserialize<'de> for EIP1559Transaction {
3307 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3308 where
3309 D: serde::Deserializer<'de>,
3310 {
3311 let mut map = <HashMap<String, serde_json::Value>>::deserialize(deserializer)?;
3312 Ok(EIP1559Transaction {
3313 chain_id: deserialize_u64_field::<D>(&mut map, "chainId")?,
3314 nonce: deserialize_u64_field::<D>(&mut map, "nonce")?,
3315 max_priority_fee_per_gas: deserialize_u64_field::<D>(
3316 &mut map,
3317 "maxPriorityFeePerGas",
3318 )?,
3319 max_fee_per_gas: deserialize_u64_field::<D>(&mut map, "maxFeePerGas")?,
3320 gas_limit: deserialize_u64_field::<D>(&mut map, "gas")?,
3321 to: deserialize_field::<TxKind, D>(&mut map, "to")?,
3322 value: deserialize_field::<U256, D>(&mut map, "value")?,
3323 data: deserialize_input_field(&mut map).map_err(serde::de::Error::custom)?,
3324 access_list: deserialize_field::<Vec<AccessListEntry>, D>(&mut map, "accessList")?
3325 .into_iter()
3326 .map(|v| (v.address, v.storage_keys))
3327 .collect::<Vec<_>>(),
3328 signature_y_parity: u8::from_str_radix(
3329 deserialize_field::<String, D>(&mut map, "yParity")?.trim_start_matches("0x"),
3330 16,
3331 )
3332 .map_err(serde::de::Error::custom)?
3333 != 0,
3334 signature_r: deserialize_field::<U256, D>(&mut map, "r")?,
3335 signature_s: deserialize_field::<U256, D>(&mut map, "s")?,
3336 ..Default::default()
3337 })
3338 }
3339 }
3340
3341 impl<'de> Deserialize<'de> for EIP4844Transaction {
3342 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3343 where
3344 D: serde::Deserializer<'de>,
3345 {
3346 let mut map = <HashMap<String, serde_json::Value>>::deserialize(deserializer)?;
3347
3348 Ok(EIP4844Transaction {
3349 chain_id: deserialize_u64_field::<D>(&mut map, "chainId")?,
3350 nonce: deserialize_u64_field::<D>(&mut map, "nonce")?,
3351 max_priority_fee_per_gas: deserialize_u64_field::<D>(
3352 &mut map,
3353 "maxPriorityFeePerGas",
3354 )?,
3355 max_fee_per_gas: deserialize_u64_field::<D>(&mut map, "maxFeePerGas")?,
3356 gas: deserialize_u64_field::<D>(&mut map, "gas")?,
3357 to: deserialize_field::<Address, D>(&mut map, "to")?,
3358 value: deserialize_field::<U256, D>(&mut map, "value")?,
3359 data: deserialize_input_field(&mut map).map_err(serde::de::Error::custom)?,
3360 access_list: deserialize_field::<Vec<AccessListEntry>, D>(&mut map, "accessList")?
3361 .into_iter()
3362 .map(|v| (v.address, v.storage_keys))
3363 .collect::<Vec<_>>(),
3364 max_fee_per_blob_gas: deserialize_field::<U256, D>(&mut map, "maxFeePerBlobGas")?,
3365 blob_versioned_hashes: deserialize_field::<Vec<H256>, D>(
3366 &mut map,
3367 "blobVersionedHashes",
3368 )?,
3369 signature_y_parity: u8::from_str_radix(
3370 deserialize_field::<String, D>(&mut map, "yParity")?.trim_start_matches("0x"),
3371 16,
3372 )
3373 .map_err(serde::de::Error::custom)?
3374 != 0,
3375 signature_r: deserialize_field::<U256, D>(&mut map, "r")?,
3376 signature_s: deserialize_field::<U256, D>(&mut map, "s")?,
3377 ..Default::default()
3378 })
3379 }
3380 }
3381
3382 impl<'de> Deserialize<'de> for EIP7702Transaction {
3383 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3384 where
3385 D: serde::Deserializer<'de>,
3386 {
3387 let mut map = <HashMap<String, serde_json::Value>>::deserialize(deserializer)?;
3388
3389 Ok(EIP7702Transaction {
3390 chain_id: deserialize_u64_field::<D>(&mut map, "chainId")?,
3391 nonce: deserialize_u64_field::<D>(&mut map, "nonce")?,
3392 max_priority_fee_per_gas: deserialize_u64_field::<D>(
3393 &mut map,
3394 "maxPriorityFeePerGas",
3395 )?,
3396 max_fee_per_gas: deserialize_u64_field::<D>(&mut map, "maxFeePerGas")?,
3397 gas_limit: deserialize_u64_field::<D>(&mut map, "gas")?,
3398 to: deserialize_field::<Address, D>(&mut map, "to")?,
3399 value: deserialize_field::<U256, D>(&mut map, "value")?,
3400 data: deserialize_input_field(&mut map).map_err(serde::de::Error::custom)?,
3401 access_list: deserialize_field::<Vec<AccessListEntry>, D>(&mut map, "accessList")?
3402 .into_iter()
3403 .map(|v| (v.address, v.storage_keys))
3404 .collect::<Vec<_>>(),
3405 authorization_list: deserialize_field::<Vec<AuthorizationTupleEntry>, D>(
3406 &mut map,
3407 "authorizationList",
3408 )?
3409 .into_iter()
3410 .map(AuthorizationTuple::from)
3411 .collect::<Vec<_>>(),
3412 signature_y_parity: u8::from_str_radix(
3413 deserialize_field::<String, D>(&mut map, "yParity")?.trim_start_matches("0x"),
3414 16,
3415 )
3416 .map_err(serde::de::Error::custom)?
3417 != 0,
3418 signature_r: deserialize_field::<U256, D>(&mut map, "r")?,
3419 signature_s: deserialize_field::<U256, D>(&mut map, "s")?,
3420 ..Default::default()
3421 })
3422 }
3423 }
3424
3425 impl<'de> Deserialize<'de> for PrivilegedL2Transaction {
3426 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3427 where
3428 D: serde::Deserializer<'de>,
3429 {
3430 let mut map = <HashMap<String, serde_json::Value>>::deserialize(deserializer)?;
3431
3432 Ok(PrivilegedL2Transaction {
3433 chain_id: deserialize_u64_field::<D>(&mut map, "chainId")?,
3434 nonce: deserialize_u64_field::<D>(&mut map, "nonce")?,
3435 max_priority_fee_per_gas: deserialize_u64_field::<D>(
3436 &mut map,
3437 "maxPriorityFeePerGas",
3438 )?,
3439 max_fee_per_gas: deserialize_u64_field::<D>(&mut map, "maxFeePerGas")?,
3440 gas_limit: deserialize_u64_field::<D>(&mut map, "gas")?,
3441 to: deserialize_field::<TxKind, D>(&mut map, "to")?,
3442 value: deserialize_field::<U256, D>(&mut map, "value")?,
3443 data: deserialize_input_field(&mut map).map_err(serde::de::Error::custom)?,
3444 access_list: deserialize_field::<Vec<AccessListEntry>, D>(&mut map, "accessList")?
3445 .into_iter()
3446 .map(|v| (v.address, v.storage_keys))
3447 .collect::<Vec<_>>(),
3448 from: deserialize_field::<Address, D>(&mut map, "sender")?,
3449 ..Default::default()
3450 })
3451 }
3452 }
3453
3454 impl<'de> Deserialize<'de> for FeeTokenTransaction {
3455 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3456 where
3457 D: serde::Deserializer<'de>,
3458 {
3459 let mut map = <HashMap<String, serde_json::Value>>::deserialize(deserializer)?;
3460
3461 Ok(FeeTokenTransaction {
3462 chain_id: deserialize_u64_field::<D>(&mut map, "chainId")?,
3463 nonce: deserialize_u64_field::<D>(&mut map, "nonce")?,
3464 max_priority_fee_per_gas: deserialize_u64_field::<D>(
3465 &mut map,
3466 "maxPriorityFeePerGas",
3467 )?,
3468 max_fee_per_gas: deserialize_u64_field::<D>(&mut map, "maxFeePerGas")?,
3469 gas_limit: deserialize_u64_field::<D>(&mut map, "gas")?,
3470 to: deserialize_field::<TxKind, D>(&mut map, "to")?,
3471 value: deserialize_field::<U256, D>(&mut map, "value")?,
3472 data: deserialize_input_field(&mut map).map_err(serde::de::Error::custom)?,
3473 access_list: deserialize_field::<Vec<AccessListEntry>, D>(&mut map, "accessList")?
3474 .into_iter()
3475 .map(|v| (v.address, v.storage_keys))
3476 .collect::<Vec<_>>(),
3477 fee_token: deserialize_field::<Address, D>(&mut map, "feeToken")?,
3478 signature_y_parity: u8::from_str_radix(
3479 deserialize_field::<String, D>(&mut map, "yParity")?.trim_start_matches("0x"),
3480 16,
3481 )
3482 .map_err(serde::de::Error::custom)?
3483 != 0,
3484 signature_r: deserialize_field::<U256, D>(&mut map, "r")?,
3485 signature_s: deserialize_field::<U256, D>(&mut map, "s")?,
3486 ..Default::default()
3487 })
3488 }
3489 }
3490
3491 #[derive(Debug, thiserror::Error)]
3492 pub enum GenericTransactionError {
3493 #[error("Invalid transaction type: {0}")]
3494 InvalidTxType(TxType),
3495 #[error("Blob bundle error: {0}")]
3496 BlobBundleError(#[from] BlobsBundleError),
3497 #[error("Missing field: {0}")]
3498 MissingField(String),
3499 #[error("Invalid field: {0}")]
3500 InvalidField(String),
3501 }
3502
3503 #[derive(Deserialize, Debug, PartialEq, Clone, Default)]
3506 #[serde(rename_all = "camelCase")]
3507 pub struct GenericTransaction {
3508 #[serde(default)]
3509 pub r#type: TxType,
3510 #[serde(default, with = "crate::serde_utils::u64::hex_str_opt")]
3511 pub nonce: Option<u64>,
3512 pub to: TxKind,
3513 #[serde(default)]
3514 pub from: Address,
3515 #[serde(default, with = "crate::serde_utils::u64::hex_str_opt")]
3516 pub gas: Option<u64>,
3517 #[serde(default)]
3518 pub value: U256,
3519 #[serde(default)]
3520 pub gas_price: U256,
3521 #[serde(default, with = "crate::serde_utils::u64::hex_str_opt")]
3522 pub max_priority_fee_per_gas: Option<u64>,
3523 #[serde(default, with = "crate::serde_utils::u64::hex_str_opt")]
3524 pub max_fee_per_gas: Option<u64>,
3525 pub max_fee_per_blob_gas: Option<U256>,
3526 #[serde(default)]
3527 pub access_list: Vec<AccessListEntry>,
3528 #[serde(default)]
3529 pub fee_token: Option<Address>,
3530 #[serde(default)]
3531 pub authorization_list: Option<Vec<AuthorizationTupleEntry>>,
3532 #[serde(default)]
3533 pub blob_versioned_hashes: Vec<H256>,
3534 pub wrapper_version: Option<u8>,
3535 #[serde(default, with = "crate::serde_utils::bytes::vec")]
3536 pub blobs: Vec<Bytes>,
3537 #[serde(default, with = "crate::serde_utils::u64::hex_str_opt")]
3538 pub chain_id: Option<u64>,
3539 #[serde(
3541 flatten,
3542 rename = "input_or_data",
3543 deserialize_with = "deserialize_input",
3544 serialize_with = "crate::serde_utils::bytes::serialize"
3545 )]
3546 pub input: Bytes,
3547 }
3548 pub fn deserialize_input<'de, D>(deserializer: D) -> Result<Bytes, D::Error>
3550 where
3551 D: Deserializer<'de>,
3552 {
3553 let variables = HashMap::<String, Value>::deserialize(deserializer)?;
3556 let data = variables.get("data");
3557 let input = variables.get("input");
3558 let value = match (data, input) {
3559 (None, None) => return Ok(Bytes::new()),
3561 (None, Some(val)) => val,
3562 (Some(val), None) => val,
3563 (Some(val_a), Some(val_b)) => {
3564 if val_a == val_b {
3565 val_a
3566 } else {
3567 return Err(D::Error::custom(
3568 "Transaction has both `data` and `input` fields with different values",
3569 ));
3570 }
3571 }
3572 };
3573 let value = String::deserialize(value).map_err(D::Error::custom)?;
3574 let bytes = hex::decode(value.trim_start_matches("0x"))
3575 .map_err(|e| D::Error::custom(e.to_string()))?;
3576 Ok(Bytes::from(bytes))
3577 }
3578
3579 impl From<EIP1559Transaction> for GenericTransaction {
3580 fn from(value: EIP1559Transaction) -> Self {
3581 Self {
3582 r#type: TxType::EIP1559,
3583 nonce: Some(value.nonce),
3584 to: value.to,
3585 gas: Some(value.gas_limit),
3586 value: value.value,
3587 input: value.data.clone(),
3588 gas_price: U256::from(value.max_fee_per_gas),
3589 max_priority_fee_per_gas: Some(value.max_priority_fee_per_gas),
3590 max_fee_per_gas: Some(value.max_fee_per_gas),
3591 max_fee_per_blob_gas: None,
3592 access_list: value
3593 .access_list
3594 .iter()
3595 .map(AccessListEntry::from)
3596 .collect(),
3597 fee_token: None,
3598 authorization_list: None,
3599 blob_versioned_hashes: vec![],
3600 blobs: vec![],
3601 wrapper_version: None,
3602 chain_id: Some(value.chain_id),
3603 from: Address::default(),
3604 }
3605 }
3606 }
3607
3608 impl TryFrom<GenericTransaction> for EIP1559Transaction {
3609 type Error = GenericTransactionError;
3610
3611 fn try_from(value: GenericTransaction) -> Result<Self, Self::Error> {
3612 if value.r#type != TxType::EIP1559 {
3613 return Err(GenericTransactionError::InvalidTxType(value.r#type));
3614 }
3615
3616 Ok(Self {
3617 nonce: value.nonce.unwrap_or_default(),
3618 to: value.to,
3619 gas_limit: value.gas.unwrap_or_default(),
3620 value: value.value,
3621 data: value.input.clone(),
3622 max_priority_fee_per_gas: value.max_priority_fee_per_gas.unwrap_or_default(),
3623 max_fee_per_gas: value.max_fee_per_gas.unwrap_or(
3624 u64::try_from(value.gas_price).map_err(|_| {
3625 GenericTransactionError::InvalidField("gas_price overflows u64".to_owned())
3626 })?,
3627 ),
3628 access_list: value
3629 .access_list
3630 .into_iter()
3631 .map(|v| (v.address, v.storage_keys))
3632 .collect::<Vec<_>>(),
3633 chain_id: value.chain_id.unwrap_or_default(),
3634 ..Default::default()
3635 })
3636 }
3637 }
3638
3639 impl From<EIP4844Transaction> for GenericTransaction {
3640 fn from(value: EIP4844Transaction) -> Self {
3641 Self {
3642 r#type: TxType::EIP4844,
3643 nonce: Some(value.nonce),
3644 to: TxKind::Call(value.to),
3645 gas: Some(value.gas),
3646 value: value.value,
3647 input: value.data.clone(),
3648 gas_price: U256::from(value.max_fee_per_gas),
3649 max_priority_fee_per_gas: Some(value.max_priority_fee_per_gas),
3650 max_fee_per_gas: Some(value.max_fee_per_gas),
3651 max_fee_per_blob_gas: Some(value.max_fee_per_blob_gas),
3652 access_list: value
3653 .access_list
3654 .iter()
3655 .map(AccessListEntry::from)
3656 .collect(),
3657 fee_token: None,
3658 authorization_list: None,
3659 blob_versioned_hashes: value.blob_versioned_hashes,
3660 blobs: vec![],
3661 wrapper_version: None,
3662 chain_id: Some(value.chain_id),
3663 from: Address::default(),
3664 }
3665 }
3666 }
3667
3668 #[cfg(feature = "c-kzg")]
3669 impl TryFrom<GenericTransaction> for WrappedEIP4844Transaction {
3670 type Error = GenericTransactionError;
3671
3672 fn try_from(value: GenericTransaction) -> Result<Self, Self::Error> {
3673 let blobs = value
3674 .blobs
3675 .iter()
3676 .map(|bytes| {
3677 let slice = bytes.as_ref();
3678 let mut blob = [0u8; BYTES_PER_BLOB];
3679 blob.copy_from_slice(slice);
3680 blob
3681 })
3682 .collect();
3683
3684 let wrapper_version = value.wrapper_version;
3685 Ok(Self {
3686 tx: value.try_into()?,
3687 wrapper_version,
3688 blobs_bundle: BlobsBundle::create_from_blobs(&blobs, wrapper_version)?,
3689 })
3690 }
3691 }
3692
3693 impl TryFrom<GenericTransaction> for EIP4844Transaction {
3694 type Error = GenericTransactionError;
3695
3696 fn try_from(value: GenericTransaction) -> Result<Self, Self::Error> {
3697 if value.r#type != TxType::EIP4844 {
3698 return Err(GenericTransactionError::InvalidTxType(value.r#type));
3699 }
3700 Ok(Self {
3701 nonce: value.nonce.unwrap_or_default(),
3702 to: match value.to {
3703 TxKind::Call(to) => to,
3704 _ => H160::default(),
3705 },
3706 gas: value.gas.unwrap_or_default(),
3707 value: value.value,
3708 data: value.input.clone(),
3709 max_priority_fee_per_gas: value.max_priority_fee_per_gas.unwrap_or_default(),
3710 max_fee_per_gas: value.max_fee_per_gas.unwrap_or(
3711 u64::try_from(value.gas_price).map_err(|_| {
3712 GenericTransactionError::InvalidField("gas_price overflows u64".to_owned())
3713 })?,
3714 ),
3715 max_fee_per_blob_gas: value.max_fee_per_blob_gas.unwrap_or_default(),
3716 access_list: value
3717 .access_list
3718 .into_iter()
3719 .map(|v| (v.address, v.storage_keys))
3720 .collect::<Vec<_>>(),
3721 blob_versioned_hashes: value.blob_versioned_hashes,
3722 chain_id: value.chain_id.unwrap_or_default(),
3723 ..Default::default()
3724 })
3725 }
3726 }
3727
3728 impl From<EIP7702Transaction> for GenericTransaction {
3729 fn from(value: EIP7702Transaction) -> Self {
3730 Self {
3731 r#type: TxType::EIP7702,
3732 nonce: Some(value.nonce),
3733 to: TxKind::Call(value.to),
3734 gas: Some(value.gas_limit),
3735 value: value.value,
3736 input: value.data.clone(),
3737 gas_price: U256::from(value.max_fee_per_gas),
3738 max_priority_fee_per_gas: Some(value.max_priority_fee_per_gas),
3739 max_fee_per_gas: Some(value.max_fee_per_gas),
3740 max_fee_per_blob_gas: None,
3741 access_list: value
3742 .access_list
3743 .iter()
3744 .map(AccessListEntry::from)
3745 .collect(),
3746 fee_token: None,
3747 authorization_list: Some(
3748 value
3749 .authorization_list
3750 .iter()
3751 .map(AuthorizationTupleEntry::from)
3752 .collect(),
3753 ),
3754 blob_versioned_hashes: vec![],
3755 blobs: vec![],
3756 wrapper_version: None,
3757 chain_id: Some(value.chain_id),
3758 from: Address::default(),
3759 }
3760 }
3761 }
3762
3763 impl TryFrom<GenericTransaction> for EIP7702Transaction {
3764 type Error = GenericTransactionError;
3765
3766 fn try_from(value: GenericTransaction) -> Result<Self, Self::Error> {
3767 if value.r#type != TxType::EIP7702 {
3768 return Err(GenericTransactionError::InvalidTxType(value.r#type));
3769 }
3770 let TxKind::Call(to) = value.to else {
3771 return Err(GenericTransactionError::MissingField("to".to_owned()));
3772 };
3773 Ok(Self {
3774 chain_id: value.chain_id.unwrap_or_default(),
3775 nonce: value.nonce.unwrap_or_default(),
3776 max_priority_fee_per_gas: value.max_priority_fee_per_gas.unwrap_or_default(),
3777 max_fee_per_gas: value.max_fee_per_gas.unwrap_or(
3778 u64::try_from(value.gas_price).map_err(|_| {
3779 GenericTransactionError::InvalidField("gas_price overflows u64".to_owned())
3780 })?,
3781 ),
3782 gas_limit: value.gas.unwrap_or_default(),
3783 to,
3784 value: value.value,
3785 data: value.input,
3786 access_list: value
3787 .access_list
3788 .into_iter()
3789 .map(|v| (v.address, v.storage_keys))
3790 .collect::<Vec<_>>(),
3791 authorization_list: value
3792 .authorization_list
3793 .unwrap_or_default()
3794 .into_iter()
3795 .map(AuthorizationTuple::from)
3796 .collect(),
3797 ..Default::default()
3798 })
3799 }
3800 }
3801
3802 impl From<PrivilegedL2Transaction> for GenericTransaction {
3803 fn from(value: PrivilegedL2Transaction) -> Self {
3804 Self {
3805 r#type: TxType::Privileged,
3806 nonce: Some(value.nonce),
3807 to: value.to,
3808 gas: Some(value.gas_limit),
3809 value: value.value,
3810 input: value.data.clone(),
3811 gas_price: U256::from(value.max_fee_per_gas),
3812 max_priority_fee_per_gas: Some(value.max_priority_fee_per_gas),
3813 max_fee_per_gas: Some(value.max_fee_per_gas),
3814 max_fee_per_blob_gas: None,
3815 access_list: value
3816 .access_list
3817 .iter()
3818 .map(AccessListEntry::from)
3819 .collect(),
3820 fee_token: None,
3821 authorization_list: None,
3822 blob_versioned_hashes: vec![],
3823 blobs: vec![],
3824 wrapper_version: None,
3825 chain_id: Some(value.chain_id),
3826 from: value.from,
3827 }
3828 }
3829 }
3830
3831 impl TryFrom<GenericTransaction> for PrivilegedL2Transaction {
3832 type Error = GenericTransactionError;
3833
3834 fn try_from(value: GenericTransaction) -> Result<Self, Self::Error> {
3835 if value.r#type != TxType::Privileged {
3836 return Err(GenericTransactionError::InvalidTxType(value.r#type));
3837 }
3838 Ok(Self {
3839 nonce: value.nonce.unwrap_or_default(),
3840 to: value.to,
3841 gas_limit: value.gas.unwrap_or_default(),
3842 value: value.value,
3843 data: value.input.clone(),
3844 max_priority_fee_per_gas: value.max_priority_fee_per_gas.unwrap_or_default(),
3845 max_fee_per_gas: value.max_fee_per_gas.unwrap_or(
3846 u64::try_from(value.gas_price).map_err(|_| {
3847 GenericTransactionError::InvalidField("gas_price overflows u64".to_owned())
3848 })?,
3849 ),
3850 access_list: value
3851 .access_list
3852 .into_iter()
3853 .map(|v| (v.address, v.storage_keys))
3854 .collect::<Vec<_>>(),
3855 chain_id: value.chain_id.unwrap_or_default(),
3856 from: value.from,
3857 ..Default::default()
3858 })
3859 }
3860 }
3861
3862 impl From<FeeTokenTransaction> for GenericTransaction {
3863 fn from(value: FeeTokenTransaction) -> Self {
3864 Self {
3865 r#type: TxType::FeeToken,
3866 nonce: Some(value.nonce),
3867 to: value.to,
3868 gas: Some(value.gas_limit),
3869 value: value.value,
3870 input: value.data.clone(),
3871 gas_price: U256::from(value.max_fee_per_gas),
3872 max_priority_fee_per_gas: Some(value.max_priority_fee_per_gas),
3873 max_fee_per_gas: Some(value.max_fee_per_gas),
3874 max_fee_per_blob_gas: None,
3875 access_list: value
3876 .access_list
3877 .iter()
3878 .map(AccessListEntry::from)
3879 .collect(),
3880 fee_token: Some(value.fee_token),
3881 authorization_list: None,
3882 blob_versioned_hashes: vec![],
3883 blobs: vec![],
3884 chain_id: Some(value.chain_id),
3885 from: Address::default(),
3886 wrapper_version: None,
3887 }
3888 }
3889 }
3890
3891 impl TryFrom<GenericTransaction> for FeeTokenTransaction {
3892 type Error = GenericTransactionError;
3893
3894 fn try_from(value: GenericTransaction) -> Result<Self, Self::Error> {
3895 if value.r#type != TxType::FeeToken {
3896 return Err(GenericTransactionError::InvalidTxType(value.r#type));
3897 }
3898
3899 Ok(Self {
3900 nonce: value.nonce.unwrap_or_default(),
3901 to: value.to,
3902 gas_limit: value.gas.unwrap_or_default(),
3903 value: value.value,
3904 data: value.input.clone(),
3905 max_priority_fee_per_gas: value.max_priority_fee_per_gas.unwrap_or_default(),
3906 max_fee_per_gas: value.max_fee_per_gas.unwrap_or(
3907 u64::try_from(value.gas_price).map_err(|_| {
3908 GenericTransactionError::InvalidField("gas_price overflows u64".to_owned())
3909 })?,
3910 ),
3911 access_list: value
3912 .access_list
3913 .into_iter()
3914 .map(|v| (v.address, v.storage_keys))
3915 .collect::<Vec<_>>(),
3916 fee_token: value
3917 .fee_token
3918 .ok_or(GenericTransactionError::MissingField(
3919 "fee token".to_owned(),
3920 ))?,
3921 chain_id: value.chain_id.unwrap_or_default(),
3922 ..Default::default()
3923 })
3924 }
3925 }
3926
3927 impl From<LegacyTransaction> for GenericTransaction {
3928 fn from(value: LegacyTransaction) -> Self {
3929 Self {
3930 r#type: TxType::Legacy,
3931 nonce: Some(value.nonce),
3932 to: value.to,
3933 from: Address::default(),
3934 gas: Some(value.gas),
3935 value: value.value,
3936 gas_price: value.gas_price,
3937 max_priority_fee_per_gas: None,
3938 max_fee_per_gas: None,
3939 max_fee_per_blob_gas: None,
3940 access_list: vec![],
3941 fee_token: None,
3942 authorization_list: None,
3943 blob_versioned_hashes: vec![],
3944 blobs: vec![],
3945 wrapper_version: None,
3946 chain_id: None,
3947 input: value.data,
3948 }
3949 }
3950 }
3951
3952 impl From<EIP2930Transaction> for GenericTransaction {
3953 fn from(value: EIP2930Transaction) -> Self {
3954 Self {
3955 r#type: TxType::EIP2930,
3956 nonce: Some(value.nonce),
3957 to: value.to,
3958 from: Address::default(),
3959 gas: Some(value.gas_limit),
3960 value: value.value,
3961 gas_price: value.gas_price,
3962 max_priority_fee_per_gas: None,
3963 max_fee_per_gas: None,
3964 max_fee_per_blob_gas: None,
3965 access_list: value
3966 .access_list
3967 .into_iter()
3968 .map(|(address, storage_keys)| AccessListEntry {
3969 address,
3970 storage_keys,
3971 })
3972 .collect(),
3973 fee_token: None,
3974 authorization_list: None,
3975 blob_versioned_hashes: vec![],
3976 blobs: vec![],
3977 wrapper_version: None,
3978 chain_id: Some(value.chain_id),
3979 input: value.data,
3980 }
3981 }
3982 }
3983
3984 impl From<Transaction> for GenericTransaction {
3985 fn from(value: Transaction) -> Self {
3986 match value {
3987 Transaction::LegacyTransaction(tx) => tx.into(),
3988 Transaction::EIP2930Transaction(tx) => tx.into(),
3989 Transaction::EIP1559Transaction(tx) => tx.into(),
3990 Transaction::EIP4844Transaction(tx) => tx.into(),
3991 Transaction::EIP7702Transaction(tx) => tx.into(),
3992 Transaction::PrivilegedL2Transaction(tx) => tx.into(),
3993 Transaction::FeeTokenTransaction(tx) => tx.into(),
3994 Transaction::FrameTransaction(tx) => tx.into(),
3995 }
3996 }
3997 }
3998
3999 impl From<FrameTransaction> for GenericTransaction {
4000 fn from(value: FrameTransaction) -> Self {
4001 Self {
4002 r#type: TxType::Frame,
4003 nonce: Some(value.nonce),
4004 to: TxKind::Call(value.sender),
4005 from: value.sender,
4006 gas: Some(value.total_gas_limit()),
4007 value: U256::zero(),
4008 gas_price: value.max_fee_per_gas.into(),
4009 max_priority_fee_per_gas: Some(value.max_priority_fee_per_gas),
4010 max_fee_per_gas: Some(value.max_fee_per_gas),
4011 max_fee_per_blob_gas: if value.blob_versioned_hashes.is_empty() {
4012 None
4013 } else {
4014 Some(value.max_fee_per_blob_gas)
4015 },
4016 access_list: vec![],
4017 fee_token: None,
4018 authorization_list: None,
4019 blob_versioned_hashes: value.blob_versioned_hashes,
4020 blobs: vec![],
4021 wrapper_version: None,
4022 chain_id: Some(value.chain_id),
4023 input: Bytes::new(),
4024 }
4025 }
4026 }
4027}
4028
4029mod mempool {
4030 use super::*;
4031 use std::{
4032 cmp::Ordering,
4033 sync::Arc,
4034 time::{SystemTime, UNIX_EPOCH},
4035 };
4036
4037 #[derive(Debug, Clone, PartialEq, Eq)]
4038 pub struct MempoolTransaction {
4039 timestamp: u128,
4041 sender: Address,
4042 inner: Arc<Transaction>,
4043 }
4044
4045 impl MempoolTransaction {
4046 pub fn new(tx: Transaction, sender: Address) -> Self {
4047 Self {
4048 timestamp: SystemTime::now()
4049 .duration_since(UNIX_EPOCH)
4050 .expect("Invalid system time")
4051 .as_micros(),
4052 sender,
4053 inner: Arc::new(tx),
4054 }
4055 }
4056 pub fn time(&self) -> u128 {
4057 self.timestamp
4058 }
4059
4060 pub fn sender(&self) -> Address {
4061 self.sender
4062 }
4063
4064 pub fn transaction(&self) -> &Transaction {
4065 &self.inner
4066 }
4067 }
4068
4069 impl RLPEncode for MempoolTransaction {
4070 fn encode(&self, buf: &mut dyn bytes::BufMut) {
4071 Encoder::new(buf)
4072 .encode_field(&self.timestamp)
4073 .encode_field(&*self.inner)
4074 .finish();
4075 }
4076 }
4077
4078 impl RLPDecode for MempoolTransaction {
4079 fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
4080 let decoder = Decoder::new(rlp)?;
4081 let (timestamp, decoder) = decoder.decode_field("timestamp")?;
4082 let (sender, decoder) = decoder.decode_field("sender")?;
4083 let (inner, decoder) = decoder.decode_field("inner")?;
4084 Ok((
4085 Self {
4086 timestamp,
4087 sender,
4088 inner: Arc::new(inner),
4089 },
4090 decoder.finish()?,
4091 ))
4092 }
4093 }
4094
4095 impl std::ops::Deref for MempoolTransaction {
4096 type Target = Transaction;
4097
4098 fn deref(&self) -> &Self::Target {
4099 &self.inner
4100 }
4101 }
4102
4103 impl Ord for MempoolTransaction {
4105 fn cmp(&self, other: &Self) -> Ordering {
4106 match (self.tx_type(), other.tx_type()) {
4107 (TxType::Privileged, TxType::Privileged) => {
4108 return self.nonce().cmp(&other.nonce());
4109 }
4110 (TxType::Privileged, _) => return Ordering::Less,
4111 (_, TxType::Privileged) => return Ordering::Greater,
4112 _ => (),
4113 };
4114 match self.nonce().cmp(&other.nonce()) {
4115 Ordering::Equal => other.time().cmp(&self.time()),
4116 ordering => ordering,
4117 }
4118 }
4119 }
4120
4121 impl PartialOrd for MempoolTransaction {
4122 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
4123 Some(self.cmp(other))
4124 }
4125 }
4126}
4127
4128#[cfg(test)]
4129mod tests {
4130
4131 use super::*;
4132 use crate::types::{
4133 AuthorizationTuple, BlockBody, Receipt, compute_receipts_root, compute_transactions_root,
4134 };
4135 use ethereum_types::H160;
4136 use hex_literal::hex;
4137 use serde_impl::{AccessListEntry, GenericTransaction};
4138 use std::str::FromStr;
4139
4140 #[test]
4141 fn legacy_chain_id_handles_out_of_range_v_without_underflow() {
4142 let legacy_chain_id = |v: u64| {
4148 Transaction::LegacyTransaction(LegacyTransaction {
4149 v: U256::from(v),
4150 ..Default::default()
4151 })
4152 .chain_id()
4153 };
4154 for v in [0u64, 1, 26, 27, 28, 34] {
4155 assert_eq!(legacy_chain_id(v), None, "v={v} must yield no chain id");
4156 }
4157 assert_eq!(legacy_chain_id(35), Some(0));
4159 assert_eq!(legacy_chain_id(36), Some(0));
4160 assert_eq!(legacy_chain_id(37), Some(1));
4161 }
4162
4163 #[test]
4164 fn test_compute_transactions_root() {
4165 let mut body = BlockBody::empty();
4166 let tx = LegacyTransaction {
4167 nonce: 0,
4168 gas_price: U256::from(0x0a),
4169 gas: 0x05f5e100,
4170 to: TxKind::Call(hex!("1000000000000000000000000000000000000000").into()),
4171 value: 0.into(),
4172 data: Default::default(),
4173 v: U256::from(0x1b),
4174 r: U256::from_big_endian(&hex!(
4175 "7e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37"
4176 )),
4177 s: U256::from_big_endian(&hex!(
4178 "5f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509b"
4179 )),
4180 ..Default::default()
4181 };
4182 body.transactions.push(Transaction::LegacyTransaction(tx));
4183 let expected_root =
4184 hex!("8151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcb");
4185 let result = compute_transactions_root(&body.transactions, ðrex_crypto::NativeCrypto);
4186
4187 assert_eq!(result, expected_root.into());
4188 }
4189 #[test]
4190 fn test_compute_hash() {
4191 let tx_eip2930 = EIP2930Transaction {
4193 chain_id: 3503995874084926u64,
4194 nonce: 7,
4195 gas_price: U256::from(0x2dbf1f9a_u64),
4196 gas_limit: 0x186A0,
4197 to: TxKind::Call(hex!("7dcd17433742f4c0ca53122ab541d0ba67fc27df").into()),
4198 value: 2.into(),
4199 data: Bytes::from(&b"\xdbS\x06$\x8e\x03\x13\xe7emit"[..]),
4200 access_list: vec![(
4201 hex!("7dcd17433742f4c0ca53122ab541d0ba67fc27df").into(),
4202 vec![
4203 hex!("0000000000000000000000000000000000000000000000000000000000000000").into(),
4204 hex!("a3d07a7d68fbd49ec2f8e6befdd86c885f86c272819f6f345f365dec35ae6707").into(),
4205 ],
4206 )],
4207 signature_y_parity: false,
4208 signature_r: U256::from_dec_str(
4209 "75813812796588349127366022588733264074091236448495248199152066031778895768879",
4210 )
4211 .unwrap(),
4212 signature_s: U256::from_dec_str(
4213 "25476208226281085290728123165613764315157904411823916642262684106502155457829",
4214 )
4215 .unwrap(),
4216 ..Default::default()
4217 };
4218 let tx = Transaction::EIP2930Transaction(tx_eip2930);
4219
4220 let expected_hash =
4221 hex!("a0762610d794acddd2dca15fb7c437ada3611c886f3bea675d53d8da8a6c41b2");
4222 let hash = tx.compute_hash(&NativeCrypto);
4223 assert_eq!(hash, expected_hash.into());
4224 }
4225
4226 #[test]
4227 fn test_compute_receipts_root() {
4228 let tx_type = TxType::Legacy;
4231 let succeeded = true;
4232 let cumulative_gas_used = 0x5208;
4233 let logs = vec![];
4234 let receipt = Receipt::new(tx_type, succeeded, cumulative_gas_used, logs);
4235
4236 let result = compute_receipts_root(&[receipt], ðrex_crypto::NativeCrypto);
4237 let expected_root =
4238 hex!("056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2");
4239 assert_eq!(result, expected_root.into());
4240 }
4241
4242 #[test]
4243 fn legacy_tx_rlp_decode() {
4244 let encoded_tx = "f86d80843baa0c4082f618946177843db3138ae69679a54b95cf345ed759450d870aa87bee538000808360306ba0151ccc02146b9b11adf516e6787b59acae3e76544fdcd75e77e67c6b598ce65da064c5dd5aae2fbb535830ebbdad0234975cd7ece3562013b63ea18cc0df6c97d4";
4245 let encoded_tx_bytes = hex::decode(encoded_tx).unwrap();
4246 let tx = LegacyTransaction::decode(&encoded_tx_bytes).unwrap();
4247 let expected_tx = LegacyTransaction {
4248 nonce: 0,
4249 gas_price: U256::from(1001000000u64),
4250 gas: 63000,
4251 to: TxKind::Call(Address::from_slice(
4252 &hex::decode("6177843db3138ae69679A54b95cf345ED759450d").unwrap(),
4253 )),
4254 value: 3000000000000000_u64.into(),
4255 data: Bytes::new(),
4256 r: U256::from_str_radix(
4257 "151ccc02146b9b11adf516e6787b59acae3e76544fdcd75e77e67c6b598ce65d",
4258 16,
4259 )
4260 .unwrap(),
4261 s: U256::from_str_radix(
4262 "64c5dd5aae2fbb535830ebbdad0234975cd7ece3562013b63ea18cc0df6c97d4",
4263 16,
4264 )
4265 .unwrap(),
4266 v: 6303851.into(),
4267 ..Default::default()
4268 };
4269 assert_eq!(tx, expected_tx);
4270 }
4271
4272 #[test]
4273 fn eip1559_tx_rlp_decode() {
4274 let encoded_tx = "f86c8330182480114e82f618946177843db3138ae69679a54b95cf345ed759450d870aa87bee53800080c080a0151ccc02146b9b11adf516e6787b59acae3e76544fdcd75e77e67c6b598ce65da064c5dd5aae2fbb535830ebbdad0234975cd7ece3562013b63ea18cc0df6c97d4";
4275 let encoded_tx_bytes = hex::decode(encoded_tx).unwrap();
4276 let tx = EIP1559Transaction::decode(&encoded_tx_bytes).unwrap();
4277 let expected_tx = EIP1559Transaction {
4278 nonce: 0,
4279 max_fee_per_gas: 78,
4280 max_priority_fee_per_gas: 17,
4281 to: TxKind::Call(Address::from_slice(
4282 &hex::decode("6177843db3138ae69679A54b95cf345ED759450d").unwrap(),
4283 )),
4284 value: 3000000000000000_u64.into(),
4285 data: Bytes::new(),
4286 signature_r: U256::from_str_radix(
4287 "151ccc02146b9b11adf516e6787b59acae3e76544fdcd75e77e67c6b598ce65d",
4288 16,
4289 )
4290 .unwrap(),
4291 signature_s: U256::from_str_radix(
4292 "64c5dd5aae2fbb535830ebbdad0234975cd7ece3562013b63ea18cc0df6c97d4",
4293 16,
4294 )
4295 .unwrap(),
4296 signature_y_parity: false,
4297 chain_id: 3151908,
4298 gas_limit: 63000,
4299 access_list: vec![],
4300 ..Default::default()
4301 };
4302 assert_eq!(tx, expected_tx);
4303 }
4304
4305 #[test]
4306 fn deserialize_tx_kind() {
4307 let tx_kind_create = r#""""#;
4308 let tx_kind_call = r#""0x6177843db3138ae69679A54b95cf345ED759450d""#;
4309 let deserialized_tx_kind_create = TxKind::Create;
4310 let deserialized_tx_kind_call = TxKind::Call(Address::from_slice(
4311 &hex::decode("6177843db3138ae69679A54b95cf345ED759450d").unwrap(),
4312 ));
4313 assert_eq!(
4314 deserialized_tx_kind_create,
4315 serde_json::from_str(tx_kind_create).unwrap()
4316 );
4317 assert_eq!(
4318 deserialized_tx_kind_call,
4319 serde_json::from_str(tx_kind_call).unwrap()
4320 )
4321 }
4322
4323 #[test]
4324 fn deserialize_tx_type() {
4325 let tx_type_eip2930 = r#""0x01""#;
4326 let tx_type_eip1559 = r#""0x02""#;
4327 let deserialized_tx_type_eip2930 = TxType::EIP2930;
4328 let deserialized_tx_type_eip1559 = TxType::EIP1559;
4329 assert_eq!(
4330 deserialized_tx_type_eip2930,
4331 serde_json::from_str(tx_type_eip2930).unwrap()
4332 );
4333 assert_eq!(
4334 deserialized_tx_type_eip1559,
4335 serde_json::from_str(tx_type_eip1559).unwrap()
4336 )
4337 }
4338
4339 #[test]
4340 fn deserialize_generic_transaction() {
4341 let generic_transaction = r#"{
4342 "type":"0x01",
4343 "nonce":"0x02",
4344 "to":"",
4345 "from":"0x6177843db3138ae69679A54b95cf345ED759450d",
4346 "gas":"0x5208",
4347 "value":"0x01",
4348 "input":"0x010203040506",
4349 "gasPrice":"0x07",
4350 "accessList": [
4351 {
4352 "address": "0x000f3df6d732807ef1319fb7b8bb8522d0beac02",
4353 "storageKeys": [
4354 "0x000000000000000000000000000000000000000000000000000000000000000c",
4355 "0x000000000000000000000000000000000000000000000000000000000000200b"
4356 ]
4357 }
4358 ]
4359 }"#;
4360 let deserialized_generic_transaction = GenericTransaction {
4361 r#type: TxType::EIP2930,
4362 nonce: Some(2),
4363 to: TxKind::Create,
4364 from: Address::from_slice(
4365 &hex::decode("6177843db3138ae69679A54b95cf345ED759450d").unwrap(),
4366 ),
4367 gas: Some(0x5208),
4368 value: U256::from(1),
4369 input: Bytes::from(hex::decode("010203040506").unwrap()),
4370 gas_price: U256::from(7),
4371 max_priority_fee_per_gas: Default::default(),
4372 max_fee_per_gas: Default::default(),
4373 max_fee_per_blob_gas: Default::default(),
4374 access_list: vec![AccessListEntry {
4375 address: Address::from_slice(
4376 &hex::decode("000f3df6d732807ef1319fb7b8bb8522d0beac02").unwrap(),
4377 ),
4378 storage_keys: vec![H256::from_low_u64_be(12), H256::from_low_u64_be(8203)],
4379 }],
4380 fee_token: None,
4381 blob_versioned_hashes: Default::default(),
4382 blobs: Default::default(),
4383 wrapper_version: None,
4384 chain_id: Default::default(),
4385 authorization_list: None,
4386 };
4387 assert_eq!(
4388 deserialized_generic_transaction,
4389 serde_json::from_str(generic_transaction).unwrap()
4390 )
4391 }
4392
4393 #[test]
4394 fn deserialize_generic_transaction_with_data_and_input_fields() {
4395 let generic_transaction = r#"{
4396 "type":"0x01",
4397 "nonce":"0x02",
4398 "to":"",
4399 "from":"0x6177843db3138ae69679A54b95cf345ED759450d",
4400 "gas":"0x5208",
4401 "value":"0x01",
4402 "input":"0x010203040506",
4403 "data":"0x010203040506",
4404 "gasPrice":"0x07",
4405 "accessList": [
4406 {
4407 "address": "0x000f3df6d732807ef1319fb7b8bb8522d0beac02",
4408 "storageKeys": [
4409 "0x000000000000000000000000000000000000000000000000000000000000000c",
4410 "0x000000000000000000000000000000000000000000000000000000000000200b"
4411 ]
4412 }
4413 ]
4414 }"#;
4415 let deserialized_generic_transaction = GenericTransaction {
4416 r#type: TxType::EIP2930,
4417 nonce: Some(2),
4418 to: TxKind::Create,
4419 from: Address::from_slice(
4420 &hex::decode("6177843db3138ae69679A54b95cf345ED759450d").unwrap(),
4421 ),
4422 gas: Some(0x5208),
4423 value: U256::from(1),
4424 input: Bytes::from(hex::decode("010203040506").unwrap()),
4425 gas_price: U256::from(7),
4426 max_priority_fee_per_gas: Default::default(),
4427 max_fee_per_gas: Default::default(),
4428 max_fee_per_blob_gas: Default::default(),
4429 access_list: vec![AccessListEntry {
4430 address: Address::from_slice(
4431 &hex::decode("000f3df6d732807ef1319fb7b8bb8522d0beac02").unwrap(),
4432 ),
4433 storage_keys: vec![H256::from_low_u64_be(12), H256::from_low_u64_be(8203)],
4434 }],
4435 fee_token: None,
4436 blob_versioned_hashes: Default::default(),
4437 blobs: Default::default(),
4438 wrapper_version: None,
4439 chain_id: Default::default(),
4440 authorization_list: None,
4441 };
4442 assert_eq!(
4443 deserialized_generic_transaction,
4444 serde_json::from_str(generic_transaction).unwrap()
4445 )
4446 }
4447
4448 #[test]
4449 fn deserialize_eip4844_transaction() {
4450 let eip4844_transaction = r#"{
4451 "chainId":"0x01",
4452 "nonce":"0x02",
4453 "maxPriorityFeePerGas":"0x01",
4454 "maxFeePerGas":"0x01",
4455 "gas":"0x5208",
4456 "to":"0x6177843db3138ae69679A54b95cf345ED759450d",
4457 "value":"0x01",
4458 "input":"0x3033",
4459 "accessList": [
4460 {
4461 "address": "0x000f3df6d732807ef1319fb7b8bb8522d0beac02",
4462 "storageKeys": [
4463 "0x000000000000000000000000000000000000000000000000000000000000000c",
4464 "0x000000000000000000000000000000000000000000000000000000000000200b"
4465 ]
4466 }
4467 ],
4468 "maxFeePerBlobGas":"0x03",
4469 "blobVersionedHashes": [
4470 "0x0000000000000000000000000000000000000000000000000000000000000001",
4471 "0x0000000000000000000000000000000000000000000000000000000000000002"
4472 ],
4473 "yParity":"0x0",
4474 "r": "0x01",
4475 "s": "0x02"
4476 }"#;
4477 let deserialized_eip4844_transaction = EIP4844Transaction {
4478 chain_id: 0x01,
4479 nonce: 0x02,
4480 to: Address::from_slice(
4481 &hex::decode("6177843db3138ae69679A54b95cf345ED759450d").unwrap(),
4482 ),
4483 max_priority_fee_per_gas: 1,
4484 max_fee_per_gas: 1,
4485 max_fee_per_blob_gas: U256::from(0x03),
4486 gas: 0x5208,
4487 value: U256::from(0x01),
4488 data: Bytes::from_static(b"03"),
4490 access_list: vec![(
4491 Address::from_slice(
4492 &hex::decode("000f3df6d732807ef1319fb7b8bb8522d0beac02").unwrap(),
4493 ),
4494 vec![H256::from_low_u64_be(12), H256::from_low_u64_be(8203)],
4495 )],
4496 blob_versioned_hashes: vec![H256::from_low_u64_be(1), H256::from_low_u64_be(2)],
4497 signature_y_parity: false,
4498 signature_r: U256::from(0x01),
4499 signature_s: U256::from(0x02),
4500 ..Default::default()
4501 };
4502
4503 assert_eq!(
4504 deserialized_eip4844_transaction,
4505 serde_json::from_str(eip4844_transaction).unwrap()
4506 )
4507 }
4508
4509 #[test]
4510 fn serialize_deserialize_transaction() {
4511 let eip1559 = EIP1559Transaction {
4512 chain_id: 65536999,
4513 nonce: 1,
4514 max_priority_fee_per_gas: 1000,
4515 max_fee_per_gas: 2000,
4516 gas_limit: 21000,
4517 to: TxKind::Call(H160::from_str("0x000a52D537c4150ec274dcE3962a0d179B7E71B0").unwrap()),
4518 value: U256::from(100000),
4519 data: Bytes::from_static(b"03"),
4520 access_list: vec![(
4521 H160::from_str("0x000a52D537c4150ec274dcE3962a0d179B7E71B3").unwrap(),
4522 vec![H256::zero()],
4523 )],
4524 signature_y_parity: true,
4525 signature_r: U256::one(),
4526 signature_s: U256::zero(),
4527 ..Default::default()
4528 };
4529 let tx_to_serialize = Transaction::EIP1559Transaction(eip1559.clone());
4530 let serialized = serde_json::to_string(&tx_to_serialize).expect("Failed to serialize");
4531
4532 let deserialized_tx: Transaction =
4533 serde_json::from_str(&serialized).expect("Failed to deserialize");
4534
4535 assert!(deserialized_tx.tx_type() == TxType::EIP1559);
4536
4537 if let Transaction::EIP1559Transaction(tx) = deserialized_tx {
4538 assert_eq!(tx, eip1559);
4539 }
4540 }
4541
4542 #[test]
4543 fn serialize_deserialize_eip7702transaction() {
4544 let eip7702 = EIP7702Transaction {
4545 chain_id: 65536999,
4546 nonce: 1,
4547 max_priority_fee_per_gas: 1000,
4548 max_fee_per_gas: 2000,
4549 gas_limit: 21000,
4550 to: Address::from_str("0x000a52D537c4150ec274dcE3962a0d179B7E71B0").unwrap(),
4551 value: U256::from(100000),
4552 data: Bytes::from_static(b"03"),
4553 access_list: vec![],
4554 signature_y_parity: true,
4555 signature_r: U256::one(),
4556 signature_s: U256::zero(),
4557 authorization_list: vec![AuthorizationTuple {
4558 chain_id: U256::from(65536999),
4559 address: H160::from_str("0x000a52D537c4150ec274dcE3962a0d179B7E71B1").unwrap(),
4560 nonce: 2,
4561 y_parity: U256::one(),
4562 r_signature: U256::from(22),
4563 s_signature: U256::from(37),
4564 }],
4565 ..Default::default()
4566 };
4567 let tx_to_serialize = Transaction::EIP7702Transaction(eip7702.clone());
4568 let serialized = serde_json::to_string(&tx_to_serialize).expect("Failed to serialize");
4569
4570 let deserialized_tx: Transaction =
4571 serde_json::from_str(&serialized).expect("Failed to deserialize");
4572
4573 assert!(deserialized_tx.tx_type() == TxType::EIP7702);
4574
4575 if let Transaction::EIP7702Transaction(tx) = deserialized_tx {
4576 assert_eq!(tx, eip7702);
4577 }
4578 }
4579
4580 #[test]
4581 fn serialize_deserialize_privileged_l2_transaction() -> Result<(), RLPDecodeError> {
4582 let privileged_l2 = PrivilegedL2Transaction {
4583 chain_id: 65536999,
4584 nonce: 0,
4585 max_priority_fee_per_gas: 875000000,
4586 max_fee_per_gas: 875000000,
4587 gas_limit: 42000u64,
4588 to: TxKind::Call(
4589 Address::from_str("0x8943545177806ed17b9f23f0a21ee5948ecaa776").unwrap(),
4590 ),
4591 value: U256::from(500000000000000000000000000u128),
4592 data: Bytes::new(),
4593 access_list: vec![],
4594 from: Address::from_str("0x8943545177806ed17b9f23f0a21ee5948ecaa776").unwrap(),
4595 ..Default::default()
4596 };
4597
4598 let encoded = PrivilegedL2Transaction::encode_to_vec(&privileged_l2);
4599 println!("encoded length: {}", encoded.len());
4600 assert_eq!(encoded.len(), privileged_l2.length());
4601
4602 let deserialized_tx = PrivilegedL2Transaction::decode(&encoded)?;
4603
4604 assert_eq!(deserialized_tx, privileged_l2);
4605
4606 Ok(())
4607 }
4608
4609 #[test]
4610 fn test_legacy_transaction_into_generic() {
4611 let legacy_tx = LegacyTransaction {
4612 nonce: 1,
4613 gas_price: U256::from(20_000_000_000u64),
4614 gas: 21000,
4615 to: TxKind::Call(
4616 Address::from_str("0x742d35Cc6634C0532925a3b844Bc454e4438f44e").unwrap(),
4617 ),
4618 value: U256::from(1_000_000_000_000_000_000u64),
4619 data: Bytes::default(),
4620 v: U256::from(27),
4621 r: U256::from(1),
4622 s: U256::from(1),
4623 ..Default::default()
4624 };
4625
4626 let generic_tx: GenericTransaction = legacy_tx.into();
4627 assert_eq!(generic_tx.r#type, TxType::Legacy);
4628 assert_eq!(generic_tx.nonce, Some(1));
4629 assert_eq!(generic_tx.gas_price, U256::from(20_000_000_000u64));
4630 assert_eq!(generic_tx.gas, Some(21000));
4631 assert_eq!(generic_tx.max_priority_fee_per_gas, None);
4632 assert_eq!(generic_tx.max_fee_per_gas, None);
4633 assert_eq!(generic_tx.access_list.len(), 0);
4634 assert_eq!(generic_tx.chain_id, None);
4635 }
4636
4637 #[test]
4638 fn test_eip2930_transaction_into_generic() {
4639 let access_list = vec![(
4640 Address::from_str("0x742d35Cc6634C0532925a3b844Bc454e4438f44e").unwrap(),
4641 vec![
4642 H256::from_str(
4643 "0x1234567890123456789012345678901234567890123456789012345678901234",
4644 )
4645 .unwrap(),
4646 ],
4647 )];
4648
4649 let eip2930_tx = EIP2930Transaction {
4650 chain_id: 1,
4651 nonce: 1,
4652 gas_price: U256::from(20_000_000_000u64),
4653 gas_limit: 21000,
4654 to: TxKind::Call(
4655 Address::from_str("0x742d35Cc6634C0532925a3b844Bc454e4438f44e").unwrap(),
4656 ),
4657 value: U256::from(1_000_000_000_000_000_000u64),
4658 data: Bytes::default(),
4659 access_list: access_list.clone(),
4660 signature_y_parity: false,
4661 signature_r: U256::from(1),
4662 signature_s: U256::from(1),
4663 ..Default::default()
4664 };
4665
4666 let generic_tx: GenericTransaction = eip2930_tx.into();
4667 assert_eq!(generic_tx.r#type, TxType::EIP2930);
4668 assert_eq!(generic_tx.nonce, Some(1));
4669 assert_eq!(generic_tx.gas_price, U256::from(20_000_000_000u64));
4670 assert_eq!(generic_tx.gas, Some(21000));
4671 assert_eq!(generic_tx.max_priority_fee_per_gas, None);
4672 assert_eq!(generic_tx.max_fee_per_gas, None);
4673 assert_eq!(generic_tx.chain_id, Some(1));
4674 assert_eq!(generic_tx.access_list.len(), 1);
4675 assert_eq!(generic_tx.access_list[0].address, access_list[0].0);
4676 assert_eq!(generic_tx.access_list[0].storage_keys, access_list[0].1);
4677 }
4678
4679 #[test]
4680 fn recover_address_rejects_high_s_signatures() {
4681 use ethrex_crypto::NativeCrypto;
4682 use k256::ecdsa::SigningKey;
4683
4684 let crypto = NativeCrypto;
4685
4686 let private_key = hex!("4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318");
4689 let signing_key = SigningKey::from_bytes(&private_key.into()).expect("Valid private key");
4690
4691 let msg = b"Test message for high-s signature rejection";
4693 let payload = keccak(msg).to_fixed_bytes();
4695
4696 let (signature, recovery_id) = signing_key
4699 .sign_prehash_recoverable(&payload)
4700 .expect("Signing failed");
4701
4702 let mut sig_bytes = [0u8; 65];
4704 sig_bytes[..64].copy_from_slice(&signature.to_bytes());
4705 sig_bytes[64] = recovery_id.to_byte();
4706
4707 let uncompressed_pub = signing_key.verifying_key().to_encoded_point(false);
4710 let pub_hash = ethrex_crypto::keccak::keccak_hash(&uncompressed_pub.as_bytes()[1..]);
4711 let expected_address = Address::from_slice(&pub_hash[12..]);
4712
4713 let recovered = crypto
4714 .recover_signer(&sig_bytes, &payload)
4715 .expect("Valid low-s signature should recover successfully");
4716 assert_eq!(recovered, expected_address, "Recovered address mismatch");
4717
4718 let n = U256::from_big_endian(&hex!(
4721 "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"
4722 ));
4723 let s = U256::from_big_endian(&sig_bytes[32..64]);
4724
4725 let half_n = n / 2;
4727 assert!(
4728 s <= half_n,
4729 "Generated signature was not low-s, cannot test high-s rejection"
4730 );
4731
4732 let s_high = n - s;
4734
4735 let mut sig_high_bytes = sig_bytes;
4736 sig_high_bytes[32..64].copy_from_slice(&s_high.to_big_endian());
4738 sig_high_bytes[64] ^= 1;
4740
4741 assert!(
4744 crypto.recover_signer(&sig_high_bytes, &payload).is_err(),
4745 "High-s signature should be rejected (EIP-2 compliance)"
4746 );
4747 }
4748
4749 #[test]
4750 fn encode_decode_low_size_tx() {
4751 let tx = Transaction::EIP2930Transaction(EIP2930Transaction::default());
4752 let tx_to_encode = tx.clone();
4755 let encoded = tx_to_encode.encode_to_vec();
4756 let decoded_tx = Transaction::decode(&encoded).unwrap();
4757 assert_eq!(tx, decoded_tx);
4758 }
4759
4760 #[test]
4761 fn test_eip1559_simple_transfer_size() {
4762 let tx = Transaction::EIP1559Transaction(EIP1559Transaction::default());
4763 assert_eq!(tx.encode_to_vec().len(), EIP1559_DEFAULT_SERIALIZED_LENGTH);
4764 }
4765
4766 fn make_test_frame_tx() -> FrameTransaction {
4769 FrameTransaction {
4770 chain_id: 1,
4771 nonce: 42,
4772 sender: Address::from_low_u64_be(0xABCD),
4773 frames: vec![
4774 Frame {
4775 mode: FrameMode::Verify as u8,
4776 flags: 0x03, target: Some(Address::from_low_u64_be(0xABCD)),
4778 gas_limit: 100_000,
4779 value: U256::zero(),
4780 data: Bytes::from_static(b"verify_data"),
4781 },
4782 Frame {
4783 mode: FrameMode::Sender as u8,
4784 flags: 0x00,
4785 target: Some(Address::from_low_u64_be(0x1234)),
4786 gas_limit: 200_000,
4787 value: U256::zero(),
4788 data: Bytes::from_static(b"call_data"),
4789 },
4790 ],
4791 signatures: vec![FrameSignature {
4792 scheme: FRAME_SIG_SCHEME_SECP256K1,
4793 signer: Address::from_low_u64_be(0xABCD),
4794 msg: Bytes::new(),
4795 signature: Bytes::from(vec![0u8; 65]),
4796 }],
4797 max_priority_fee_per_gas: 1_000_000_000,
4798 max_fee_per_gas: 30_000_000_000,
4799 max_fee_per_blob_gas: U256::zero(),
4800 blob_versioned_hashes: vec![],
4801 inner_hash: OnceCell::new(),
4802 cached_canonical: OnceCell::new(),
4803 }
4804 }
4805
4806 #[test]
4807 fn atomic_batch_flag_valid_on_default_and_verify_frames() {
4808 let mut tx = make_test_frame_tx();
4810 tx.frames = vec![
4811 Frame {
4812 mode: FrameMode::Default as u8,
4813 flags: 0x04, target: Some(Address::from_low_u64_be(0xB0B)),
4815 gas_limit: 21_000,
4816 value: U256::zero(),
4817 data: Bytes::new(),
4818 },
4819 Frame {
4820 mode: FrameMode::Verify as u8,
4821 flags: 0x04 | 0x03, target: None,
4823 gas_limit: 21_000,
4824 value: U256::zero(),
4825 data: Bytes::new(),
4826 },
4827 Frame {
4828 mode: FrameMode::Sender as u8,
4829 flags: 0x00, target: Some(Address::from_low_u64_be(0xCAFE)),
4831 gas_limit: 21_000,
4832 value: U256::zero(),
4833 data: Bytes::new(),
4834 },
4835 ];
4836 assert!(tx.validate_static_constraints().is_ok());
4837 }
4838
4839 #[test]
4840 fn atomic_batch_flag_on_last_frame_still_invalid() {
4841 let mut tx = make_test_frame_tx();
4842 tx.frames = vec![Frame {
4843 mode: FrameMode::Sender as u8,
4844 flags: 0x04,
4845 target: Some(Address::from_low_u64_be(0xCAFE)),
4846 gas_limit: 21_000,
4847 value: U256::zero(),
4848 data: Bytes::new(),
4849 }];
4850 let err = tx.validate_static_constraints().unwrap_err();
4851 assert!(err.contains("atomic batch flag on last frame"), "{err}");
4852 }
4853
4854 #[test]
4855 fn test_frame_rlp_roundtrip() {
4856 let frame = Frame {
4857 mode: FrameMode::Verify as u8,
4858 flags: 0x03,
4859 target: Some(Address::from_low_u64_be(0x1234)),
4860 gas_limit: 50_000,
4861 value: U256::zero(),
4862 data: Bytes::from_static(b"hello"),
4863 };
4864 let encoded = frame.encode_to_vec();
4865 let decoded = Frame::decode(&encoded).unwrap();
4866 assert_eq!(frame, decoded);
4867 }
4868
4869 #[test]
4870 fn test_frame_null_target_rlp_roundtrip() {
4871 let frame = Frame {
4872 mode: FrameMode::Default as u8,
4873 flags: 0x00,
4874 target: None,
4875 gas_limit: 10_000,
4876 value: U256::zero(),
4877 data: Bytes::from_static(b"deploy"),
4878 };
4879 let encoded = frame.encode_to_vec();
4880 let decoded = Frame::decode(&encoded).unwrap();
4881 assert_eq!(frame, decoded);
4882 }
4883
4884 #[test]
4885 fn test_frame_mode_with_flags_rlp_roundtrip() {
4886 for mode_val in [0u8, 1, 2] {
4888 let frame = Frame {
4889 mode: mode_val,
4890 flags: if mode_val == 1 { 0x03 } else { 0x00 },
4891 target: Some(Address::from_low_u64_be(0x1234)),
4892 gas_limit: 50_000,
4893 value: U256::zero(),
4894 data: Bytes::new(),
4895 };
4896 let encoded = frame.encode_to_vec();
4897 let decoded = Frame::decode(&encoded).unwrap();
4898 assert_eq!(frame, decoded);
4899 }
4900 let frame = Frame {
4902 mode: 2, flags: 0x01 | 0x04, target: Some(Address::from_low_u64_be(0x1234)),
4905 gas_limit: 50_000,
4906 value: U256::zero(),
4907 data: Bytes::new(),
4908 };
4909 assert_eq!(frame.execution_mode(), FrameMode::Sender);
4910 assert_eq!(frame.scope_restriction(), 1);
4911 assert!(frame.is_atomic_batch());
4912 let encoded = frame.encode_to_vec();
4913 let decoded = Frame::decode(&encoded).unwrap();
4914 assert_eq!(frame, decoded);
4915 }
4916
4917 #[test]
4918 fn frame_rlp_roundtrip_preserves_value() {
4919 let frame = Frame {
4920 mode: FrameMode::Sender as u8,
4921 flags: 0x00,
4922 target: Some(Address::from_low_u64_be(0xCAFE)),
4923 gas_limit: 100_000,
4924 value: U256::from(1_000_000_000_000_000u64), data: Bytes::from_static(b"hello"),
4926 };
4927 let encoded = frame.encode_to_vec();
4928 let decoded = Frame::decode(&encoded).unwrap();
4929 assert_eq!(frame, decoded);
4930 assert_eq!(decoded.value, U256::from(1_000_000_000_000_000u64));
4931 }
4932
4933 #[test]
4934 fn test_frame_transaction_rlp_roundtrip() {
4935 let tx = make_test_frame_tx();
4936 let encoded = tx.encode_to_vec();
4937 let decoded = FrameTransaction::decode(&encoded).unwrap();
4938 assert_eq!(tx, decoded);
4939 }
4940
4941 #[test]
4942 fn test_frame_transaction_canonical_roundtrip() {
4943 let tx = Transaction::FrameTransaction(make_test_frame_tx());
4944 let encoded = tx.encode_canonical_to_vec();
4945 let decoded = Transaction::decode_canonical(&encoded).unwrap();
4946 assert_eq!(
4948 tx.hash(ðrex_crypto::NativeCrypto),
4949 decoded.hash(ðrex_crypto::NativeCrypto)
4950 );
4951 assert_eq!(tx.tx_type(), decoded.tx_type());
4953 assert_eq!(tx.nonce(), decoded.nonce());
4954 assert_eq!(tx.chain_id(), decoded.chain_id());
4955 }
4956
4957 #[test]
4958 fn test_frame_transaction_sig_hash_covers_all_frame_data() {
4959 let tx = make_test_frame_tx();
4961 let hash1 = tx.compute_sig_hash();
4962
4963 let mut tx2 = tx.clone();
4965 tx2.frames[0].data = Bytes::from_static(b"completely_different_verify_data");
4966 let hash2 = tx2.compute_sig_hash();
4967 assert_ne!(hash1, hash2);
4968
4969 let mut tx3 = tx;
4971 tx3.frames[1].data = Bytes::from_static(b"different_call_data");
4972 let hash3 = tx3.compute_sig_hash();
4973 assert_ne!(hash1, hash3);
4974 }
4975
4976 #[test]
4977 fn test_frame_transaction_accessor_methods() {
4978 let frame_tx = make_test_frame_tx();
4979 let tx = Transaction::FrameTransaction(frame_tx);
4980
4981 assert_eq!(tx.tx_type(), TxType::Frame);
4982 assert_eq!(tx.nonce(), 42);
4983 assert_eq!(tx.chain_id(), Some(1));
4984 assert_eq!(tx.to(), TxKind::Call(Address::from_low_u64_be(0xABCD)));
4985 assert_eq!(tx.value(), U256::zero());
4986 assert_eq!(tx.data(), &Bytes::new());
4987 assert!(tx.access_list().is_empty());
4988 assert!(tx.authorization_list().is_none());
4989 assert_eq!(tx.max_priority_fee(), Some(1_000_000_000));
4990 assert_eq!(tx.max_fee_per_gas(), Some(30_000_000_000));
4991 assert_eq!(tx.max_fee_per_blob_gas(), None); assert!(!tx.is_contract_creation());
4993 assert_eq!(
4995 tx.sender(ðrex_crypto::NativeCrypto).unwrap(),
4996 Address::from_low_u64_be(0xABCD)
4997 );
4998 assert!(tx.gas_limit() >= 300_000); }
5001
5002 #[test]
5003 fn test_frame_transaction_hash_uses_oncecell() {
5004 let tx = Transaction::FrameTransaction(make_test_frame_tx());
5005 let h1 = tx.hash(ðrex_crypto::NativeCrypto);
5006 let h2 = tx.hash(ðrex_crypto::NativeCrypto);
5007 assert_eq!(h1, h2);
5008 }
5009
5010 #[test]
5011 fn frame_transaction_rlp_roundtrip_preserves_fields() {
5012 let tx = make_test_frame_tx();
5013 let mut buf = Vec::new();
5014 tx.encode(&mut buf);
5015 let (decoded, rest) = FrameTransaction::decode_unfinished(&buf).unwrap();
5016 assert!(rest.is_empty());
5017 assert_eq!(decoded.chain_id, tx.chain_id);
5018 assert_eq!(decoded.nonce, tx.nonce);
5019 assert_eq!(decoded.sender, tx.sender);
5020 }
5021
5022 #[test]
5023 fn frame_transaction_variant_is_exposed_on_transaction_enum() {
5024 let tx = Transaction::FrameTransaction(make_test_frame_tx());
5025 assert!(matches!(tx, Transaction::FrameTransaction(_)));
5026 }
5027
5028 #[test]
5029 fn frame_transaction_serializes_signatures_as_array() {
5030 let tx = make_test_frame_tx();
5032 let json = serde_json::to_value(&tx).unwrap();
5033 let sigs = json
5034 .get("signatures")
5035 .expect("signatures field present")
5036 .as_array()
5037 .expect("signatures serialized as an array");
5038 assert_eq!(sigs.len(), tx.signatures.len());
5039 assert_eq!(sigs[0].get("scheme").unwrap(), "0x0");
5040 assert!(sigs[0].get("signer").is_some());
5041 assert!(sigs[0].get("signature").is_some());
5042 assert!(sigs[0].get("msg").is_some());
5043 }
5044
5045 fn make_frame_tx_with_gas_limits(limits: Vec<u64>) -> FrameTransaction {
5046 let frames = limits
5047 .into_iter()
5048 .map(|gl| Frame {
5049 mode: FrameMode::Sender as u8,
5050 flags: 0x00,
5051 target: Some(Address::from_low_u64_be(0x1234)),
5052 gas_limit: gl,
5053 value: U256::zero(),
5054 data: Bytes::new(),
5055 })
5056 .collect();
5057 FrameTransaction {
5058 chain_id: 1,
5059 nonce: 0,
5060 sender: Address::from_low_u64_be(0xABCD),
5061 frames,
5062 signatures: vec![],
5063 max_priority_fee_per_gas: 1_000_000_000,
5064 max_fee_per_gas: 30_000_000_000,
5065 max_fee_per_blob_gas: U256::zero(),
5066 blob_versioned_hashes: vec![],
5067 inner_hash: OnceCell::new(),
5068 cached_canonical: OnceCell::new(),
5069 }
5070 }
5071
5072 #[test]
5073 fn per_frame_gas_limit_above_i64_max_is_rejected() {
5074 let tx = make_frame_tx_with_gas_limits(vec![(i64::MAX as u64) + 1]);
5075 let err = tx.validate_static_constraints().unwrap_err();
5076 assert!(err.contains("exceeds 2**63-1"), "unexpected error: {err}");
5077 }
5078
5079 #[test]
5080 fn cumulative_frame_gas_limit_above_i64_max_is_rejected() {
5081 let half = (i64::MAX as u64) / 2 + 1;
5082 let tx = make_frame_tx_with_gas_limits(vec![half, half]);
5083 let err = tx.validate_static_constraints().unwrap_err();
5084 assert!(err.contains("cumulative"), "unexpected error: {err}");
5085 }
5086
5087 #[test]
5088 fn cumulative_frame_gas_limit_equal_to_i64_max_is_accepted() {
5089 let a = (i64::MAX as u64) / 2;
5090 let b = i64::MAX as u64 - a;
5091 let tx = make_frame_tx_with_gas_limits(vec![a, b]);
5092 tx.validate_static_constraints()
5093 .expect("exact i64::MAX total should be accepted");
5094 }
5095
5096 #[test]
5097 fn empty_frames_list_is_rejected_by_count_check_not_gas_check() {
5098 let tx = make_frame_tx_with_gas_limits(vec![]);
5101 let err = tx.validate_static_constraints().unwrap_err();
5102 assert!(err.contains("between 1 and"), "unexpected error: {err}");
5103 }
5104
5105 #[test]
5106 fn sig_hash_covers_frame_value() {
5107 let tx = make_test_frame_tx();
5110 let baseline = tx.compute_sig_hash();
5111
5112 let mut with_sender_value = tx.clone();
5113 with_sender_value.frames[1].value = U256::from(1u64);
5114 assert_ne!(
5115 baseline,
5116 with_sender_value.compute_sig_hash(),
5117 "sig_hash must change when a SENDER frame's value changes"
5118 );
5119
5120 let mut with_verify_value = tx.clone();
5121 with_verify_value.frames[0].value = U256::from(1u64);
5122 assert_ne!(
5123 baseline,
5124 with_verify_value.compute_sig_hash(),
5125 "sig_hash must change when a VERIFY frame's value changes"
5126 );
5127
5128 let mut with_verify_data = tx;
5130 with_verify_data.frames[0].data = Bytes::from_static(b"different_verify_data");
5131 assert_ne!(
5132 baseline,
5133 with_verify_data.compute_sig_hash(),
5134 "VERIFY.data is now covered by sig_hash (no longer elided)"
5135 );
5136 }
5137
5138 #[test]
5139 fn validate_static_constraints_rejects_nonzero_value_on_non_sender_frames() {
5140 let verify_tx = FrameTransaction {
5142 chain_id: 1,
5143 nonce: 0,
5144 sender: Address::from_low_u64_be(0xABCD),
5145 frames: vec![Frame {
5146 mode: FrameMode::Verify as u8,
5147 flags: 0x01,
5148 target: None,
5149 gas_limit: 50_000,
5150 value: U256::from(1u64),
5151 data: Bytes::new(),
5152 }],
5153 signatures: vec![],
5154 max_priority_fee_per_gas: 1_000_000_000,
5155 max_fee_per_gas: 30_000_000_000,
5156 max_fee_per_blob_gas: U256::zero(),
5157 blob_versioned_hashes: vec![],
5158 inner_hash: OnceCell::new(),
5159 cached_canonical: OnceCell::new(),
5160 };
5161 let err = verify_tx.validate_static_constraints().unwrap_err();
5162 assert!(
5163 err.contains("non-zero value only allowed in SENDER mode"),
5164 "unexpected error for VERIFY: {err}"
5165 );
5166
5167 let default_tx = FrameTransaction {
5169 frames: vec![Frame {
5170 mode: FrameMode::Default as u8,
5171 flags: 0x00,
5172 target: Some(Address::from_low_u64_be(0x1234)),
5173 gas_limit: 50_000,
5174 value: U256::from(1u64),
5175 data: Bytes::new(),
5176 }],
5177 ..verify_tx
5178 };
5179 let err = default_tx.validate_static_constraints().unwrap_err();
5180 assert!(
5181 err.contains("non-zero value only allowed in SENDER mode"),
5182 "unexpected error for DEFAULT: {err}"
5183 );
5184
5185 let sender_tx = FrameTransaction {
5187 frames: vec![Frame {
5188 mode: FrameMode::Sender as u8,
5189 flags: 0x00,
5190 target: Some(Address::from_low_u64_be(0x1234)),
5191 gas_limit: 50_000,
5192 value: U256::from(1u64),
5193 data: Bytes::new(),
5194 }],
5195 ..default_tx
5196 };
5197 sender_tx
5198 .validate_static_constraints()
5199 .expect("SENDER frames may carry non-zero value");
5200 }
5201
5202 fn chain_config_with_hegota(hegota_time: Option<u64>) -> crate::types::ChainConfig {
5205 crate::types::ChainConfig {
5206 hegota_time,
5207 ..Default::default()
5208 }
5209 }
5210
5211 #[test]
5212 fn test_frame_tx_pre_fork_chain_config_rejects() {
5213 let cfg = chain_config_with_hegota(None);
5214 assert!(!cfg.is_hegota_activated(0));
5215 assert!(!cfg.is_hegota_activated(u64::MAX));
5216 }
5217
5218 #[test]
5219 fn test_frame_tx_post_fork_admits() {
5220 let cfg = chain_config_with_hegota(Some(1000));
5221 assert!(cfg.is_hegota_activated(2000));
5222 }
5223
5224 #[test]
5225 fn test_frame_tx_fork_boundary_admits() {
5226 let activation_time = 1_700_000_000u64;
5227 let cfg = chain_config_with_hegota(Some(activation_time));
5228 assert!(cfg.is_hegota_activated(activation_time));
5229 assert!(!cfg.is_hegota_activated(activation_time - 1));
5230 }
5231
5232 #[test]
5233 fn test_frame_tx_devnet_fork_epoch_zero_admits() {
5234 let cfg = chain_config_with_hegota(Some(0));
5235 assert!(cfg.is_hegota_activated(0));
5236 assert!(cfg.is_hegota_activated(1));
5237 assert!(cfg.is_hegota_activated(u64::MAX));
5238 }
5239
5240 #[test]
5241 fn test_frame_tx_pre_fork_rlp_roundtrip_still_decodes() {
5242 let tx = make_test_frame_tx();
5245 let mut buf = Vec::new();
5246 tx.encode(&mut buf);
5247 let (decoded, rest) = FrameTransaction::decode_unfinished(&buf).unwrap();
5248 assert!(rest.is_empty());
5249 assert_eq!(decoded.chain_id, tx.chain_id);
5250 assert_eq!(decoded.nonce, tx.nonce);
5251 assert_eq!(decoded.sender, tx.sender);
5252 }
5253
5254 fn expiry_frame(deadline: u64) -> Frame {
5257 Frame {
5258 mode: FrameMode::Verify as u8,
5259 flags: 0x00,
5260 target: Some(frame_tx_expiry_verifier()),
5261 gas_limit: 30_000,
5262 value: U256::zero(),
5263 data: Bytes::copy_from_slice(&deadline.to_be_bytes()),
5264 }
5265 }
5266
5267 #[test]
5268 fn expiry_verifier_frame_passes_static_validation() {
5269 let mut tx = make_test_frame_tx();
5270 tx.frames.insert(0, expiry_frame(1_700_000_000));
5271 assert!(tx.validate_static_constraints().is_ok());
5272 }
5273
5274 #[test]
5275 fn expiry_verifier_frame_rejects_bad_data_length() {
5276 let mut tx = make_test_frame_tx();
5277 let mut f = expiry_frame(0);
5278 f.data = Bytes::from_static(b"short");
5279 tx.frames.insert(0, f);
5280 let err = tx.validate_static_constraints().unwrap_err();
5281 assert!(err.contains("8 bytes"), "{err}");
5282 }
5283
5284 #[test]
5285 fn expiry_verifier_frame_rejects_nonzero_flags() {
5286 let mut tx = make_test_frame_tx();
5287 let mut f = expiry_frame(0);
5288 f.flags = 0x01;
5289 tx.frames.insert(0, f);
5290 let err = tx.validate_static_constraints().unwrap_err();
5291 assert!(err.contains("flags == 0"), "{err}");
5292 }
5293
5294 #[test]
5295 fn at_most_one_expiry_verifier_frame() {
5296 let mut tx = make_test_frame_tx();
5297 tx.frames.insert(0, expiry_frame(1));
5298 tx.frames.insert(0, expiry_frame(2));
5299 let err = tx.validate_static_constraints().unwrap_err();
5300 assert!(err.contains("more than one expiry"), "{err}");
5301 }
5302
5303 #[test]
5304 fn verify_frame_with_zero_scope_is_now_statically_valid() {
5305 let mut tx = make_test_frame_tx();
5310 tx.frames[0].flags = 0x00;
5311 assert!(tx.validate_static_constraints().is_ok());
5312 }
5313
5314 #[test]
5315 fn sig_hash_covers_expiry_deadline_and_all_verify_data() {
5316 let mut tx_a = make_test_frame_tx();
5318 tx_a.frames.insert(0, expiry_frame(100));
5319 let mut tx_b = make_test_frame_tx();
5320 tx_b.frames.insert(0, expiry_frame(200));
5321 assert_ne!(tx_a.compute_sig_hash(), tx_b.compute_sig_hash());
5323
5324 let tx_c = make_test_frame_tx();
5326 let mut tx_d = make_test_frame_tx();
5327 tx_d.frames[0].data = Bytes::from_static(b"different_verify_data");
5328 assert_ne!(tx_c.compute_sig_hash(), tx_d.compute_sig_hash());
5329 }
5330
5331 #[test]
5332 fn expiry_deadline_parses_or_none() {
5333 let mut tx = make_test_frame_tx();
5334 assert_eq!(tx.expiry_deadline(), None);
5335 tx.frames.insert(0, expiry_frame(1_700_000_123));
5336 assert_eq!(tx.expiry_deadline(), Some(1_700_000_123));
5337 }
5338
5339 #[test]
5340 fn signature_rlp_roundtrip() {
5341 let sig = FrameSignature {
5342 scheme: FRAME_SIG_SCHEME_P256,
5343 signer: Address::from_low_u64_be(0x1234),
5344 msg: Bytes::from(vec![7u8; 32]),
5345 signature: Bytes::from(vec![9u8; 128]),
5346 };
5347 let mut buf = Vec::new();
5348 sig.encode(&mut buf);
5349 let (decoded, rest) = FrameSignature::decode_unfinished(&buf).unwrap();
5350 assert!(rest.is_empty());
5351 assert_eq!(decoded, sig);
5352 }
5353
5354 #[test]
5355 fn frame_tx_with_signatures_rlp_roundtrip() {
5356 let tx = make_test_frame_tx();
5357 assert!(!tx.signatures.is_empty());
5358 let mut buf = Vec::new();
5359 tx.encode(&mut buf);
5360 let (decoded, rest) = FrameTransaction::decode_unfinished(&buf).unwrap();
5361 assert!(rest.is_empty());
5362 assert_eq!(decoded.signatures, tx.signatures);
5363 assert_eq!(decoded.frames, tx.frames);
5364 assert_eq!(decoded.nonce, tx.nonce);
5365 }
5366
5367 #[test]
5368 fn p2p_frame_transaction_rlp_roundtrip() {
5369 let ft = make_test_frame_tx();
5372 assert!(!ft.signatures.is_empty());
5373 assert!(!ft.frames.is_empty());
5374 let original = P2PTransaction::FrameTransaction(ft);
5375
5376 let encoded = original.encode_to_vec();
5377 let (decoded, rest) = P2PTransaction::decode_unfinished(&encoded).unwrap();
5378 assert!(rest.is_empty());
5379 assert_eq!(decoded, original);
5380 assert_eq!(decoded.tx_type(), TxType::Frame);
5381
5382 let as_tx: Transaction = decoded.try_into().unwrap();
5385 assert!(matches!(as_tx, Transaction::FrameTransaction(_)));
5386 }
5387
5388 #[test]
5389 fn static_validation_rejects_unknown_scheme() {
5390 let mut tx = make_test_frame_tx();
5391 tx.signatures[0].scheme = 2;
5392 assert!(
5393 tx.validate_static_constraints()
5394 .unwrap_err()
5395 .contains("unsupported scheme"),
5396 );
5397 }
5398
5399 #[test]
5400 fn static_validation_rejects_bad_msg_length() {
5401 let mut tx = make_test_frame_tx();
5402 tx.signatures[0].msg = Bytes::from(vec![1u8; 16]);
5403 assert!(
5404 tx.validate_static_constraints()
5405 .unwrap_err()
5406 .contains("32 bytes"),
5407 );
5408 }
5409
5410 #[test]
5411 fn static_validation_rejects_zero_explicit_msg() {
5412 let mut tx = make_test_frame_tx();
5413 tx.signatures[0].msg = Bytes::from(vec![0u8; 32]);
5414 assert!(
5415 tx.validate_static_constraints()
5416 .unwrap_err()
5417 .contains("zero digest"),
5418 );
5419 }
5420
5421 #[test]
5422 fn sig_hash_elides_empty_msg_signature_bytes() {
5423 let mut a = make_test_frame_tx();
5424 let mut b = make_test_frame_tx();
5425 assert!(a.signatures[0].msg.is_empty());
5427 a.signatures[0].signature = Bytes::from(vec![1u8; 65]);
5428 b.signatures[0].signature = Bytes::from(vec![2u8; 65]);
5429 assert_eq!(a.compute_sig_hash(), b.compute_sig_hash());
5430 }
5431
5432 #[test]
5433 fn sig_hash_covers_frame_data_now() {
5434 let mut a = make_test_frame_tx();
5435 let mut b = make_test_frame_tx();
5436 a.frames[0].data = Bytes::from_static(b"aaaa");
5438 b.frames[0].data = Bytes::from_static(b"bbbb");
5439 assert_ne!(a.compute_sig_hash(), b.compute_sig_hash());
5440 }
5441
5442 #[test]
5443 fn sig_hash_covers_explicit_msg_signature_bytes() {
5444 let mut a = make_test_frame_tx();
5445 let mut b = make_test_frame_tx();
5446 a.signatures[0].msg = Bytes::from(vec![9u8; 32]);
5447 b.signatures[0].msg = Bytes::from(vec![9u8; 32]);
5448 a.signatures[0].signature = Bytes::from(vec![1u8; 65]);
5449 b.signatures[0].signature = Bytes::from(vec![2u8; 65]);
5450 assert_ne!(a.compute_sig_hash(), b.compute_sig_hash());
5452 }
5453
5454 #[test]
5455 fn total_gas_limit_includes_signature_costs() {
5456 let mut tx = make_test_frame_tx();
5457 let base = tx.total_gas_limit();
5458 tx.signatures.push(FrameSignature {
5460 scheme: FRAME_SIG_SCHEME_P256,
5461 signer: Address::from_low_u64_be(1),
5462 msg: Bytes::new(),
5463 signature: Bytes::from(vec![0u8; 128]),
5464 });
5465 assert!(tx.total_gas_limit() >= base + 6700);
5466 assert_eq!(tx.signature_verification_cost(), 2800 + 6700);
5467 }
5468
5469 #[test]
5470 fn golden_frame_tx_rlp_and_sig_hash() {
5471 let tx = FrameTransaction {
5476 chain_id: 1,
5477 nonce: 7,
5478 sender: Address::from_low_u64_be(0xABCD),
5479 frames: vec![
5480 Frame {
5481 mode: 1,
5482 flags: 3,
5483 target: None,
5484 gas_limit: 0x5208,
5485 value: U256::zero(),
5486 data: Bytes::from_static(&[0x11, 0x22]),
5487 },
5488 Frame {
5489 mode: 2,
5490 flags: 0,
5491 target: Some(Address::from_low_u64_be(0x1234)),
5492 gas_limit: 0x9c40,
5493 value: U256::zero(),
5494 data: Bytes::new(),
5495 },
5496 ],
5497 signatures: vec![FrameSignature {
5498 scheme: FRAME_SIG_SCHEME_SECP256K1,
5499 signer: Address::from_low_u64_be(0xABCD),
5500 msg: Bytes::new(),
5501 signature: Bytes::from(vec![0x01u8; 65]),
5502 }],
5503 max_priority_fee_per_gas: 0x3b9aca00,
5504 max_fee_per_gas: 0x6fc23ac00,
5505 max_fee_per_blob_gas: U256::zero(),
5506 blob_versioned_hashes: vec![],
5507 inner_hash: OnceCell::new(),
5508 cached_canonical: OnceCell::new(),
5509 };
5510
5511 let mut buf = Vec::new();
5512 tx.encode(&mut buf);
5513 let rlp_hex = hex::encode(&buf);
5514 assert_eq!(
5516 rlp_hex,
5517 "f8ab010794000000000000000000000000000000000000abcde8ca01038082520880821122dc0280940000000000000000000000000000000000001234829c408080f85cf85a8094000000000000000000000000000000000000abcd80b8410101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101843b9aca008506fc23ac0080c0"
5518 );
5519
5520 let (decoded, rest) = FrameTransaction::decode_unfinished(&buf).unwrap();
5522 assert!(rest.is_empty());
5523 assert_eq!(decoded, tx);
5524
5525 let sig_hash = tx.compute_sig_hash();
5526 assert_eq!(
5528 format!("{:#x}", sig_hash),
5529 "0x87d1a9ce8a1f242345bb20deab5e5111a41780814ea497fbd20700a60a2ecd8d",
5530 );
5531
5532 let mut tx2 = tx.clone();
5534 tx2.signatures[0].signature = Bytes::from(vec![0x02u8; 65]);
5535 assert_eq!(
5536 tx.compute_sig_hash(),
5537 tx2.compute_sig_hash(),
5538 "sig_hash must be independent of empty-msg signature bytes",
5539 );
5540 }
5541
5542 #[test]
5543 fn test_cost_without_base_fee_eip4844_includes_blob_gas() {
5544 use crate::constants::GAS_PER_BLOB;
5548
5549 let max_fee_per_gas: u64 = 100;
5550 let gas: u64 = 21_000;
5551 let value = U256::from(7u64);
5552 let max_fee_per_blob_gas = U256::from(50u64);
5553 let blob_count: usize = 1;
5554
5555 let tx = Transaction::EIP4844Transaction(EIP4844Transaction {
5556 max_fee_per_gas,
5557 gas,
5558 value,
5559 max_fee_per_blob_gas,
5560 blob_versioned_hashes: vec![H256::zero(); blob_count],
5561 ..Default::default()
5562 });
5563
5564 let got = tx.cost_without_base_fee().expect("cost is computable");
5565
5566 let gas_cost = U256::from(max_fee_per_gas) * U256::from(gas);
5567 let blob_gas = U256::from(GAS_PER_BLOB) * U256::from(blob_count as u64);
5568 let blob_cost = blob_gas * max_fee_per_blob_gas;
5569 let expected = gas_cost + blob_cost + value;
5570
5571 assert_eq!(
5572 got, expected,
5573 "blob-gas term missing from cost_without_base_fee() for EIP-4844"
5574 );
5575 }
5576}