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