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
240impl AuthorizationListEntry {
241	/// RLP encode only the unsigned part (chain_id, address, nonce) for signing
242	pub fn rlp_encode_unsigned(&self) -> Vec<u8> {
243		let mut s = rlp::RlpStream::new_list(3);
244		s.append(&self.chain_id);
245		s.append(&self.address);
246		s.append(&self.nonce);
247		s.out().to_vec()
248	}
249}
250
251/// See <https://eips.ethereum.org/EIPS/eip-1559>
252impl Encodable for Transaction1559Unsigned {
253	fn rlp_append(&self, s: &mut rlp::RlpStream) {
254		s.begin_list(9);
255		s.append(&self.chain_id);
256		s.append(&self.nonce);
257		s.append(&self.max_priority_fee_per_gas);
258		s.append(&self.max_fee_per_gas);
259		s.append(&self.gas);
260		match self.to {
261			Some(ref to) => s.append(to),
262			None => s.append_empty_data(),
263		};
264		s.append(&self.value);
265		s.append(&self.input.0);
266		s.append_list(&self.access_list);
267	}
268}
269
270/// See <https://eips.ethereum.org/EIPS/eip-1559>
271impl Encodable for Transaction1559Signed {
272	fn rlp_append(&self, s: &mut rlp::RlpStream) {
273		let tx = &self.transaction_1559_unsigned;
274		s.begin_list(12);
275		s.append(&tx.chain_id);
276		s.append(&tx.nonce);
277		s.append(&tx.max_priority_fee_per_gas);
278		s.append(&tx.max_fee_per_gas);
279		s.append(&tx.gas);
280		match tx.to {
281			Some(ref to) => s.append(to),
282			None => s.append_empty_data(),
283		};
284		s.append(&tx.value);
285		s.append(&tx.input.0);
286		s.append_list(&tx.access_list);
287
288		s.append(&self.y_parity);
289		s.append(&self.r);
290		s.append(&self.s);
291	}
292}
293
294impl Decodable for Transaction1559Signed {
295	fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
296		Ok(Transaction1559Signed {
297			transaction_1559_unsigned: {
298				Transaction1559Unsigned {
299					chain_id: rlp.val_at(0)?,
300					nonce: rlp.val_at(1)?,
301					max_priority_fee_per_gas: rlp.val_at(2)?,
302					max_fee_per_gas: rlp.val_at(3)?,
303					gas: rlp.val_at(4)?,
304					to: {
305						let to = rlp.at(5)?;
306						if to.is_empty() { None } else { Some(to.as_val()?) }
307					},
308					value: rlp.val_at(6)?,
309					input: Bytes(rlp.val_at(7)?),
310					access_list: rlp.list_at(8)?,
311					..Default::default()
312				}
313			},
314			y_parity: rlp.val_at(9)?,
315			r: rlp.val_at(10)?,
316			s: rlp.val_at(11)?,
317			..Default::default()
318		})
319	}
320}
321
322// See https://eips.ethereum.org/EIPS/eip-2930
323impl Encodable for Transaction2930Unsigned {
324	fn rlp_append(&self, s: &mut rlp::RlpStream) {
325		s.begin_list(8);
326		s.append(&self.chain_id);
327		s.append(&self.nonce);
328		s.append(&self.gas_price);
329		s.append(&self.gas);
330		match self.to {
331			Some(ref to) => s.append(to),
332			None => s.append_empty_data(),
333		};
334		s.append(&self.value);
335		s.append(&self.input.0);
336		s.append_list(&self.access_list);
337	}
338}
339
340// See https://eips.ethereum.org/EIPS/eip-2930
341impl Encodable for Transaction2930Signed {
342	fn rlp_append(&self, s: &mut rlp::RlpStream) {
343		let tx = &self.transaction_2930_unsigned;
344		s.begin_list(11);
345		s.append(&tx.chain_id);
346		s.append(&tx.nonce);
347		s.append(&tx.gas_price);
348		s.append(&tx.gas);
349		match tx.to {
350			Some(ref to) => s.append(to),
351			None => s.append_empty_data(),
352		};
353		s.append(&tx.value);
354		s.append(&tx.input.0);
355		s.append_list(&tx.access_list);
356		s.append(&self.y_parity);
357		s.append(&self.r);
358		s.append(&self.s);
359	}
360}
361
362impl Decodable for Transaction2930Signed {
363	fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
364		Ok(Transaction2930Signed {
365			transaction_2930_unsigned: {
366				Transaction2930Unsigned {
367					chain_id: rlp.val_at(0)?,
368					nonce: rlp.val_at(1)?,
369					gas_price: rlp.val_at(2)?,
370					gas: rlp.val_at(3)?,
371					to: {
372						let to = rlp.at(4)?;
373						if to.is_empty() { None } else { Some(to.as_val()?) }
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: rlp.val_at(4)?,
416					to: rlp.val_at(5)?,
417					value: rlp.val_at(6)?,
418					input: Bytes(rlp.val_at(7)?),
419					access_list: rlp.list_at(8)?,
420					authorization_list: rlp.list_at(9)?,
421					r#type: Default::default(),
422				}
423			},
424			y_parity: rlp.val_at(10)?,
425			r: rlp.val_at(11)?,
426			s: rlp.val_at(12)?,
427			v: None,
428		})
429	}
430}
431
432impl Encodable for Transaction4844Unsigned {
433	fn rlp_append(&self, s: &mut rlp::RlpStream) {
434		s.begin_list(11);
435		s.append(&self.chain_id);
436		s.append(&self.nonce);
437		s.append(&self.max_priority_fee_per_gas);
438		s.append(&self.max_fee_per_gas);
439		s.append(&self.gas);
440		s.append(&self.to);
441		s.append(&self.value);
442		s.append(&self.input.0);
443		s.append_list(&self.access_list);
444		s.append(&self.max_fee_per_blob_gas);
445		s.append_list(&self.blob_versioned_hashes);
446	}
447}
448
449// See https://eips.ethereum.org/EIPS/eip-7702
450impl Encodable for Transaction7702Signed {
451	fn rlp_append(&self, s: &mut rlp::RlpStream) {
452		let tx = &self.transaction_7702_unsigned;
453		s.begin_list(13);
454		s.append(&tx.chain_id);
455		s.append(&tx.nonce);
456		s.append(&tx.max_priority_fee_per_gas);
457		s.append(&tx.max_fee_per_gas);
458		s.append(&tx.gas);
459		s.append(&tx.to);
460		s.append(&tx.value);
461		s.append(&tx.input.0);
462		s.append_list(&tx.access_list);
463		s.append_list(&tx.authorization_list);
464		s.append(&self.y_parity);
465		s.append(&self.r);
466		s.append(&self.s);
467	}
468}
469
470// See https://eips.ethereum.org/EIPS/eip-4844
471impl Encodable for Transaction4844Signed {
472	fn rlp_append(&self, s: &mut rlp::RlpStream) {
473		let tx = &self.transaction_4844_unsigned;
474		s.begin_list(14);
475		s.append(&tx.chain_id);
476		s.append(&tx.nonce);
477		s.append(&tx.max_priority_fee_per_gas);
478		s.append(&tx.max_fee_per_gas);
479		s.append(&tx.gas);
480		s.append(&tx.to);
481		s.append(&tx.value);
482		s.append(&tx.input.0);
483		s.append_list(&tx.access_list);
484		s.append(&tx.max_fee_per_blob_gas);
485		s.append_list(&tx.blob_versioned_hashes);
486		s.append(&self.y_parity);
487		s.append(&self.r);
488		s.append(&self.s);
489	}
490}
491
492impl Decodable for Transaction4844Signed {
493	fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
494		Ok(Transaction4844Signed {
495			transaction_4844_unsigned: {
496				Transaction4844Unsigned {
497					chain_id: rlp.val_at(0)?,
498					nonce: rlp.val_at(1)?,
499					max_priority_fee_per_gas: rlp.val_at(2)?,
500					max_fee_per_gas: rlp.val_at(3)?,
501					gas: rlp.val_at(4)?,
502					to: rlp.val_at(5)?,
503					value: rlp.val_at(6)?,
504					input: Bytes(rlp.val_at(7)?),
505					access_list: rlp.list_at(8)?,
506					max_fee_per_blob_gas: rlp.val_at(9)?,
507					blob_versioned_hashes: rlp.list_at(10)?,
508					..Default::default()
509				}
510			},
511			y_parity: rlp.val_at(11)?,
512			r: rlp.val_at(12)?,
513			s: rlp.val_at(13)?,
514		})
515	}
516}
517
518/// See <https://eips.ethereum.org/EIPS/eip-155>
519impl Decodable for TransactionLegacySigned {
520	fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
521		let v: U256 = rlp.val_at(6)?;
522
523		let extract_chain_id = |v: U256| {
524			if v.ge(&35u32.into()) { Some((v - 35) / 2) } else { None }
525		};
526
527		Ok(TransactionLegacySigned {
528			transaction_legacy_unsigned: {
529				TransactionLegacyUnsigned {
530					nonce: rlp.val_at(0)?,
531					gas_price: rlp.val_at(1)?,
532					gas: rlp.val_at(2)?,
533					to: {
534						let to = rlp.at(3)?;
535						if to.is_empty() { None } else { Some(to.as_val()?) }
536					},
537					value: rlp.val_at(4)?,
538					input: Bytes(rlp.val_at(5)?),
539					chain_id: extract_chain_id(v).map(|v| v.into()),
540					r#type: TypeLegacy {},
541				}
542			},
543			v,
544			r: rlp.val_at(7)?,
545			s: rlp.val_at(8)?,
546		})
547	}
548}
549
550#[cfg(test)]
551mod test {
552	use super::*;
553
554	#[test]
555	fn encode_decode_tx_works() {
556		let txs = [
557			// Legacy
558			(
559				"f86080808301e24194095e7baea6a6c7c4c2dfeb977efac326af552d87808025a0fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0a06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8",
560				r#"
561				{
562					"chainId": "0x1",
563					"gas": "0x1e241",
564					"gasPrice": "0x0",
565					"input": "0x",
566					"nonce": "0x0",
567					"to": "0x095e7baea6a6c7c4c2dfeb977efac326af552d87",
568					"type": "0x0",
569					"value": "0x0",
570					"r": "0xfe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0",
571					"s": "0x6de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8",
572					"v": "0x25"
573				}
574				"#,
575			),
576			// type 1: EIP2930
577			(
578				"01f89b0180808301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080a0fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0a06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8",
579				r#"
580				{
581					"accessList": [
582						{
583						"address": "0x0000000000000000000000000000000000000001",
584						"storageKeys": ["0x0000000000000000000000000000000000000000000000000000000000000000"]
585						}
586					],
587					"chainId": "0x1",
588					"gas": "0x1e241",
589					"gasPrice": "0x0",
590					"input": "0x",
591					"nonce": "0x0",
592					"to": "0x095e7baea6a6c7c4c2dfeb977efac326af552d87",
593					"type": "0x1",
594					"value": "0x0",
595					"r": "0xfe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0",
596					"s": "0x6de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8",
597					"yParity": "0x0"
598				}
599				"#,
600			),
601			// type 2: EIP1559
602			(
603				"02f89c018080018301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080a0fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0a06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8",
604				r#"
605				{
606					"accessList": [
607						{
608							"address": "0x0000000000000000000000000000000000000001",
609							"storageKeys": ["0x0000000000000000000000000000000000000000000000000000000000000000"]
610						}
611					],
612					"chainId": "0x1",
613					"gas": "0x1e241",
614					"gasPrice": "0x0",
615					"input": "0x",
616					"maxFeePerGas": "0x1",
617					"maxPriorityFeePerGas": "0x0",
618					"nonce": "0x0",
619					"to": "0x095e7baea6a6c7c4c2dfeb977efac326af552d87",
620					"type": "0x2",
621					"value": "0x0",
622					"r": "0xfe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0",
623					"s": "0x6de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8",
624					"yParity": "0x0"
625
626				}
627				"#,
628			),
629			// type 3: EIP4844
630			(
631				"03f8bf018002018301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080e1a0000000000000000000000000000000000000000000000000000000000000000080a0fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0a06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8",
632				r#"
633				{
634					"accessList": [
635						{
636						"address": "0x0000000000000000000000000000000000000001",
637						"storageKeys": ["0x0000000000000000000000000000000000000000000000000000000000000000"]
638						}
639					],
640					"blobVersionedHashes": ["0x0000000000000000000000000000000000000000000000000000000000000000"],
641					"chainId": "0x1",
642					"gas": "0x1e241",
643					"input": "0x",
644					"maxFeePerBlobGas": "0x0",
645					"maxFeePerGas": "0x1",
646					"maxPriorityFeePerGas": "0x2",
647					"nonce": "0x0",
648					"to": "0x095e7baea6a6c7c4c2dfeb977efac326af552d87",
649					"type": "0x3",
650					"value": "0x0",
651					"r": "0xfe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0",
652					"s": "0x6de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8",
653					"yParity": "0x0"
654				}
655				"#,
656			),
657		];
658
659		for (tx, json) in txs {
660			let raw_tx = alloy_core::hex::decode(tx).unwrap();
661			let tx = TransactionSigned::decode(&raw_tx).unwrap();
662			assert_eq!(tx.signed_payload(), raw_tx);
663			let expected_tx =
664				serde_json::from_str::<pallet_revive_types::runtime_api::TransactionSignedV1>(json)
665					.unwrap();
666			assert_eq!(
667				pallet_revive_types::runtime_api::TransactionSignedV1::from(tx),
668				expected_tx
669			);
670		}
671	}
672
673	#[test]
674	fn encode_decode_7702_tx_works() {
675		let tx = TransactionSigned::Transaction7702Signed(Transaction7702Signed {
676			transaction_7702_unsigned: Transaction7702Unsigned {
677				chain_id: U256::from(1),
678				nonce: U256::zero(),
679				max_priority_fee_per_gas: U256::zero(),
680				max_fee_per_gas: U256::from(1),
681				gas: U256::from(0x1e241),
682				to: "0x095e7baea6a6c7c4c2dfeb977efac326af552d87".parse().unwrap(),
683				value: U256::zero(),
684				input: Bytes(vec![]),
685				access_list: vec![AccessListEntry {
686					address: H160::from_low_u64_be(1),
687					storage_keys: vec![H256::zero()],
688				}],
689				authorization_list: vec![AuthorizationListEntry {
690					chain_id: U256::from(1),
691					address: H160::from_low_u64_be(42),
692					nonce: U256::zero(),
693					y_parity: U256::zero(),
694					r: U256::from(1),
695					s: U256::from(2),
696				}],
697				r#type: TypeEip7702 {},
698			},
699			y_parity: U256::zero(),
700			r: U256::from(1),
701			s: U256::from(2),
702			v: None,
703		});
704
705		let encoded = tx.signed_payload();
706		let decoded = TransactionSigned::decode(&encoded).unwrap();
707		assert_eq!(tx, decoded);
708	}
709
710	#[test]
711	fn dummy_signed_payload_works() {
712		let tx: TransactionUnsigned = TransactionLegacyUnsigned {
713			chain_id: Some(596.into()),
714			gas: U256::from(21000),
715			nonce: U256::from(1),
716			gas_price: U256::from("0x640000006a"),
717			to: Some(Account::from(subxt_signer::eth::dev::baltathar()).address()),
718			value: U256::from(123123),
719			input: Bytes(vec![]),
720			r#type: TypeLegacy,
721		}
722		.into();
723
724		let dummy_signed_payload = tx.clone().dummy_signed_payload();
725		let payload = Account::default().sign_transaction(tx).signed_payload();
726		assert_eq!(dummy_signed_payload.len(), payload.len());
727	}
728
729	#[test]
730	fn rlp_codec_is_compatible_with_ethereum() {
731		// RLP encoded transactions
732		let test_cases = [
733			// Legacy
734			"f86080808301e24194095e7baea6a6c7c4c2dfeb977efac326af552d87808025a0fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0a06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8",
735			// EIP-2930
736			"01f89b0180808301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080a0fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0a06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8",
737			// EIP-1559
738			"02f89c018080018301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080a0fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0a06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8",
739			// EIP4844
740			"03f89783aa36a701832dc6c083fc546c8261a8947f8b1ca29f95274e06367b60fc4a539e4910fd0c865af3107a400080c0831e8480e1a0018fd423d1ad106395f04abac797217d4dece29da3ba649d9aa4da70e98fa6ff80a028d2350a1bfa5043de1533911143eb5c43815a58039121a0ccf124870620fca6a0157eca4963615cd3926538af88e529cfa3baf6c55787a33f79c25babe9f5db2b",
741		];
742
743		for hex_tx in test_cases {
744			let rlp_encoded_tx = alloy_core::hex::decode(hex_tx).unwrap();
745
746			// RLP decode using this implementation
747			let tx_revive = TransactionSigned::decode(&rlp_encoded_tx).unwrap();
748
749			// RLP encode using this implementation
750			let rlp_encoded_revive = tx_revive.signed_payload();
751
752			// Verify round-trip: our encoding should decode back to the same transaction
753			assert_eq!(rlp_encoded_tx, rlp_encoded_revive);
754		}
755	}
756}