referrerpolicy=no-referrer-when-downgrade

pallet_revive/evm/api/
rlp_codec.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17//! RLP encoding and decoding for Ethereum transactions.
18//! See <https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/> for more information about RLP encoding.
19
20use super::*;
21use alloc::vec::Vec;
22use rlp::{Decodable, Encodable};
23
24impl TransactionUnsigned {
25	/// Return the bytes to be signed by the private key.
26	pub fn unsigned_payload(&self) -> Vec<u8> {
27		use TransactionUnsigned::*;
28		let mut s = rlp::RlpStream::new();
29		match self {
30			Transaction7702Unsigned(ref tx) => {
31				s.append(&tx.r#type.value());
32				s.append(tx);
33			},
34			Transaction2930Unsigned(ref tx) => {
35				s.append(&tx.r#type.value());
36				s.append(tx);
37			},
38			Transaction1559Unsigned(ref tx) => {
39				s.append(&tx.r#type.value());
40				s.append(tx);
41			},
42			Transaction4844Unsigned(ref tx) => {
43				s.append(&tx.r#type.value());
44				s.append(tx);
45			},
46			TransactionLegacyUnsigned(ref tx) => {
47				s.append(tx);
48			},
49		}
50
51		s.out().to_vec()
52	}
53}
54
55impl TransactionSigned {
56	/// Extract the unsigned transaction from a signed transaction.
57	pub fn unsigned(self) -> TransactionUnsigned {
58		use TransactionSigned::*;
59		use TransactionUnsigned::*;
60		match self {
61			Transaction7702Signed(tx) => Transaction7702Unsigned(tx.transaction_7702_unsigned),
62			Transaction2930Signed(tx) => Transaction2930Unsigned(tx.transaction_2930_unsigned),
63			Transaction1559Signed(tx) => Transaction1559Unsigned(tx.transaction_1559_unsigned),
64			Transaction4844Signed(tx) => Transaction4844Unsigned(tx.transaction_4844_unsigned),
65			TransactionLegacySigned(tx) =>
66				TransactionLegacyUnsigned(tx.transaction_legacy_unsigned),
67		}
68	}
69
70	/// Encode the Ethereum transaction into bytes.
71	pub fn signed_payload(&self) -> Vec<u8> {
72		use TransactionSigned::*;
73		let mut s = rlp::RlpStream::new();
74		match self {
75			Transaction7702Signed(ref tx) => {
76				s.append(&tx.transaction_7702_unsigned.r#type.value());
77				s.append(tx);
78			},
79			Transaction2930Signed(ref tx) => {
80				s.append(&tx.transaction_2930_unsigned.r#type.value());
81				s.append(tx);
82			},
83			Transaction1559Signed(ref tx) => {
84				s.append(&tx.transaction_1559_unsigned.r#type.value());
85				s.append(tx);
86			},
87			Transaction4844Signed(ref tx) => {
88				s.append(&tx.transaction_4844_unsigned.r#type.value());
89				s.append(tx);
90			},
91			TransactionLegacySigned(ref tx) => {
92				s.append(tx);
93			},
94		}
95
96		s.out().to_vec()
97	}
98
99	/// Decode the Ethereum transaction from bytes.
100	pub fn decode(data: &[u8]) -> Result<Self, rlp::DecoderError> {
101		if data.is_empty() {
102			return Err(rlp::DecoderError::RlpIsTooShort);
103		}
104		let first_byte = data[0];
105
106		// EIP-2718: Typed transactions use type identifiers in [0x00, 0x7f].
107		if first_byte <= 0x7f {
108			match first_byte {
109				TYPE_EIP2930 => rlp::decode::<Transaction2930Signed>(&data[1..]).map(Into::into),
110				TYPE_EIP1559 => rlp::decode::<Transaction1559Signed>(&data[1..]).map(Into::into),
111				TYPE_EIP4844 => rlp::decode::<Transaction4844Signed>(&data[1..]).map(Into::into),
112				TYPE_EIP7702 => rlp::decode::<Transaction7702Signed>(&data[1..]).map(Into::into),
113				_ => Err(rlp::DecoderError::Custom("Unknown transaction type")),
114			}
115		} else {
116			rlp::decode::<TransactionLegacySigned>(data).map(Into::into)
117		}
118	}
119}
120
121impl TransactionUnsigned {
122	/// Get a signed transaction payload with a dummy 65 bytes signature.
123	pub fn dummy_signed_payload(self) -> Vec<u8> {
124		const DUMMY_SIGNATURE: [u8; 65] = [1u8; 65];
125		self.with_signature(DUMMY_SIGNATURE).signed_payload()
126	}
127}
128
129/// See <https://eips.ethereum.org/EIPS/eip-155>
130impl Encodable for TransactionLegacyUnsigned {
131	fn rlp_append(&self, s: &mut rlp::RlpStream) {
132		if let Some(chain_id) = self.chain_id {
133			s.begin_list(9);
134			s.append(&self.nonce);
135			s.append(&self.gas_price);
136			s.append(&self.gas);
137			match self.to {
138				Some(ref to) => s.append(to),
139				None => s.append_empty_data(),
140			};
141			s.append(&self.value);
142			s.append(&self.input.0);
143			s.append(&chain_id);
144			s.append(&0u8);
145			s.append(&0u8);
146		} else {
147			s.begin_list(6);
148			s.append(&self.nonce);
149			s.append(&self.gas_price);
150			s.append(&self.gas);
151			match self.to {
152				Some(ref to) => s.append(to),
153				None => s.append_empty_data(),
154			};
155			s.append(&self.value);
156			s.append(&self.input.0);
157		}
158	}
159}
160
161impl Decodable for TransactionLegacyUnsigned {
162	fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
163		Ok(TransactionLegacyUnsigned {
164			nonce: rlp.val_at(0)?,
165			gas_price: rlp.val_at(1)?,
166			gas: rlp.val_at(2)?,
167			to: {
168				let to = rlp.at(3)?;
169				if to.is_empty() {
170					None
171				} else {
172					Some(to.as_val()?)
173				}
174			},
175			value: rlp.val_at(4)?,
176			input: Bytes(rlp.val_at(5)?),
177			chain_id: rlp.val_at(6).ok(),
178			..Default::default()
179		})
180	}
181}
182
183impl Encodable for TransactionLegacySigned {
184	fn rlp_append(&self, s: &mut rlp::RlpStream) {
185		let tx = &self.transaction_legacy_unsigned;
186
187		s.begin_list(9);
188		s.append(&tx.nonce);
189		s.append(&tx.gas_price);
190		s.append(&tx.gas);
191		match tx.to {
192			Some(ref to) => s.append(to),
193			None => s.append_empty_data(),
194		};
195		s.append(&tx.value);
196		s.append(&tx.input.0);
197
198		s.append(&self.v);
199		s.append(&self.r);
200		s.append(&self.s);
201	}
202}
203
204impl Encodable for AccessListEntry {
205	fn rlp_append(&self, s: &mut rlp::RlpStream) {
206		s.begin_list(2);
207		s.append(&self.address);
208		s.append_list(&self.storage_keys);
209	}
210}
211
212impl Decodable for AccessListEntry {
213	fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
214		Ok(AccessListEntry { address: rlp.val_at(0)?, storage_keys: rlp.list_at(1)? })
215	}
216}
217
218impl Encodable for AuthorizationListEntry {
219	fn rlp_append(&self, s: &mut rlp::RlpStream) {
220		s.begin_list(6);
221		s.append(&self.chain_id);
222		s.append(&self.address);
223		s.append(&self.nonce);
224		s.append(&self.y_parity);
225		s.append(&self.r);
226		s.append(&self.s);
227	}
228}
229
230impl Decodable for AuthorizationListEntry {
231	fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
232		Ok(AuthorizationListEntry {
233			chain_id: rlp.val_at(0)?,
234			address: rlp.val_at(1)?,
235			nonce: rlp.val_at(2)?,
236			y_parity: rlp.val_at(3)?,
237			r: rlp.val_at(4)?,
238			s: rlp.val_at(5)?,
239		})
240	}
241}
242
243/// See <https://eips.ethereum.org/EIPS/eip-1559>
244impl Encodable for Transaction1559Unsigned {
245	fn rlp_append(&self, s: &mut rlp::RlpStream) {
246		s.begin_list(9);
247		s.append(&self.chain_id);
248		s.append(&self.nonce);
249		s.append(&self.max_priority_fee_per_gas);
250		s.append(&self.max_fee_per_gas);
251		s.append(&self.gas);
252		match self.to {
253			Some(ref to) => s.append(to),
254			None => s.append_empty_data(),
255		};
256		s.append(&self.value);
257		s.append(&self.input.0);
258		s.append_list(&self.access_list);
259	}
260}
261
262/// See <https://eips.ethereum.org/EIPS/eip-1559>
263impl Encodable for Transaction1559Signed {
264	fn rlp_append(&self, s: &mut rlp::RlpStream) {
265		let tx = &self.transaction_1559_unsigned;
266		s.begin_list(12);
267		s.append(&tx.chain_id);
268		s.append(&tx.nonce);
269		s.append(&tx.max_priority_fee_per_gas);
270		s.append(&tx.max_fee_per_gas);
271		s.append(&tx.gas);
272		match tx.to {
273			Some(ref to) => s.append(to),
274			None => s.append_empty_data(),
275		};
276		s.append(&tx.value);
277		s.append(&tx.input.0);
278		s.append_list(&tx.access_list);
279
280		s.append(&self.y_parity);
281		s.append(&self.r);
282		s.append(&self.s);
283	}
284}
285
286impl Decodable for Transaction1559Signed {
287	fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
288		Ok(Transaction1559Signed {
289			transaction_1559_unsigned: {
290				Transaction1559Unsigned {
291					chain_id: rlp.val_at(0)?,
292					nonce: rlp.val_at(1)?,
293					max_priority_fee_per_gas: rlp.val_at(2)?,
294					max_fee_per_gas: rlp.val_at(3)?,
295					gas: rlp.val_at(4)?,
296					to: {
297						let to = rlp.at(5)?;
298						if to.is_empty() {
299							None
300						} else {
301							Some(to.as_val()?)
302						}
303					},
304					value: rlp.val_at(6)?,
305					input: Bytes(rlp.val_at(7)?),
306					access_list: rlp.list_at(8)?,
307					..Default::default()
308				}
309			},
310			y_parity: rlp.val_at(9)?,
311			r: rlp.val_at(10)?,
312			s: rlp.val_at(11)?,
313			..Default::default()
314		})
315	}
316}
317
318//See https://eips.ethereum.org/EIPS/eip-2930
319impl Encodable for Transaction2930Unsigned {
320	fn rlp_append(&self, s: &mut rlp::RlpStream) {
321		s.begin_list(8);
322		s.append(&self.chain_id);
323		s.append(&self.nonce);
324		s.append(&self.gas_price);
325		s.append(&self.gas);
326		match self.to {
327			Some(ref to) => s.append(to),
328			None => s.append_empty_data(),
329		};
330		s.append(&self.value);
331		s.append(&self.input.0);
332		s.append_list(&self.access_list);
333	}
334}
335
336//See https://eips.ethereum.org/EIPS/eip-2930
337impl Encodable for Transaction2930Signed {
338	fn rlp_append(&self, s: &mut rlp::RlpStream) {
339		let tx = &self.transaction_2930_unsigned;
340		s.begin_list(11);
341		s.append(&tx.chain_id);
342		s.append(&tx.nonce);
343		s.append(&tx.gas_price);
344		s.append(&tx.gas);
345		match tx.to {
346			Some(ref to) => s.append(to),
347			None => s.append_empty_data(),
348		};
349		s.append(&tx.value);
350		s.append(&tx.input.0);
351		s.append_list(&tx.access_list);
352		s.append(&self.y_parity);
353		s.append(&self.r);
354		s.append(&self.s);
355	}
356}
357
358impl Decodable for Transaction2930Signed {
359	fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
360		Ok(Transaction2930Signed {
361			transaction_2930_unsigned: {
362				Transaction2930Unsigned {
363					chain_id: rlp.val_at(0)?,
364					nonce: rlp.val_at(1)?,
365					gas_price: rlp.val_at(2)?,
366					gas: rlp.val_at(3)?,
367					to: {
368						let to = rlp.at(4)?;
369						if to.is_empty() {
370							None
371						} else {
372							Some(to.as_val()?)
373						}
374					},
375					value: rlp.val_at(5)?,
376					input: Bytes(rlp.val_at(6)?),
377					access_list: rlp.list_at(7)?,
378					..Default::default()
379				}
380			},
381			y_parity: rlp.val_at(8)?,
382			r: rlp.val_at(9)?,
383			s: rlp.val_at(10)?,
384			..Default::default()
385		})
386	}
387}
388
389//See https://eips.ethereum.org/EIPS/eip-7702
390impl Encodable for Transaction7702Unsigned {
391	fn rlp_append(&self, s: &mut rlp::RlpStream) {
392		s.begin_list(10);
393		s.append(&self.chain_id);
394		s.append(&self.nonce);
395		s.append(&self.max_priority_fee_per_gas);
396		s.append(&self.max_fee_per_gas);
397		s.append(&self.gas);
398		s.append(&self.to);
399		s.append(&self.value);
400		s.append(&self.input.0);
401		s.append_list(&self.access_list);
402		s.append_list(&self.authorization_list);
403	}
404}
405
406impl Decodable for Transaction7702Signed {
407	fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
408		Ok(Transaction7702Signed {
409			transaction_7702_unsigned: {
410				Transaction7702Unsigned {
411					chain_id: rlp.val_at(0)?,
412					nonce: rlp.val_at(1)?,
413					max_priority_fee_per_gas: rlp.val_at(2)?,
414					max_fee_per_gas: rlp.val_at(3)?,
415					gas_price: rlp.val_at(4)?,
416					gas: rlp.val_at(5)?,
417					to: rlp.val_at(6)?,
418					value: rlp.val_at(7)?,
419					input: Bytes(rlp.val_at(8)?),
420					access_list: rlp.list_at(9)?,
421					authorization_list: rlp.list_at(10)?,
422					r#type: Default::default(),
423				}
424			},
425			y_parity: rlp.val_at(11)?,
426			r: rlp.val_at(12)?,
427			s: rlp.val_at(13)?,
428			v: None,
429		})
430	}
431}
432
433impl Encodable for Transaction4844Unsigned {
434	fn rlp_append(&self, s: &mut rlp::RlpStream) {
435		s.begin_list(11);
436		s.append(&self.chain_id);
437		s.append(&self.nonce);
438		s.append(&self.max_priority_fee_per_gas);
439		s.append(&self.max_fee_per_gas);
440		s.append(&self.gas);
441		s.append(&self.to);
442		s.append(&self.value);
443		s.append(&self.input.0);
444		s.append_list(&self.access_list);
445		s.append(&self.max_fee_per_blob_gas);
446		s.append_list(&self.blob_versioned_hashes);
447	}
448}
449
450//See https://eips.ethereum.org/EIPS/eip-7702
451impl Encodable for Transaction7702Signed {
452	fn rlp_append(&self, s: &mut rlp::RlpStream) {
453		let tx = &self.transaction_7702_unsigned;
454		s.begin_list(13);
455		s.append(&tx.chain_id);
456		s.append(&tx.nonce);
457		s.append(&tx.max_priority_fee_per_gas);
458		s.append(&tx.max_fee_per_gas);
459		s.append(&tx.gas);
460		s.append(&tx.to);
461		s.append(&tx.value);
462		s.append(&tx.input.0);
463		s.append_list(&tx.access_list);
464		s.append_list(&tx.authorization_list);
465		s.append(&self.y_parity);
466		s.append(&self.r);
467		s.append(&self.s);
468	}
469}
470
471//See https://eips.ethereum.org/EIPS/eip-4844
472impl Encodable for Transaction4844Signed {
473	fn rlp_append(&self, s: &mut rlp::RlpStream) {
474		let tx = &self.transaction_4844_unsigned;
475		s.begin_list(14);
476		s.append(&tx.chain_id);
477		s.append(&tx.nonce);
478		s.append(&tx.max_priority_fee_per_gas);
479		s.append(&tx.max_fee_per_gas);
480		s.append(&tx.gas);
481		s.append(&tx.to);
482		s.append(&tx.value);
483		s.append(&tx.input.0);
484		s.append_list(&tx.access_list);
485		s.append(&tx.max_fee_per_blob_gas);
486		s.append_list(&tx.blob_versioned_hashes);
487		s.append(&self.y_parity);
488		s.append(&self.r);
489		s.append(&self.s);
490	}
491}
492
493impl Decodable for Transaction4844Signed {
494	fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
495		Ok(Transaction4844Signed {
496			transaction_4844_unsigned: {
497				Transaction4844Unsigned {
498					chain_id: rlp.val_at(0)?,
499					nonce: rlp.val_at(1)?,
500					max_priority_fee_per_gas: rlp.val_at(2)?,
501					max_fee_per_gas: rlp.val_at(3)?,
502					gas: rlp.val_at(4)?,
503					to: rlp.val_at(5)?,
504					value: rlp.val_at(6)?,
505					input: Bytes(rlp.val_at(7)?),
506					access_list: rlp.list_at(8)?,
507					max_fee_per_blob_gas: rlp.val_at(9)?,
508					blob_versioned_hashes: rlp.list_at(10)?,
509					..Default::default()
510				}
511			},
512			y_parity: rlp.val_at(11)?,
513			r: rlp.val_at(12)?,
514			s: rlp.val_at(13)?,
515		})
516	}
517}
518
519/// See <https://eips.ethereum.org/EIPS/eip-155>
520impl Decodable for TransactionLegacySigned {
521	fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
522		let v: U256 = rlp.val_at(6)?;
523
524		let extract_chain_id = |v: U256| {
525			if v.ge(&35u32.into()) {
526				Some((v - 35) / 2)
527			} else {
528				None
529			}
530		};
531
532		Ok(TransactionLegacySigned {
533			transaction_legacy_unsigned: {
534				TransactionLegacyUnsigned {
535					nonce: rlp.val_at(0)?,
536					gas_price: rlp.val_at(1)?,
537					gas: rlp.val_at(2)?,
538					to: {
539						let to = rlp.at(3)?;
540						if to.is_empty() {
541							None
542						} else {
543							Some(to.as_val()?)
544						}
545					},
546					value: rlp.val_at(4)?,
547					input: Bytes(rlp.val_at(5)?),
548					chain_id: extract_chain_id(v).map(|v| v.into()),
549					r#type: TypeLegacy {},
550				}
551			},
552			v,
553			r: rlp.val_at(7)?,
554			s: rlp.val_at(8)?,
555		})
556	}
557}
558
559#[cfg(test)]
560mod test {
561	use super::*;
562
563	#[test]
564	fn encode_decode_tx_works() {
565		let txs = [
566			// Legacy
567			(
568				"f86080808301e24194095e7baea6a6c7c4c2dfeb977efac326af552d87808025a0fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0a06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8",
569				r#"
570				{
571					"chainId": "0x1",
572					"gas": "0x1e241",
573					"gasPrice": "0x0",
574					"input": "0x",
575					"nonce": "0x0",
576					"to": "0x095e7baea6a6c7c4c2dfeb977efac326af552d87",
577					"type": "0x0",
578					"value": "0x0",
579					"r": "0xfe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0",
580					"s": "0x6de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8",
581					"v": "0x25"
582				}
583				"#
584			),
585			// type 1: EIP2930
586			(
587				"01f89b0180808301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080a0fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0a06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8",
588				r#"
589				{
590					"accessList": [
591						{
592						"address": "0x0000000000000000000000000000000000000001",
593						"storageKeys": ["0x0000000000000000000000000000000000000000000000000000000000000000"]
594						}
595					],
596					"chainId": "0x1",
597					"gas": "0x1e241",
598					"gasPrice": "0x0",
599					"input": "0x",
600					"nonce": "0x0",
601					"to": "0x095e7baea6a6c7c4c2dfeb977efac326af552d87",
602					"type": "0x1",
603					"value": "0x0",
604					"r": "0xfe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0",
605					"s": "0x6de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8",
606					"yParity": "0x0"
607				}
608				"#
609			),
610			// type 2: EIP1559
611			(
612				"02f89c018080018301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080a0fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0a06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8",
613				r#"
614				{
615					"accessList": [
616						{
617							"address": "0x0000000000000000000000000000000000000001",
618							"storageKeys": ["0x0000000000000000000000000000000000000000000000000000000000000000"]
619						}
620					],
621					"chainId": "0x1",
622					"gas": "0x1e241",
623					"gasPrice": "0x0",
624					"input": "0x",
625					"maxFeePerGas": "0x1",
626					"maxPriorityFeePerGas": "0x0",
627					"nonce": "0x0",
628					"to": "0x095e7baea6a6c7c4c2dfeb977efac326af552d87",
629					"type": "0x2",
630					"value": "0x0",
631					"r": "0xfe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0",
632					"s": "0x6de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8",
633					"yParity": "0x0"
634
635				}
636				"#
637			),
638			// type 3: EIP4844
639			(
640
641				"03f8bf018002018301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080e1a0000000000000000000000000000000000000000000000000000000000000000080a0fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0a06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8",
642				r#"
643				{
644					"accessList": [
645						{
646						"address": "0x0000000000000000000000000000000000000001",
647						"storageKeys": ["0x0000000000000000000000000000000000000000000000000000000000000000"]
648						}
649					],
650					"blobVersionedHashes": ["0x0000000000000000000000000000000000000000000000000000000000000000"],
651					"chainId": "0x1",
652					"gas": "0x1e241",
653					"input": "0x",
654					"maxFeePerBlobGas": "0x0",
655					"maxFeePerGas": "0x1",
656					"maxPriorityFeePerGas": "0x2",
657					"nonce": "0x0",
658					"to": "0x095e7baea6a6c7c4c2dfeb977efac326af552d87",
659					"type": "0x3",
660					"value": "0x0",
661					"r": "0xfe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0",
662					"s": "0x6de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8",
663					"yParity": "0x0"
664				}
665				"#
666			)
667		];
668
669		for (tx, json) in txs {
670			let raw_tx = alloy_core::hex::decode(tx).unwrap();
671			let tx = TransactionSigned::decode(&raw_tx).unwrap();
672			assert_eq!(tx.signed_payload(), raw_tx);
673			let expected_tx = serde_json::from_str(json).unwrap();
674			assert_eq!(tx, expected_tx);
675		}
676	}
677
678	#[test]
679	fn dummy_signed_payload_works() {
680		let tx: TransactionUnsigned = TransactionLegacyUnsigned {
681			chain_id: Some(596.into()),
682			gas: U256::from(21000),
683			nonce: U256::from(1),
684			gas_price: U256::from("0x640000006a"),
685			to: Some(Account::from(subxt_signer::eth::dev::baltathar()).address()),
686			value: U256::from(123123),
687			input: Bytes(vec![]),
688			r#type: TypeLegacy,
689		}
690		.into();
691
692		let dummy_signed_payload = tx.clone().dummy_signed_payload();
693		let payload = Account::default().sign_transaction(tx).signed_payload();
694		assert_eq!(dummy_signed_payload.len(), payload.len());
695	}
696}