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#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
19#[doc(alias = "LegacyTransaction", alias = "TransactionLegacy", alias = "LegacyTx")]
20pub struct TxLegacy {
21 #[cfg_attr(
23 feature = "serde",
24 serde(
25 default,
26 with = "alloy_serde::quantity::opt",
27 skip_serializing_if = "Option::is_none",
28 )
29 )]
30 pub chain_id: Option<ChainId>,
31 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
33 pub nonce: u64,
34 #[cfg_attr(feature = "serde", serde(with = "gas_price"))]
46 pub gas_price: u128,
47 #[cfg_attr(
53 feature = "serde",
54 serde(with = "alloy_serde::quantity", rename = "gas", alias = "gasLimit")
55 )]
56 pub gas_limit: u64,
57 #[cfg_attr(feature = "serde", serde(default))]
60 pub to: TxKind,
61 pub value: U256,
66 pub input: Bytes,
72}
73
74impl TxLegacy {
75 pub const TX_TYPE: isize = 0;
77
78 #[inline]
80 pub fn size(&self) -> usize {
81 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() }
89
90 pub(crate) fn eip155_fields_len(&self) -> usize {
93 self.chain_id.map_or(
94 0,
96 |id| id.length() + 2,
100 )
101 }
102
103 pub(crate) fn encode_eip155_signing_fields(&self, out: &mut dyn BufMut) {
106 if let Some(id) = self.chain_id {
109 id.encode(out);
111 0x00u8.encode(out);
112 0x00u8.encode(out);
113 }
114 }
115}
116
117impl RlpEcdsaEncodableTx for TxLegacy {
120 fn rlp_encoded_fields_length(&self) -> usize {
121 self.nonce.length()
122 + self.gas_price.length()
123 + self.gas_limit.length()
124 + self.to.length()
125 + self.value.length()
126 + self.input.0.length()
127 }
128
129 fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
130 self.nonce.encode(out);
131 self.gas_price.encode(out);
132 self.gas_limit.encode(out);
133 self.to.encode(out);
134 self.value.encode(out);
135 self.input.0.encode(out);
136 }
137
138 fn rlp_header_signed(&self, signature: &Signature) -> Header {
139 let payload_length = self.rlp_encoded_fields_length()
140 + signature.rlp_rs_len()
141 + to_eip155_value(signature.v(), self.chain_id).length();
142 Header { list: true, payload_length }
143 }
144
145 fn rlp_encoded_length_with_signature(&self, signature: &Signature) -> usize {
146 self.rlp_header_signed(signature).length_with_payload()
148 }
149
150 fn rlp_encode_signed(&self, signature: &Signature, out: &mut dyn BufMut) {
151 self.rlp_header_signed(signature).encode(out);
153 self.rlp_encode_fields(out);
154 signature.write_rlp_vrs(out, to_eip155_value(signature.v(), self.chain_id));
155 }
156
157 fn eip2718_encoded_length(&self, signature: &Signature) -> usize {
158 self.rlp_encoded_length_with_signature(signature)
159 }
160
161 fn eip2718_encode_with_type(&self, signature: &Signature, _ty: u8, out: &mut dyn BufMut) {
162 self.rlp_encode_signed(signature, out);
163 }
164
165 fn network_header(&self, signature: &Signature) -> Header {
166 self.rlp_header_signed(signature)
167 }
168
169 fn network_encoded_length(&self, signature: &Signature) -> usize {
170 self.rlp_encoded_length_with_signature(signature)
171 }
172
173 fn network_encode_with_type(&self, signature: &Signature, _ty: u8, out: &mut dyn BufMut) {
174 self.rlp_encode_signed(signature, out);
175 }
176
177 fn tx_hash_with_type(&self, signature: &Signature, _ty: u8) -> alloy_primitives::TxHash {
178 let mut buf = Vec::with_capacity(self.rlp_encoded_length_with_signature(signature));
179 self.rlp_encode_signed(signature, &mut buf);
180 keccak256(&buf)
181 }
182}
183
184impl RlpEcdsaDecodableTx for TxLegacy {
185 const DEFAULT_TX_TYPE: u8 = { Self::TX_TYPE as u8 };
186
187 fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
188 Ok(Self {
189 nonce: Decodable::decode(buf)?,
190 gas_price: Decodable::decode(buf)?,
191 gas_limit: Decodable::decode(buf)?,
192 to: Decodable::decode(buf)?,
193 value: Decodable::decode(buf)?,
194 input: Decodable::decode(buf)?,
195 chain_id: None,
196 })
197 }
198
199 fn rlp_decode_with_signature(buf: &mut &[u8]) -> alloy_rlp::Result<(Self, Signature)> {
200 let header = Header::decode(buf)?;
201 if !header.list {
202 return Err(alloy_rlp::Error::UnexpectedString);
203 }
204
205 let remaining = buf.len();
206 let mut tx = Self::rlp_decode_fields(buf)?;
207 let signature = Signature::decode_rlp_vrs(buf, |buf| {
208 let value = Decodable::decode(buf)?;
209 let (parity, chain_id) =
210 from_eip155_value(value).ok_or(alloy_rlp::Error::Custom("invalid parity value"))?;
211 tx.chain_id = chain_id;
212 Ok(parity)
213 })?;
214
215 if buf.len() + header.payload_length != remaining {
216 return Err(alloy_rlp::Error::ListLengthMismatch {
217 expected: header.payload_length,
218 got: remaining - buf.len(),
219 });
220 }
221
222 Ok((tx, signature))
223 }
224
225 fn eip2718_decode_with_type(
226 buf: &mut &[u8],
227 _ty: u8,
228 ) -> alloy_eips::eip2718::Eip2718Result<Signed<Self>> {
229 Self::rlp_decode_signed(buf).map_err(Into::into)
230 }
231
232 fn eip2718_decode(buf: &mut &[u8]) -> alloy_eips::eip2718::Eip2718Result<Signed<Self>> {
233 Self::rlp_decode_signed(buf).map_err(Into::into)
234 }
235
236 fn network_decode_with_type(
237 buf: &mut &[u8],
238 _ty: u8,
239 ) -> alloy_eips::eip2718::Eip2718Result<Signed<Self>> {
240 Self::rlp_decode_signed(buf).map_err(Into::into)
241 }
242}
243
244impl Transaction for TxLegacy {
245 #[inline]
246 fn chain_id(&self) -> Option<ChainId> {
247 self.chain_id
248 }
249
250 #[inline]
251 fn nonce(&self) -> u64 {
252 self.nonce
253 }
254
255 #[inline]
256 fn gas_limit(&self) -> u64 {
257 self.gas_limit
258 }
259
260 #[inline]
261 fn gas_price(&self) -> Option<u128> {
262 Some(self.gas_price)
263 }
264
265 #[inline]
266 fn max_fee_per_gas(&self) -> u128 {
267 self.gas_price
268 }
269
270 #[inline]
271 fn max_priority_fee_per_gas(&self) -> Option<u128> {
272 None
273 }
274
275 #[inline]
276 fn max_fee_per_blob_gas(&self) -> Option<u128> {
277 None
278 }
279
280 #[inline]
281 fn priority_fee_or_price(&self) -> u128 {
282 self.gas_price
283 }
284
285 fn effective_gas_price(&self, _base_fee: Option<u64>) -> u128 {
286 self.gas_price
287 }
288
289 #[inline]
290 fn is_dynamic_fee(&self) -> bool {
291 false
292 }
293
294 #[inline]
295 fn kind(&self) -> TxKind {
296 self.to
297 }
298
299 #[inline]
300 fn is_create(&self) -> bool {
301 self.to.is_create()
302 }
303
304 #[inline]
305 fn value(&self) -> U256 {
306 self.value
307 }
308
309 #[inline]
310 fn input(&self) -> &Bytes {
311 &self.input
312 }
313
314 #[inline]
315 fn access_list(&self) -> Option<&AccessList> {
316 None
317 }
318
319 #[inline]
320 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
321 None
322 }
323
324 #[inline]
325 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
326 None
327 }
328}
329
330impl SignableTransaction<Signature> for TxLegacy {
331 fn set_chain_id(&mut self, chain_id: ChainId) {
332 self.chain_id = Some(chain_id);
333 }
334
335 fn encode_for_signing(&self, out: &mut dyn BufMut) {
336 Header {
337 list: true,
338 payload_length: self.rlp_encoded_fields_length() + self.eip155_fields_len(),
339 }
340 .encode(out);
341 self.rlp_encode_fields(out);
342 self.encode_eip155_signing_fields(out);
343 }
344
345 fn payload_len_for_signature(&self) -> usize {
346 let payload_length = self.rlp_encoded_fields_length() + self.eip155_fields_len();
347 Header { list: true, payload_length }.length_with_payload()
349 }
350}
351
352impl Typed2718 for TxLegacy {
353 fn ty(&self) -> u8 {
354 TxType::Legacy as u8
355 }
356}
357
358impl IsTyped2718 for TxLegacy {
359 fn is_type(type_id: u8) -> bool {
360 matches!(type_id, 0x00)
361 }
362}
363
364impl Encodable for TxLegacy {
365 fn encode(&self, out: &mut dyn BufMut) {
366 self.encode_for_signing(out)
367 }
368
369 fn length(&self) -> usize {
370 let payload_length = self.rlp_encoded_fields_length() + self.eip155_fields_len();
371 length_of_length(payload_length) + payload_length
373 }
374}
375
376impl Decodable for TxLegacy {
377 fn decode(data: &mut &[u8]) -> Result<Self> {
378 let header = Header::decode(data)?;
379 let remaining_len = data.len();
380
381 let transaction_payload_len = header.payload_length;
382
383 if transaction_payload_len > remaining_len {
384 return Err(alloy_rlp::Error::InputTooShort);
385 }
386
387 let mut transaction = Self::rlp_decode_fields(data)?;
388
389 if !data.is_empty() {
391 transaction.chain_id = Some(Decodable::decode(data)?);
392 let _: U256 = Decodable::decode(data)?; let _: U256 = Decodable::decode(data)?; }
395
396 let decoded = remaining_len - data.len();
397 if decoded != transaction_payload_len {
398 return Err(alloy_rlp::Error::UnexpectedLength);
399 }
400
401 Ok(transaction)
402 }
403}
404
405pub const fn to_eip155_value(y_parity: bool, chain_id: Option<ChainId>) -> u128 {
407 match chain_id {
408 Some(id) => 35 + id as u128 * 2 + y_parity as u128,
409 None => 27 + y_parity as u128,
410 }
411}
412
413pub const fn from_eip155_value(value: u128) -> Option<(bool, Option<ChainId>)> {
415 match value {
416 27 => Some((false, None)),
417 28 => Some((true, None)),
418 v @ 35.. => {
419 let y_parity = ((v - 35) % 2) != 0;
420 let chain_id = (v - 35) / 2;
421
422 if chain_id > u64::MAX as u128 {
423 return None;
424 }
425 Some((y_parity, Some(chain_id as u64)))
426 }
427 _ => None,
428 }
429}
430
431#[cfg(feature = "serde")]
432mod gas_price {
433 use alloy_primitives::U256;
434 use serde::{Deserialize, Deserializer};
435
436 pub(super) use alloy_serde::quantity::serialize;
437
438 pub(super) fn deserialize<'de, D>(deserializer: D) -> Result<u128, D::Error>
439 where
440 D: Deserializer<'de>,
441 {
442 U256::deserialize(deserializer).map(|x| x.saturating_to())
443 }
444
445 #[test]
446 fn test_gas_price() {
447 #[derive(Debug, PartialEq, Eq, serde::Serialize, Deserialize)]
448 struct Value {
449 #[serde(with = "self")]
450 inner: u128,
451 }
452
453 let json = r#"{"inner":"0x3e8"}"#;
454 let value = serde_json::from_str::<Value>(json).unwrap();
455 assert_eq!(value.inner, 1000);
456
457 let json =
458 r#"{"inner":"0x30783134626639633464372e3333333333333333333333333333333333333333"}"#;
459 let value = serde_json::from_str::<Value>(json).unwrap();
460 assert_eq!(value.inner, u128::MAX);
461 }
462}
463
464#[cfg(feature = "serde")]
465pub mod signed_legacy_serde {
466 use super::*;
472 use alloc::borrow::Cow;
473 use alloy_primitives::U128;
474 use serde::{Deserialize, Serialize};
475
476 struct LegacySignature {
477 r: U256,
478 s: U256,
479 v: U128,
480 }
481
482 #[derive(Serialize, Deserialize)]
483 struct HumanReadableRepr {
484 r: U256,
485 s: U256,
486 v: U128,
487 }
488
489 #[derive(Serialize, Deserialize)]
490 #[serde(transparent)]
491 struct NonHumanReadableRepr((U256, U256, U128));
492
493 impl Serialize for LegacySignature {
494 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
495 where
496 S: serde::Serializer,
497 {
498 if serializer.is_human_readable() {
499 HumanReadableRepr { r: self.r, s: self.s, v: self.v }.serialize(serializer)
500 } else {
501 NonHumanReadableRepr((self.r, self.s, self.v)).serialize(serializer)
502 }
503 }
504 }
505
506 impl<'de> Deserialize<'de> for LegacySignature {
507 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
508 where
509 D: serde::Deserializer<'de>,
510 {
511 if deserializer.is_human_readable() {
512 HumanReadableRepr::deserialize(deserializer).map(|repr| Self {
513 r: repr.r,
514 s: repr.s,
515 v: repr.v,
516 })
517 } else {
518 NonHumanReadableRepr::deserialize(deserializer).map(|repr| Self {
519 r: repr.0 .0,
520 s: repr.0 .1,
521 v: repr.0 .2,
522 })
523 }
524 }
525 }
526
527 #[derive(Serialize, Deserialize)]
528 struct SignedLegacy<'a> {
529 #[serde(flatten)]
530 tx: Cow<'a, TxLegacy>,
531 #[serde(flatten)]
532 signature: LegacySignature,
533 hash: B256,
534 }
535
536 pub fn serialize<S>(signed: &crate::Signed<TxLegacy>, serializer: S) -> Result<S::Ok, S::Error>
538 where
539 S: serde::Serializer,
540 {
541 SignedLegacy {
542 tx: Cow::Borrowed(signed.tx()),
543 signature: LegacySignature {
544 v: U128::from(to_eip155_value(signed.signature().v(), signed.tx().chain_id())),
545 r: signed.signature().r(),
546 s: signed.signature().s(),
547 },
548 hash: *signed.hash(),
549 }
550 .serialize(serializer)
551 }
552
553 pub fn deserialize<'de, D>(deserializer: D) -> Result<crate::Signed<TxLegacy>, D::Error>
555 where
556 D: serde::Deserializer<'de>,
557 {
558 let SignedLegacy { mut tx, signature, hash } = SignedLegacy::deserialize(deserializer)?;
559
560 let is_fake_system_signature =
568 signature.r.is_zero() && signature.s.is_zero() && signature.v.is_zero();
569
570 let signature = if is_fake_system_signature {
571 Signature::new(U256::ZERO, U256::ZERO, false)
572 } else {
573 let (parity, chain_id) = from_eip155_value(signature.v.to()).ok_or_else(|| {
574 serde::de::Error::custom("invalid EIP-155 signature parity value")
575 })?;
576
577 if let Some((tx_chain_id, chain_id)) = tx.chain_id().zip(chain_id) {
580 if tx_chain_id != chain_id {
581 return Err(serde::de::Error::custom("chain id mismatch"));
582 }
583 }
584
585 tx.to_mut().chain_id = chain_id;
587
588 Signature::new(signature.r, signature.s, parity)
589 };
590 Ok(Signed::new_unchecked(tx.into_owned(), signature, hash))
591 }
592}
593
594#[cfg(feature = "serde")]
595pub mod untagged_legacy_serde {
596 use super::*;
604 use serde::Deserialize;
605
606 #[derive(Deserialize)]
607 pub(crate) struct UntaggedLegacy {
608 #[serde(default, rename = "type", deserialize_with = "alloy_serde::reject_if_some")]
609 _ty: Option<()>,
610 #[serde(flatten, with = "crate::transaction::signed_legacy_serde")]
611 tx: Signed<TxLegacy>,
612 }
613
614 pub fn deserialize<'de, D>(deserializer: D) -> Result<Signed<TxLegacy>, D::Error>
616 where
617 D: serde::Deserializer<'de>,
618 {
619 UntaggedLegacy::deserialize(deserializer).map(|tx| tx.tx)
620 }
621}
622
623#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
625pub(super) mod serde_bincode_compat {
626 use alloc::borrow::Cow;
627 use alloy_primitives::{Bytes, ChainId, TxKind, U256};
628 use serde::{Deserialize, Deserializer, Serialize, Serializer};
629 use serde_with::{DeserializeAs, SerializeAs};
630
631 #[derive(Debug, Serialize, Deserialize)]
647 pub struct TxLegacy<'a> {
648 #[serde(default, with = "alloy_serde::quantity::opt")]
649 chain_id: Option<ChainId>,
650 nonce: u64,
651 gas_price: u128,
652 gas_limit: u64,
653 #[serde(default)]
654 to: TxKind,
655 value: U256,
656 input: Cow<'a, Bytes>,
657 }
658
659 impl<'a> From<&'a super::TxLegacy> for TxLegacy<'a> {
660 fn from(value: &'a super::TxLegacy) -> Self {
661 Self {
662 chain_id: value.chain_id,
663 nonce: value.nonce,
664 gas_price: value.gas_price,
665 gas_limit: value.gas_limit,
666 to: value.to,
667 value: value.value,
668 input: Cow::Borrowed(&value.input),
669 }
670 }
671 }
672
673 impl<'a> From<TxLegacy<'a>> for super::TxLegacy {
674 fn from(value: TxLegacy<'a>) -> Self {
675 Self {
676 chain_id: value.chain_id,
677 nonce: value.nonce,
678 gas_price: value.gas_price,
679 gas_limit: value.gas_limit,
680 to: value.to,
681 value: value.value,
682 input: value.input.into_owned(),
683 }
684 }
685 }
686
687 impl SerializeAs<super::TxLegacy> for TxLegacy<'_> {
688 fn serialize_as<S>(source: &super::TxLegacy, serializer: S) -> Result<S::Ok, S::Error>
689 where
690 S: Serializer,
691 {
692 TxLegacy::from(source).serialize(serializer)
693 }
694 }
695
696 impl<'de> DeserializeAs<'de, super::TxLegacy> for TxLegacy<'de> {
697 fn deserialize_as<D>(deserializer: D) -> Result<super::TxLegacy, D::Error>
698 where
699 D: Deserializer<'de>,
700 {
701 TxLegacy::deserialize(deserializer).map(Into::into)
702 }
703 }
704
705 #[cfg(test)]
706 mod tests {
707 use arbitrary::Arbitrary;
708 use bincode::config;
709 use rand::Rng;
710 use serde::{Deserialize, Serialize};
711 use serde_with::serde_as;
712
713 use super::super::{serde_bincode_compat, TxLegacy};
714
715 #[test]
716 fn test_tx_legacy_bincode_roundtrip() {
717 #[serde_as]
718 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
719 struct Data {
720 #[serde_as(as = "serde_bincode_compat::TxLegacy")]
721 transaction: TxLegacy,
722 }
723
724 let mut bytes = [0u8; 1024];
725 rand::thread_rng().fill(bytes.as_mut_slice());
726 let data = Data {
727 transaction: TxLegacy::arbitrary(&mut arbitrary::Unstructured::new(&bytes))
728 .unwrap(),
729 };
730
731 let encoded = bincode::serde::encode_to_vec(&data, config::legacy()).unwrap();
732 let (decoded, _) =
733 bincode::serde::decode_from_slice::<Data, _>(&encoded, config::legacy()).unwrap();
734 assert_eq!(decoded, data);
735 }
736 }
737}
738
739#[cfg(all(test, feature = "k256"))]
740mod tests {
741 use super::signed_legacy_serde;
742 use crate::{
743 transaction::{from_eip155_value, to_eip155_value},
744 SignableTransaction, TxLegacy,
745 };
746 use alloy_primitives::{address, b256, hex, Address, Signature, TxKind, B256, U256};
747
748 #[test]
749 fn recover_signer_legacy() {
750 let signer: Address = hex!("398137383b3d25c92898c656696e41950e47316b").into();
751 let hash: B256 =
752 hex!("bb3a336e3f823ec18197f1e13ee875700f08f03e2cab75f0d0b118dabb44cba0").into();
753
754 let tx = TxLegacy {
755 chain_id: Some(1),
756 nonce: 0x18,
757 gas_price: 0xfa56ea00,
758 gas_limit: 119902,
759 to: TxKind::Call(hex!("06012c8cf97bead5deae237070f9587f8e7a266d").into()),
760 value: U256::from(0x1c6bf526340000u64),
761 input: hex!("f7d8c88300000000000000000000000000000000000000000000000000000000000cee6100000000000000000000000000000000000000000000000000000000000ac3e1").into(),
762 };
763
764 let sig = Signature::from_scalars_and_parity(
765 b256!("2a378831cf81d99a3f06a18ae1b6ca366817ab4d88a70053c41d7a8f0368e031"),
766 b256!("450d831a05b6e418724436c05c155e0a1b7b921015d0fbc2f667aed709ac4fb5"),
767 false,
768 );
769
770 let signed_tx = tx.into_signed(sig);
771
772 assert_eq!(*signed_tx.hash(), hash, "Expected same hash");
773 assert_eq!(signed_tx.recover_signer().unwrap(), signer, "Recovering signer should pass.");
774 }
775
776 #[test]
777 fn decode_legacy_and_recover_signer() {
779 use crate::transaction::RlpEcdsaDecodableTx;
780 let raw_tx = alloy_primitives::bytes!("f9015482078b8505d21dba0083022ef1947a250d5630b4cf539739df2c5dacb4c659f2488d880c46549a521b13d8b8e47ff36ab50000000000000000000000000000000000000000000066ab5a608bd00a23f2fe000000000000000000000000000000000000000000000000000000000000008000000000000000000000000048c04ed5691981c42154c6167398f95e8f38a7ff00000000000000000000000000000000000000000000000000000000632ceac70000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006c6ee5e31d828de241282b9606c8e98ea48526e225a0c9077369501641a92ef7399ff81c21639ed4fd8fc69cb793cfa1dbfab342e10aa0615facb2f1bcf3274a354cfe384a38d0cc008a11c2dd23a69111bc6930ba27a8");
781
782 let tx = TxLegacy::rlp_decode_signed(&mut raw_tx.as_ref()).unwrap();
783
784 let recovered = tx.recover_signer().unwrap();
785 let expected = address!("a12e1462d0ceD572f396F58B6E2D03894cD7C8a4");
786
787 assert_eq!(tx.tx().chain_id, Some(1), "Expected same chain id");
788 assert_eq!(expected, recovered, "Expected same signer");
789 }
790
791 #[test]
792 fn eip155_roundtrip() {
793 assert_eq!(from_eip155_value(to_eip155_value(false, None)), Some((false, None)));
794 assert_eq!(from_eip155_value(to_eip155_value(true, None)), Some((true, None)));
795
796 for chain_id in [0, 1, 10, u64::MAX] {
797 assert_eq!(
798 from_eip155_value(to_eip155_value(false, Some(chain_id))),
799 Some((false, Some(chain_id)))
800 );
801 assert_eq!(
802 from_eip155_value(to_eip155_value(true, Some(chain_id))),
803 Some((true, Some(chain_id)))
804 );
805 }
806 }
807
808 #[test]
809 fn can_deserialize_system_transaction_with_zero_signature() {
810 let raw_tx = serde_json::json!({
811 "blockHash": "0x5307b5c812a067f8bc1ed1cc89d319ae6f9a0c9693848bd25c36b5191de60b85",
812 "blockNumber": "0x45a59bb",
813 "from": "0x0000000000000000000000000000000000000000",
814 "gas": "0x1e8480",
815 "gasPrice": "0x0",
816 "hash": "0x16ef68aa8f35add3a03167a12b5d1268e344f6605a64ecc3f1c3aa68e98e4e06",
817 "input": "0xcbd4ece900000000000000000000000032155c9d39084f040ba17890fe8134dbe2a0453f0000000000000000000000004a0126ee88018393b1ad2455060bc350ead9908a000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000469f700000000000000000000000000000000000000000000000000000000000000644ff746f60000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002043e908a4e862aebb10e7e27db0b892b58a7e32af11d64387a414dabc327b00e200000000000000000000000000000000000000000000000000000000",
818 "nonce": "0x469f7",
819 "to": "0x4200000000000000000000000000000000000007",
820 "transactionIndex": "0x0",
821 "value": "0x0",
822 "v": "0x0",
823 "r": "0x0",
824 "s": "0x0",
825 "queueOrigin": "l1",
826 "l1TxOrigin": "0x36bde71c97b33cc4729cf772ae268934f7ab70b2",
827 "l1BlockNumber": "0xfd1a6c",
828 "l1Timestamp": "0x63e434ff",
829 "index": "0x45a59ba",
830 "queueIndex": "0x469f7",
831 "rawTransaction": "0xcbd4ece900000000000000000000000032155c9d39084f040ba17890fe8134dbe2a0453f0000000000000000000000004a0126ee88018393b1ad2455060bc350ead9908a000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000469f700000000000000000000000000000000000000000000000000000000000000644ff746f60000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002043e908a4e862aebb10e7e27db0b892b58a7e32af11d64387a414dabc327b00e200000000000000000000000000000000000000000000000000000000"
832 });
833
834 let signed: crate::Signed<TxLegacy> = signed_legacy_serde::deserialize(raw_tx).unwrap();
835
836 assert_eq!(signed.signature().r(), U256::ZERO);
837 assert_eq!(signed.signature().s(), U256::ZERO);
838 assert!(!signed.signature().v());
839
840 assert_eq!(
841 signed.hash(),
842 &b256!("0x16ef68aa8f35add3a03167a12b5d1268e344f6605a64ecc3f1c3aa68e98e4e06"),
843 "hash should match the transaction hash"
844 );
845 }
846}