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