1use crate::{
2 transaction::{RlpEcdsaDecodableTx, RlpEcdsaEncodableTx},
3 SignableTransaction, Signed, Transaction, TxType,
4};
5use alloc::vec::Vec;
6use alloy_eips::{
7 eip2718::IsTyped2718, eip2930::AccessList, eip7702::SignedAuthorization, Typed2718,
8};
9use alloy_primitives::{keccak256, Bytes, ChainId, Signature, TxKind, B256, U256};
10use alloy_rlp::{length_of_length, BufMut, Decodable, Encodable, Header, Result};
11use core::mem;
12
13#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
15#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
18#[doc(alias = "LegacyTransaction", alias = "TransactionLegacy", alias = "LegacyTx")]
19pub struct TxLegacy {
20 #[cfg_attr(
22 feature = "serde",
23 serde(
24 default,
25 with = "alloy_serde::quantity::opt",
26 skip_serializing_if = "Option::is_none",
27 )
28 )]
29 pub chain_id: Option<ChainId>,
30 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
32 pub nonce: u64,
33 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
41 pub gas_price: u128,
42 #[cfg_attr(
48 feature = "serde",
49 serde(with = "alloy_serde::quantity", rename = "gas", alias = "gasLimit")
50 )]
51 pub gas_limit: u64,
52 #[cfg_attr(feature = "serde", serde(default))]
55 pub to: TxKind,
56 pub value: U256,
61 pub input: Bytes,
67}
68
69impl TxLegacy {
70 pub const TX_TYPE: isize = 0;
72
73 #[inline]
75 pub fn size(&self) -> usize {
76 mem::size_of::<Option<ChainId>>() + mem::size_of::<u64>() + mem::size_of::<u128>() + mem::size_of::<u64>() + self.to.size() + mem::size_of::<U256>() + self.input.len() }
84
85 pub(crate) fn eip155_fields_len(&self) -> usize {
88 self.chain_id.map_or(
89 0,
91 |id| id.length() + 2,
95 )
96 }
97
98 pub(crate) fn encode_eip155_signing_fields(&self, out: &mut dyn BufMut) {
101 if let Some(id) = self.chain_id {
104 id.encode(out);
106 0x00u8.encode(out);
107 0x00u8.encode(out);
108 }
109 }
110}
111
112impl RlpEcdsaEncodableTx for TxLegacy {
115 fn rlp_encoded_fields_length(&self) -> usize {
116 self.nonce.length()
117 + self.gas_price.length()
118 + self.gas_limit.length()
119 + self.to.length()
120 + self.value.length()
121 + self.input.0.length()
122 }
123
124 fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
125 self.nonce.encode(out);
126 self.gas_price.encode(out);
127 self.gas_limit.encode(out);
128 self.to.encode(out);
129 self.value.encode(out);
130 self.input.0.encode(out);
131 }
132
133 fn rlp_header_signed(&self, signature: &Signature) -> Header {
134 let payload_length = self.rlp_encoded_fields_length()
135 + signature.rlp_rs_len()
136 + to_eip155_value(signature.v(), self.chain_id).length();
137 Header { list: true, payload_length }
138 }
139
140 fn rlp_encoded_length_with_signature(&self, signature: &Signature) -> usize {
141 self.rlp_header_signed(signature).length_with_payload()
143 }
144
145 fn rlp_encode_signed(&self, signature: &Signature, out: &mut dyn BufMut) {
146 self.rlp_header_signed(signature).encode(out);
148 self.rlp_encode_fields(out);
149 signature.write_rlp_vrs(out, to_eip155_value(signature.v(), self.chain_id));
150 }
151
152 fn eip2718_encoded_length(&self, signature: &Signature) -> usize {
153 self.rlp_encoded_length_with_signature(signature)
154 }
155
156 fn eip2718_encode_with_type(&self, signature: &Signature, _ty: u8, out: &mut dyn BufMut) {
157 self.rlp_encode_signed(signature, out);
158 }
159
160 fn network_header(&self, signature: &Signature) -> Header {
161 self.rlp_header_signed(signature)
162 }
163
164 fn network_encoded_length(&self, signature: &Signature) -> usize {
165 self.rlp_encoded_length_with_signature(signature)
166 }
167
168 fn network_encode_with_type(&self, signature: &Signature, _ty: u8, out: &mut dyn BufMut) {
169 self.rlp_encode_signed(signature, out);
170 }
171
172 fn tx_hash_with_type(&self, signature: &Signature, _ty: u8) -> alloy_primitives::TxHash {
173 let mut buf = Vec::with_capacity(self.rlp_encoded_length_with_signature(signature));
174 self.rlp_encode_signed(signature, &mut buf);
175 keccak256(&buf)
176 }
177}
178
179impl RlpEcdsaDecodableTx for TxLegacy {
180 const DEFAULT_TX_TYPE: u8 = { Self::TX_TYPE as u8 };
181
182 fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
183 Ok(Self {
184 nonce: Decodable::decode(buf)?,
185 gas_price: Decodable::decode(buf)?,
186 gas_limit: Decodable::decode(buf)?,
187 to: Decodable::decode(buf)?,
188 value: Decodable::decode(buf)?,
189 input: Decodable::decode(buf)?,
190 chain_id: None,
191 })
192 }
193
194 fn rlp_decode_with_signature(buf: &mut &[u8]) -> alloy_rlp::Result<(Self, Signature)> {
195 let header = Header::decode(buf)?;
196 if !header.list {
197 return Err(alloy_rlp::Error::UnexpectedString);
198 }
199
200 let remaining = buf.len();
201 let mut tx = Self::rlp_decode_fields(buf)?;
202 let signature = Signature::decode_rlp_vrs(buf, |buf| {
203 let value = Decodable::decode(buf)?;
204 let (parity, chain_id) =
205 from_eip155_value(value).ok_or(alloy_rlp::Error::Custom("invalid parity value"))?;
206 tx.chain_id = chain_id;
207 Ok(parity)
208 })?;
209
210 if buf.len() + header.payload_length != remaining {
211 return Err(alloy_rlp::Error::ListLengthMismatch {
212 expected: header.payload_length,
213 got: remaining - buf.len(),
214 });
215 }
216
217 Ok((tx, signature))
218 }
219
220 fn eip2718_decode_with_type(
221 buf: &mut &[u8],
222 _ty: u8,
223 ) -> alloy_eips::eip2718::Eip2718Result<Signed<Self>> {
224 Self::rlp_decode_signed(buf).map_err(Into::into)
225 }
226
227 fn eip2718_decode(buf: &mut &[u8]) -> alloy_eips::eip2718::Eip2718Result<Signed<Self>> {
228 Self::rlp_decode_signed(buf).map_err(Into::into)
229 }
230
231 fn network_decode_with_type(
232 buf: &mut &[u8],
233 _ty: u8,
234 ) -> alloy_eips::eip2718::Eip2718Result<Signed<Self>> {
235 Self::rlp_decode_signed(buf).map_err(Into::into)
236 }
237}
238
239impl Transaction for TxLegacy {
240 #[inline]
241 fn chain_id(&self) -> Option<ChainId> {
242 self.chain_id
243 }
244
245 #[inline]
246 fn nonce(&self) -> u64 {
247 self.nonce
248 }
249
250 #[inline]
251 fn gas_limit(&self) -> u64 {
252 self.gas_limit
253 }
254
255 #[inline]
256 fn gas_price(&self) -> Option<u128> {
257 Some(self.gas_price)
258 }
259
260 #[inline]
261 fn max_fee_per_gas(&self) -> u128 {
262 self.gas_price
263 }
264
265 #[inline]
266 fn max_priority_fee_per_gas(&self) -> Option<u128> {
267 None
268 }
269
270 #[inline]
271 fn max_fee_per_blob_gas(&self) -> Option<u128> {
272 None
273 }
274
275 #[inline]
276 fn priority_fee_or_price(&self) -> u128 {
277 self.gas_price
278 }
279
280 fn effective_gas_price(&self, _base_fee: Option<u64>) -> u128 {
281 self.gas_price
282 }
283
284 #[inline]
285 fn is_dynamic_fee(&self) -> bool {
286 false
287 }
288
289 #[inline]
290 fn kind(&self) -> TxKind {
291 self.to
292 }
293
294 #[inline]
295 fn is_create(&self) -> bool {
296 self.to.is_create()
297 }
298
299 #[inline]
300 fn value(&self) -> U256 {
301 self.value
302 }
303
304 #[inline]
305 fn input(&self) -> &Bytes {
306 &self.input
307 }
308
309 #[inline]
310 fn access_list(&self) -> Option<&AccessList> {
311 None
312 }
313
314 #[inline]
315 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
316 None
317 }
318
319 #[inline]
320 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
321 None
322 }
323}
324
325impl SignableTransaction<Signature> for TxLegacy {
326 fn set_chain_id(&mut self, chain_id: ChainId) {
327 self.chain_id = Some(chain_id);
328 }
329
330 fn encode_for_signing(&self, out: &mut dyn BufMut) {
331 Header {
332 list: true,
333 payload_length: self.rlp_encoded_fields_length() + self.eip155_fields_len(),
334 }
335 .encode(out);
336 self.rlp_encode_fields(out);
337 self.encode_eip155_signing_fields(out);
338 }
339
340 fn payload_len_for_signature(&self) -> usize {
341 let payload_length = self.rlp_encoded_fields_length() + self.eip155_fields_len();
342 Header { list: true, payload_length }.length_with_payload()
344 }
345}
346
347impl Typed2718 for TxLegacy {
348 fn ty(&self) -> u8 {
349 TxType::Legacy as u8
350 }
351}
352
353impl IsTyped2718 for TxLegacy {
354 fn is_type(type_id: u8) -> bool {
355 matches!(type_id, 0x00)
356 }
357}
358
359impl Encodable for TxLegacy {
360 fn encode(&self, out: &mut dyn BufMut) {
361 self.encode_for_signing(out)
362 }
363
364 fn length(&self) -> usize {
365 let payload_length = self.rlp_encoded_fields_length() + self.eip155_fields_len();
366 length_of_length(payload_length) + payload_length
368 }
369}
370
371impl Decodable for TxLegacy {
372 fn decode(data: &mut &[u8]) -> Result<Self> {
373 let header = Header::decode(data)?;
374 let remaining_len = data.len();
375
376 let transaction_payload_len = header.payload_length;
377
378 if transaction_payload_len > remaining_len {
379 return Err(alloy_rlp::Error::InputTooShort);
380 }
381
382 let mut transaction = Self::rlp_decode_fields(data)?;
383
384 if !data.is_empty() {
386 transaction.chain_id = Some(Decodable::decode(data)?);
387 let _: U256 = Decodable::decode(data)?; let _: U256 = Decodable::decode(data)?; }
390
391 let decoded = remaining_len - data.len();
392 if decoded != transaction_payload_len {
393 return Err(alloy_rlp::Error::UnexpectedLength);
394 }
395
396 Ok(transaction)
397 }
398}
399
400pub const fn to_eip155_value(y_parity: bool, chain_id: Option<ChainId>) -> u128 {
402 match chain_id {
403 Some(id) => 35 + id as u128 * 2 + y_parity as u128,
404 None => 27 + y_parity as u128,
405 }
406}
407
408pub const fn from_eip155_value(value: u128) -> Option<(bool, Option<ChainId>)> {
410 match value {
411 27 => Some((false, None)),
412 28 => Some((true, None)),
413 v @ 35.. => {
414 let y_parity = ((v - 35) % 2) != 0;
415 let chain_id = (v - 35) / 2;
416
417 if chain_id > u64::MAX as u128 {
418 return None;
419 }
420 Some((y_parity, Some(chain_id as u64)))
421 }
422 _ => None,
423 }
424}
425
426#[cfg(feature = "serde")]
427pub mod signed_legacy_serde {
428 use super::*;
434 use alloc::borrow::Cow;
435 use alloy_primitives::U128;
436 use serde::{Deserialize, Serialize};
437
438 struct LegacySignature {
439 r: U256,
440 s: U256,
441 v: U128,
442 }
443
444 #[derive(Serialize, Deserialize)]
445 struct HumanReadableRepr {
446 r: U256,
447 s: U256,
448 v: U128,
449 }
450
451 #[derive(Serialize, Deserialize)]
452 #[serde(transparent)]
453 struct NonHumanReadableRepr((U256, U256, U128));
454
455 impl Serialize for LegacySignature {
456 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
457 where
458 S: serde::Serializer,
459 {
460 if serializer.is_human_readable() {
461 HumanReadableRepr { r: self.r, s: self.s, v: self.v }.serialize(serializer)
462 } else {
463 NonHumanReadableRepr((self.r, self.s, self.v)).serialize(serializer)
464 }
465 }
466 }
467
468 impl<'de> Deserialize<'de> for LegacySignature {
469 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
470 where
471 D: serde::Deserializer<'de>,
472 {
473 if deserializer.is_human_readable() {
474 HumanReadableRepr::deserialize(deserializer).map(|repr| Self {
475 r: repr.r,
476 s: repr.s,
477 v: repr.v,
478 })
479 } else {
480 NonHumanReadableRepr::deserialize(deserializer).map(|repr| Self {
481 r: repr.0 .0,
482 s: repr.0 .1,
483 v: repr.0 .2,
484 })
485 }
486 }
487 }
488
489 #[derive(Serialize, Deserialize)]
490 struct SignedLegacy<'a> {
491 #[serde(flatten)]
492 tx: Cow<'a, TxLegacy>,
493 #[serde(flatten)]
494 signature: LegacySignature,
495 hash: B256,
496 }
497
498 pub fn serialize<S>(signed: &crate::Signed<TxLegacy>, serializer: S) -> Result<S::Ok, S::Error>
500 where
501 S: serde::Serializer,
502 {
503 SignedLegacy {
504 tx: Cow::Borrowed(signed.tx()),
505 signature: LegacySignature {
506 v: U128::from(to_eip155_value(signed.signature().v(), signed.tx().chain_id())),
507 r: signed.signature().r(),
508 s: signed.signature().s(),
509 },
510 hash: *signed.hash(),
511 }
512 .serialize(serializer)
513 }
514
515 pub fn deserialize<'de, D>(deserializer: D) -> Result<crate::Signed<TxLegacy>, D::Error>
517 where
518 D: serde::Deserializer<'de>,
519 {
520 let SignedLegacy { mut tx, signature, hash } = SignedLegacy::deserialize(deserializer)?;
521
522 let is_fake_system_signature =
530 signature.r.is_zero() && signature.s.is_zero() && signature.v.is_zero();
531
532 let signature = if is_fake_system_signature {
533 Signature::new(U256::ZERO, U256::ZERO, false)
534 } else {
535 let (parity, chain_id) = from_eip155_value(signature.v.to()).ok_or_else(|| {
536 serde::de::Error::custom("invalid EIP-155 signature parity value")
537 })?;
538
539 if let Some((tx_chain_id, chain_id)) = tx.chain_id().zip(chain_id) {
542 if tx_chain_id != chain_id {
543 return Err(serde::de::Error::custom("chain id mismatch"));
544 }
545 }
546
547 tx.to_mut().chain_id = chain_id;
549
550 Signature::new(signature.r, signature.s, parity)
551 };
552 Ok(Signed::new_unchecked(tx.into_owned(), signature, hash))
553 }
554}
555
556#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
558pub(super) mod serde_bincode_compat {
559 use alloc::borrow::Cow;
560 use alloy_primitives::{Bytes, ChainId, TxKind, U256};
561 use serde::{Deserialize, Deserializer, Serialize, Serializer};
562 use serde_with::{DeserializeAs, SerializeAs};
563
564 #[derive(Debug, Serialize, Deserialize)]
580 pub struct TxLegacy<'a> {
581 #[serde(default, with = "alloy_serde::quantity::opt")]
582 chain_id: Option<ChainId>,
583 nonce: u64,
584 gas_price: u128,
585 gas_limit: u64,
586 #[serde(default)]
587 to: TxKind,
588 value: U256,
589 input: Cow<'a, Bytes>,
590 }
591
592 impl<'a> From<&'a super::TxLegacy> for TxLegacy<'a> {
593 fn from(value: &'a super::TxLegacy) -> Self {
594 Self {
595 chain_id: value.chain_id,
596 nonce: value.nonce,
597 gas_price: value.gas_price,
598 gas_limit: value.gas_limit,
599 to: value.to,
600 value: value.value,
601 input: Cow::Borrowed(&value.input),
602 }
603 }
604 }
605
606 impl<'a> From<TxLegacy<'a>> for super::TxLegacy {
607 fn from(value: TxLegacy<'a>) -> Self {
608 Self {
609 chain_id: value.chain_id,
610 nonce: value.nonce,
611 gas_price: value.gas_price,
612 gas_limit: value.gas_limit,
613 to: value.to,
614 value: value.value,
615 input: value.input.into_owned(),
616 }
617 }
618 }
619
620 impl SerializeAs<super::TxLegacy> for TxLegacy<'_> {
621 fn serialize_as<S>(source: &super::TxLegacy, serializer: S) -> Result<S::Ok, S::Error>
622 where
623 S: Serializer,
624 {
625 TxLegacy::from(source).serialize(serializer)
626 }
627 }
628
629 impl<'de> DeserializeAs<'de, super::TxLegacy> for TxLegacy<'de> {
630 fn deserialize_as<D>(deserializer: D) -> Result<super::TxLegacy, D::Error>
631 where
632 D: Deserializer<'de>,
633 {
634 TxLegacy::deserialize(deserializer).map(Into::into)
635 }
636 }
637
638 #[cfg(test)]
639 mod tests {
640 use arbitrary::Arbitrary;
641 use bincode::config;
642 use rand::Rng;
643 use serde::{Deserialize, Serialize};
644 use serde_with::serde_as;
645
646 use super::super::{serde_bincode_compat, TxLegacy};
647
648 #[test]
649 fn test_tx_legacy_bincode_roundtrip() {
650 #[serde_as]
651 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
652 struct Data {
653 #[serde_as(as = "serde_bincode_compat::TxLegacy")]
654 transaction: TxLegacy,
655 }
656
657 let mut bytes = [0u8; 1024];
658 rand::thread_rng().fill(bytes.as_mut_slice());
659 let data = Data {
660 transaction: TxLegacy::arbitrary(&mut arbitrary::Unstructured::new(&bytes))
661 .unwrap(),
662 };
663
664 let encoded = bincode::serde::encode_to_vec(&data, config::legacy()).unwrap();
665 let (decoded, _) =
666 bincode::serde::decode_from_slice::<Data, _>(&encoded, config::legacy()).unwrap();
667 assert_eq!(decoded, data);
668 }
669 }
670}
671
672#[cfg(all(test, feature = "k256"))]
673mod tests {
674 use super::signed_legacy_serde;
675 use crate::{
676 transaction::{from_eip155_value, to_eip155_value},
677 SignableTransaction, TxLegacy,
678 };
679 use alloy_primitives::{address, b256, hex, Address, Signature, TxKind, B256, U256};
680
681 #[test]
682 fn recover_signer_legacy() {
683 let signer: Address = hex!("398137383b3d25c92898c656696e41950e47316b").into();
684 let hash: B256 =
685 hex!("bb3a336e3f823ec18197f1e13ee875700f08f03e2cab75f0d0b118dabb44cba0").into();
686
687 let tx = TxLegacy {
688 chain_id: Some(1),
689 nonce: 0x18,
690 gas_price: 0xfa56ea00,
691 gas_limit: 119902,
692 to: TxKind::Call(hex!("06012c8cf97bead5deae237070f9587f8e7a266d").into()),
693 value: U256::from(0x1c6bf526340000u64),
694 input: hex!("f7d8c88300000000000000000000000000000000000000000000000000000000000cee6100000000000000000000000000000000000000000000000000000000000ac3e1").into(),
695 };
696
697 let sig = Signature::from_scalars_and_parity(
698 b256!("2a378831cf81d99a3f06a18ae1b6ca366817ab4d88a70053c41d7a8f0368e031"),
699 b256!("450d831a05b6e418724436c05c155e0a1b7b921015d0fbc2f667aed709ac4fb5"),
700 false,
701 );
702
703 let signed_tx = tx.into_signed(sig);
704
705 assert_eq!(*signed_tx.hash(), hash, "Expected same hash");
706 assert_eq!(signed_tx.recover_signer().unwrap(), signer, "Recovering signer should pass.");
707 }
708
709 #[test]
710 fn decode_legacy_and_recover_signer() {
712 use crate::transaction::RlpEcdsaDecodableTx;
713 let raw_tx = alloy_primitives::bytes!("f9015482078b8505d21dba0083022ef1947a250d5630b4cf539739df2c5dacb4c659f2488d880c46549a521b13d8b8e47ff36ab50000000000000000000000000000000000000000000066ab5a608bd00a23f2fe000000000000000000000000000000000000000000000000000000000000008000000000000000000000000048c04ed5691981c42154c6167398f95e8f38a7ff00000000000000000000000000000000000000000000000000000000632ceac70000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006c6ee5e31d828de241282b9606c8e98ea48526e225a0c9077369501641a92ef7399ff81c21639ed4fd8fc69cb793cfa1dbfab342e10aa0615facb2f1bcf3274a354cfe384a38d0cc008a11c2dd23a69111bc6930ba27a8");
714
715 let tx = TxLegacy::rlp_decode_signed(&mut raw_tx.as_ref()).unwrap();
716
717 let recovered = tx.recover_signer().unwrap();
718 let expected = address!("a12e1462d0ceD572f396F58B6E2D03894cD7C8a4");
719
720 assert_eq!(tx.tx().chain_id, Some(1), "Expected same chain id");
721 assert_eq!(expected, recovered, "Expected same signer");
722 }
723
724 #[test]
725 fn eip155_roundtrip() {
726 assert_eq!(from_eip155_value(to_eip155_value(false, None)), Some((false, None)));
727 assert_eq!(from_eip155_value(to_eip155_value(true, None)), Some((true, None)));
728
729 for chain_id in [0, 1, 10, u64::MAX] {
730 assert_eq!(
731 from_eip155_value(to_eip155_value(false, Some(chain_id))),
732 Some((false, Some(chain_id)))
733 );
734 assert_eq!(
735 from_eip155_value(to_eip155_value(true, Some(chain_id))),
736 Some((true, Some(chain_id)))
737 );
738 }
739 }
740
741 #[test]
742 fn can_deserialize_system_transaction_with_zero_signature() {
743 let raw_tx = serde_json::json!({
744 "blockHash": "0x5307b5c812a067f8bc1ed1cc89d319ae6f9a0c9693848bd25c36b5191de60b85",
745 "blockNumber": "0x45a59bb",
746 "from": "0x0000000000000000000000000000000000000000",
747 "gas": "0x1e8480",
748 "gasPrice": "0x0",
749 "hash": "0x16ef68aa8f35add3a03167a12b5d1268e344f6605a64ecc3f1c3aa68e98e4e06",
750 "input": "0xcbd4ece900000000000000000000000032155c9d39084f040ba17890fe8134dbe2a0453f0000000000000000000000004a0126ee88018393b1ad2455060bc350ead9908a000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000469f700000000000000000000000000000000000000000000000000000000000000644ff746f60000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002043e908a4e862aebb10e7e27db0b892b58a7e32af11d64387a414dabc327b00e200000000000000000000000000000000000000000000000000000000",
751 "nonce": "0x469f7",
752 "to": "0x4200000000000000000000000000000000000007",
753 "transactionIndex": "0x0",
754 "value": "0x0",
755 "v": "0x0",
756 "r": "0x0",
757 "s": "0x0",
758 "queueOrigin": "l1",
759 "l1TxOrigin": "0x36bde71c97b33cc4729cf772ae268934f7ab70b2",
760 "l1BlockNumber": "0xfd1a6c",
761 "l1Timestamp": "0x63e434ff",
762 "index": "0x45a59ba",
763 "queueIndex": "0x469f7",
764 "rawTransaction": "0xcbd4ece900000000000000000000000032155c9d39084f040ba17890fe8134dbe2a0453f0000000000000000000000004a0126ee88018393b1ad2455060bc350ead9908a000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000469f700000000000000000000000000000000000000000000000000000000000000644ff746f60000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002043e908a4e862aebb10e7e27db0b892b58a7e32af11d64387a414dabc327b00e200000000000000000000000000000000000000000000000000000000"
765 });
766
767 let signed: crate::Signed<TxLegacy> = signed_legacy_serde::deserialize(raw_tx).unwrap();
768
769 assert_eq!(signed.signature().r(), U256::ZERO);
770 assert_eq!(signed.signature().s(), U256::ZERO);
771 assert!(!signed.signature().v());
772
773 assert_eq!(
774 signed.hash(),
775 &b256!("0x16ef68aa8f35add3a03167a12b5d1268e344f6605a64ecc3f1c3aa68e98e4e06"),
776 "hash should match the transaction hash"
777 );
778 }
779}