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