From 4f5f802df6eaadf7709faa992bc49283edbd68ef Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 11 Nov 2025 16:40:12 +0100 Subject: [PATCH 1/7] Fix eth transaction decoding --- .../frame/revive/src/evm/api/rlp_codec.rs | 979 +++++++++--------- 1 file changed, 498 insertions(+), 481 deletions(-) diff --git a/substrate/frame/revive/src/evm/api/rlp_codec.rs b/substrate/frame/revive/src/evm/api/rlp_codec.rs index 0bee83f340165..3e45327a66669 100644 --- a/substrate/frame/revive/src/evm/api/rlp_codec.rs +++ b/substrate/frame/revive/src/evm/api/rlp_codec.rs @@ -22,540 +22,557 @@ use alloc::vec::Vec; use rlp::{Decodable, Encodable}; impl TransactionUnsigned { - /// Return the bytes to be signed by the private key. - pub fn unsigned_payload(&self) -> Vec { - use TransactionUnsigned::*; - let mut s = rlp::RlpStream::new(); - match self { - Transaction7702Unsigned(ref tx) => { - s.append(&tx.r#type.value()); - s.append(tx); - }, - Transaction2930Unsigned(ref tx) => { - s.append(&tx.r#type.value()); - s.append(tx); - }, - Transaction1559Unsigned(ref tx) => { - s.append(&tx.r#type.value()); - s.append(tx); - }, - Transaction4844Unsigned(ref tx) => { - s.append(&tx.r#type.value()); - s.append(tx); - }, - TransactionLegacyUnsigned(ref tx) => { - s.append(tx); - }, - } - - s.out().to_vec() - } + /// Return the bytes to be signed by the private key. + pub fn unsigned_payload(&self) -> Vec { + use TransactionUnsigned::*; + let mut s = rlp::RlpStream::new(); + match self { + Transaction7702Unsigned(ref tx) => { + s.append(&tx.r#type.value()); + s.append(tx); + } + Transaction2930Unsigned(ref tx) => { + s.append(&tx.r#type.value()); + s.append(tx); + } + Transaction1559Unsigned(ref tx) => { + s.append(&tx.r#type.value()); + s.append(tx); + } + Transaction4844Unsigned(ref tx) => { + s.append(&tx.r#type.value()); + s.append(tx); + } + TransactionLegacyUnsigned(ref tx) => { + s.append(tx); + } + } + + s.out().to_vec() + } } impl TransactionSigned { - /// Extract the unsigned transaction from a signed transaction. - pub fn unsigned(self) -> TransactionUnsigned { - use TransactionSigned::*; - use TransactionUnsigned::*; - match self { - Transaction7702Signed(tx) => Transaction7702Unsigned(tx.transaction_7702_unsigned), - Transaction2930Signed(tx) => Transaction2930Unsigned(tx.transaction_2930_unsigned), - Transaction1559Signed(tx) => Transaction1559Unsigned(tx.transaction_1559_unsigned), - Transaction4844Signed(tx) => Transaction4844Unsigned(tx.transaction_4844_unsigned), - TransactionLegacySigned(tx) => - TransactionLegacyUnsigned(tx.transaction_legacy_unsigned), - } - } - - /// Encode the Ethereum transaction into bytes. - pub fn signed_payload(&self) -> Vec { - use TransactionSigned::*; - let mut s = rlp::RlpStream::new(); - match self { - Transaction7702Signed(ref tx) => { - s.append(&tx.transaction_7702_unsigned.r#type.value()); - s.append(tx); - }, - Transaction2930Signed(ref tx) => { - s.append(&tx.transaction_2930_unsigned.r#type.value()); - s.append(tx); - }, - Transaction1559Signed(ref tx) => { - s.append(&tx.transaction_1559_unsigned.r#type.value()); - s.append(tx); - }, - Transaction4844Signed(ref tx) => { - s.append(&tx.transaction_4844_unsigned.r#type.value()); - s.append(tx); - }, - TransactionLegacySigned(ref tx) => { - s.append(tx); - }, - } - - s.out().to_vec() - } - - /// Decode the Ethereum transaction from bytes. - pub fn decode(data: &[u8]) -> Result { - if data.len() < 1 { - return Err(rlp::DecoderError::RlpIsTooShort); - } - match data[0] { - TYPE_EIP2930 => rlp::decode::(&data[1..]).map(Into::into), - TYPE_EIP1559 => rlp::decode::(&data[1..]).map(Into::into), - TYPE_EIP4844 => rlp::decode::(&data[1..]).map(Into::into), - TYPE_EIP7702 => rlp::decode::(&data[1..]).map(Into::into), - _ => rlp::decode::(data).map(Into::into), - } - } + /// Extract the unsigned transaction from a signed transaction. + pub fn unsigned(self) -> TransactionUnsigned { + use TransactionSigned::*; + use TransactionUnsigned::*; + match self { + Transaction7702Signed(tx) => Transaction7702Unsigned(tx.transaction_7702_unsigned), + Transaction2930Signed(tx) => Transaction2930Unsigned(tx.transaction_2930_unsigned), + Transaction1559Signed(tx) => Transaction1559Unsigned(tx.transaction_1559_unsigned), + Transaction4844Signed(tx) => Transaction4844Unsigned(tx.transaction_4844_unsigned), + TransactionLegacySigned(tx) => { + TransactionLegacyUnsigned(tx.transaction_legacy_unsigned) + } + } + } + + /// Encode the Ethereum transaction into bytes. + pub fn signed_payload(&self) -> Vec { + use TransactionSigned::*; + let mut s = rlp::RlpStream::new(); + match self { + Transaction7702Signed(ref tx) => { + s.append(&tx.transaction_7702_unsigned.r#type.value()); + s.append(tx); + } + Transaction2930Signed(ref tx) => { + s.append(&tx.transaction_2930_unsigned.r#type.value()); + s.append(tx); + } + Transaction1559Signed(ref tx) => { + s.append(&tx.transaction_1559_unsigned.r#type.value()); + s.append(tx); + } + Transaction4844Signed(ref tx) => { + s.append(&tx.transaction_4844_unsigned.r#type.value()); + s.append(tx); + } + TransactionLegacySigned(ref tx) => { + s.append(tx); + } + } + + s.out().to_vec() + } + + /// Decode the Ethereum transaction from bytes. + /// According to EIP-2718, legacy transactions start with a byte in [0xc0, 0xff] (RLP list), + /// while typed transactions use type identifiers in [0x00, 0x7f]. + pub fn decode(data: &[u8]) -> Result { + if data.is_empty() { + return Err(rlp::DecoderError::RlpIsTooShort); + } + let first_byte = data[0]; + + // Typed transactions: first byte in [0x00, 0x7f] + if first_byte <= 0x7f { + match first_byte { + TYPE_EIP2930 => rlp::decode::(&data[1..]).map(Into::into), + TYPE_EIP1559 => rlp::decode::(&data[1..]).map(Into::into), + TYPE_EIP4844 => rlp::decode::(&data[1..]).map(Into::into), + TYPE_EIP7702 => rlp::decode::(&data[1..]).map(Into::into), + _ => Err(rlp::DecoderError::Custom("Unknown transaction type")), + } + } + // Legacy transactions: first byte in [0xc0, 0xff] + else if first_byte >= 0xc0 { + rlp::decode::(data).map(Into::into) + } else { + Err(rlp::DecoderError::Custom("Invalid transaction encoding")) + } + } } impl TransactionUnsigned { - /// Get a signed transaction payload with a dummy 65 bytes signature. - pub fn dummy_signed_payload(self) -> Vec { - const DUMMY_SIGNATURE: [u8; 65] = [1u8; 65]; - self.with_signature(DUMMY_SIGNATURE).signed_payload() - } + /// Get a signed transaction payload with a dummy 65 bytes signature. + pub fn dummy_signed_payload(self) -> Vec { + const DUMMY_SIGNATURE: [u8; 65] = [1u8; 65]; + self.with_signature(DUMMY_SIGNATURE).signed_payload() + } } /// See impl Encodable for TransactionLegacyUnsigned { - fn rlp_append(&self, s: &mut rlp::RlpStream) { - if let Some(chain_id) = self.chain_id { - s.begin_list(9); - s.append(&self.nonce); - s.append(&self.gas_price); - s.append(&self.gas); - match self.to { - Some(ref to) => s.append(to), - None => s.append_empty_data(), - }; - s.append(&self.value); - s.append(&self.input.0); - s.append(&chain_id); - s.append(&0u8); - s.append(&0u8); - } else { - s.begin_list(6); - s.append(&self.nonce); - s.append(&self.gas_price); - s.append(&self.gas); - match self.to { - Some(ref to) => s.append(to), - None => s.append_empty_data(), - }; - s.append(&self.value); - s.append(&self.input.0); - } - } + fn rlp_append(&self, s: &mut rlp::RlpStream) { + if let Some(chain_id) = self.chain_id { + s.begin_list(9); + s.append(&self.nonce); + s.append(&self.gas_price); + s.append(&self.gas); + match self.to { + Some(ref to) => s.append(to), + None => s.append_empty_data(), + }; + s.append(&self.value); + s.append(&self.input.0); + s.append(&chain_id); + s.append(&0u8); + s.append(&0u8); + } else { + s.begin_list(6); + s.append(&self.nonce); + s.append(&self.gas_price); + s.append(&self.gas); + match self.to { + Some(ref to) => s.append(to), + None => s.append_empty_data(), + }; + s.append(&self.value); + s.append(&self.input.0); + } + } } impl Decodable for TransactionLegacyUnsigned { - fn decode(rlp: &rlp::Rlp) -> Result { - Ok(TransactionLegacyUnsigned { - nonce: rlp.val_at(0)?, - gas_price: rlp.val_at(1)?, - gas: rlp.val_at(2)?, - to: { - let to = rlp.at(3)?; - if to.is_empty() { - None - } else { - Some(to.as_val()?) - } - }, - value: rlp.val_at(4)?, - input: Bytes(rlp.val_at(5)?), - chain_id: rlp.val_at(6).ok(), - ..Default::default() - }) - } + fn decode(rlp: &rlp::Rlp) -> Result { + Ok(TransactionLegacyUnsigned { + nonce: rlp.val_at(0)?, + gas_price: rlp.val_at(1)?, + gas: rlp.val_at(2)?, + to: { + let to = rlp.at(3)?; + if to.is_empty() { + None + } else { + Some(to.as_val()?) + } + }, + value: rlp.val_at(4)?, + input: Bytes(rlp.val_at(5)?), + chain_id: rlp.val_at(6).ok(), + ..Default::default() + }) + } } impl Encodable for TransactionLegacySigned { - fn rlp_append(&self, s: &mut rlp::RlpStream) { - let tx = &self.transaction_legacy_unsigned; - - s.begin_list(9); - s.append(&tx.nonce); - s.append(&tx.gas_price); - s.append(&tx.gas); - match tx.to { - Some(ref to) => s.append(to), - None => s.append_empty_data(), - }; - s.append(&tx.value); - s.append(&tx.input.0); - - s.append(&self.v); - s.append(&self.r); - s.append(&self.s); - } + fn rlp_append(&self, s: &mut rlp::RlpStream) { + let tx = &self.transaction_legacy_unsigned; + + s.begin_list(9); + s.append(&tx.nonce); + s.append(&tx.gas_price); + s.append(&tx.gas); + match tx.to { + Some(ref to) => s.append(to), + None => s.append_empty_data(), + }; + s.append(&tx.value); + s.append(&tx.input.0); + + s.append(&self.v); + s.append(&self.r); + s.append(&self.s); + } } impl Encodable for AccessListEntry { - fn rlp_append(&self, s: &mut rlp::RlpStream) { - s.begin_list(2); - s.append(&self.address); - s.append_list(&self.storage_keys); - } + fn rlp_append(&self, s: &mut rlp::RlpStream) { + s.begin_list(2); + s.append(&self.address); + s.append_list(&self.storage_keys); + } } impl Decodable for AccessListEntry { - fn decode(rlp: &rlp::Rlp) -> Result { - Ok(AccessListEntry { address: rlp.val_at(0)?, storage_keys: rlp.list_at(1)? }) - } + fn decode(rlp: &rlp::Rlp) -> Result { + Ok(AccessListEntry { + address: rlp.val_at(0)?, + storage_keys: rlp.list_at(1)?, + }) + } } impl Encodable for AuthorizationListEntry { - fn rlp_append(&self, s: &mut rlp::RlpStream) { - s.begin_list(6); - s.append(&self.chain_id); - s.append(&self.address); - s.append(&self.nonce); - s.append(&self.y_parity); - s.append(&self.r); - s.append(&self.s); - } + fn rlp_append(&self, s: &mut rlp::RlpStream) { + s.begin_list(6); + s.append(&self.chain_id); + s.append(&self.address); + s.append(&self.nonce); + s.append(&self.y_parity); + s.append(&self.r); + s.append(&self.s); + } } impl Decodable for AuthorizationListEntry { - fn decode(rlp: &rlp::Rlp) -> Result { - Ok(AuthorizationListEntry { - chain_id: rlp.val_at(0)?, - address: rlp.val_at(1)?, - nonce: rlp.val_at(2)?, - y_parity: rlp.val_at(3)?, - r: rlp.val_at(4)?, - s: rlp.val_at(5)?, - }) - } + fn decode(rlp: &rlp::Rlp) -> Result { + Ok(AuthorizationListEntry { + chain_id: rlp.val_at(0)?, + address: rlp.val_at(1)?, + nonce: rlp.val_at(2)?, + y_parity: rlp.val_at(3)?, + r: rlp.val_at(4)?, + s: rlp.val_at(5)?, + }) + } } /// See impl Encodable for Transaction1559Unsigned { - fn rlp_append(&self, s: &mut rlp::RlpStream) { - s.begin_list(9); - s.append(&self.chain_id); - s.append(&self.nonce); - s.append(&self.max_priority_fee_per_gas); - s.append(&self.max_fee_per_gas); - s.append(&self.gas); - match self.to { - Some(ref to) => s.append(to), - None => s.append_empty_data(), - }; - s.append(&self.value); - s.append(&self.input.0); - s.append_list(&self.access_list); - } + fn rlp_append(&self, s: &mut rlp::RlpStream) { + s.begin_list(9); + s.append(&self.chain_id); + s.append(&self.nonce); + s.append(&self.max_priority_fee_per_gas); + s.append(&self.max_fee_per_gas); + s.append(&self.gas); + match self.to { + Some(ref to) => s.append(to), + None => s.append_empty_data(), + }; + s.append(&self.value); + s.append(&self.input.0); + s.append_list(&self.access_list); + } } /// See impl Encodable for Transaction1559Signed { - fn rlp_append(&self, s: &mut rlp::RlpStream) { - let tx = &self.transaction_1559_unsigned; - s.begin_list(12); - s.append(&tx.chain_id); - s.append(&tx.nonce); - s.append(&tx.max_priority_fee_per_gas); - s.append(&tx.max_fee_per_gas); - s.append(&tx.gas); - match tx.to { - Some(ref to) => s.append(to), - None => s.append_empty_data(), - }; - s.append(&tx.value); - s.append(&tx.input.0); - s.append_list(&tx.access_list); - - s.append(&self.y_parity); - s.append(&self.r); - s.append(&self.s); - } + fn rlp_append(&self, s: &mut rlp::RlpStream) { + let tx = &self.transaction_1559_unsigned; + s.begin_list(12); + s.append(&tx.chain_id); + s.append(&tx.nonce); + s.append(&tx.max_priority_fee_per_gas); + s.append(&tx.max_fee_per_gas); + s.append(&tx.gas); + match tx.to { + Some(ref to) => s.append(to), + None => s.append_empty_data(), + }; + s.append(&tx.value); + s.append(&tx.input.0); + s.append_list(&tx.access_list); + + s.append(&self.y_parity); + s.append(&self.r); + s.append(&self.s); + } } impl Decodable for Transaction1559Signed { - fn decode(rlp: &rlp::Rlp) -> Result { - Ok(Transaction1559Signed { - transaction_1559_unsigned: { - Transaction1559Unsigned { - chain_id: rlp.val_at(0)?, - nonce: rlp.val_at(1)?, - max_priority_fee_per_gas: rlp.val_at(2)?, - max_fee_per_gas: rlp.val_at(3)?, - gas: rlp.val_at(4)?, - to: { - let to = rlp.at(5)?; - if to.is_empty() { - None - } else { - Some(to.as_val()?) - } - }, - value: rlp.val_at(6)?, - input: Bytes(rlp.val_at(7)?), - access_list: rlp.list_at(8)?, - ..Default::default() - } - }, - y_parity: rlp.val_at(9)?, - r: rlp.val_at(10)?, - s: rlp.val_at(11)?, - ..Default::default() - }) - } + fn decode(rlp: &rlp::Rlp) -> Result { + Ok(Transaction1559Signed { + transaction_1559_unsigned: { + Transaction1559Unsigned { + chain_id: rlp.val_at(0)?, + nonce: rlp.val_at(1)?, + max_priority_fee_per_gas: rlp.val_at(2)?, + max_fee_per_gas: rlp.val_at(3)?, + gas: rlp.val_at(4)?, + to: { + let to = rlp.at(5)?; + if to.is_empty() { + None + } else { + Some(to.as_val()?) + } + }, + value: rlp.val_at(6)?, + input: Bytes(rlp.val_at(7)?), + access_list: rlp.list_at(8)?, + ..Default::default() + } + }, + y_parity: rlp.val_at(9)?, + r: rlp.val_at(10)?, + s: rlp.val_at(11)?, + ..Default::default() + }) + } } //See https://eips.ethereum.org/EIPS/eip-2930 impl Encodable for Transaction2930Unsigned { - fn rlp_append(&self, s: &mut rlp::RlpStream) { - s.begin_list(8); - s.append(&self.chain_id); - s.append(&self.nonce); - s.append(&self.gas_price); - s.append(&self.gas); - match self.to { - Some(ref to) => s.append(to), - None => s.append_empty_data(), - }; - s.append(&self.value); - s.append(&self.input.0); - s.append_list(&self.access_list); - } + fn rlp_append(&self, s: &mut rlp::RlpStream) { + s.begin_list(8); + s.append(&self.chain_id); + s.append(&self.nonce); + s.append(&self.gas_price); + s.append(&self.gas); + match self.to { + Some(ref to) => s.append(to), + None => s.append_empty_data(), + }; + s.append(&self.value); + s.append(&self.input.0); + s.append_list(&self.access_list); + } } //See https://eips.ethereum.org/EIPS/eip-2930 impl Encodable for Transaction2930Signed { - fn rlp_append(&self, s: &mut rlp::RlpStream) { - let tx = &self.transaction_2930_unsigned; - s.begin_list(11); - s.append(&tx.chain_id); - s.append(&tx.nonce); - s.append(&tx.gas_price); - s.append(&tx.gas); - match tx.to { - Some(ref to) => s.append(to), - None => s.append_empty_data(), - }; - s.append(&tx.value); - s.append(&tx.input.0); - s.append_list(&tx.access_list); - s.append(&self.y_parity); - s.append(&self.r); - s.append(&self.s); - } + fn rlp_append(&self, s: &mut rlp::RlpStream) { + let tx = &self.transaction_2930_unsigned; + s.begin_list(11); + s.append(&tx.chain_id); + s.append(&tx.nonce); + s.append(&tx.gas_price); + s.append(&tx.gas); + match tx.to { + Some(ref to) => s.append(to), + None => s.append_empty_data(), + }; + s.append(&tx.value); + s.append(&tx.input.0); + s.append_list(&tx.access_list); + s.append(&self.y_parity); + s.append(&self.r); + s.append(&self.s); + } } impl Decodable for Transaction2930Signed { - fn decode(rlp: &rlp::Rlp) -> Result { - Ok(Transaction2930Signed { - transaction_2930_unsigned: { - Transaction2930Unsigned { - chain_id: rlp.val_at(0)?, - nonce: rlp.val_at(1)?, - gas_price: rlp.val_at(2)?, - gas: rlp.val_at(3)?, - to: { - let to = rlp.at(4)?; - if to.is_empty() { - None - } else { - Some(to.as_val()?) - } - }, - value: rlp.val_at(5)?, - input: Bytes(rlp.val_at(6)?), - access_list: rlp.list_at(7)?, - ..Default::default() - } - }, - y_parity: rlp.val_at(8)?, - r: rlp.val_at(9)?, - s: rlp.val_at(10)?, - ..Default::default() - }) - } + fn decode(rlp: &rlp::Rlp) -> Result { + Ok(Transaction2930Signed { + transaction_2930_unsigned: { + Transaction2930Unsigned { + chain_id: rlp.val_at(0)?, + nonce: rlp.val_at(1)?, + gas_price: rlp.val_at(2)?, + gas: rlp.val_at(3)?, + to: { + let to = rlp.at(4)?; + if to.is_empty() { + None + } else { + Some(to.as_val()?) + } + }, + value: rlp.val_at(5)?, + input: Bytes(rlp.val_at(6)?), + access_list: rlp.list_at(7)?, + ..Default::default() + } + }, + y_parity: rlp.val_at(8)?, + r: rlp.val_at(9)?, + s: rlp.val_at(10)?, + ..Default::default() + }) + } } //See https://eips.ethereum.org/EIPS/eip-7702 impl Encodable for Transaction7702Unsigned { - fn rlp_append(&self, s: &mut rlp::RlpStream) { - s.begin_list(10); - s.append(&self.chain_id); - s.append(&self.nonce); - s.append(&self.max_priority_fee_per_gas); - s.append(&self.max_fee_per_gas); - s.append(&self.gas); - s.append(&self.to); - s.append(&self.value); - s.append(&self.input.0); - s.append_list(&self.access_list); - s.append_list(&self.authorization_list); - } + fn rlp_append(&self, s: &mut rlp::RlpStream) { + s.begin_list(10); + s.append(&self.chain_id); + s.append(&self.nonce); + s.append(&self.max_priority_fee_per_gas); + s.append(&self.max_fee_per_gas); + s.append(&self.gas); + s.append(&self.to); + s.append(&self.value); + s.append(&self.input.0); + s.append_list(&self.access_list); + s.append_list(&self.authorization_list); + } } impl Decodable for Transaction7702Signed { - fn decode(rlp: &rlp::Rlp) -> Result { - Ok(Transaction7702Signed { - transaction_7702_unsigned: { - Transaction7702Unsigned { - chain_id: rlp.val_at(0)?, - nonce: rlp.val_at(1)?, - max_priority_fee_per_gas: rlp.val_at(2)?, - max_fee_per_gas: rlp.val_at(3)?, - gas_price: rlp.val_at(4)?, - gas: rlp.val_at(5)?, - to: rlp.val_at(6)?, - value: rlp.val_at(7)?, - input: Bytes(rlp.val_at(8)?), - access_list: rlp.list_at(9)?, - authorization_list: rlp.list_at(10)?, - r#type: Default::default(), - } - }, - y_parity: rlp.val_at(11)?, - r: rlp.val_at(12)?, - s: rlp.val_at(13)?, - v: None, - }) - } + fn decode(rlp: &rlp::Rlp) -> Result { + Ok(Transaction7702Signed { + transaction_7702_unsigned: { + Transaction7702Unsigned { + chain_id: rlp.val_at(0)?, + nonce: rlp.val_at(1)?, + max_priority_fee_per_gas: rlp.val_at(2)?, + max_fee_per_gas: rlp.val_at(3)?, + gas_price: rlp.val_at(4)?, + gas: rlp.val_at(5)?, + to: rlp.val_at(6)?, + value: rlp.val_at(7)?, + input: Bytes(rlp.val_at(8)?), + access_list: rlp.list_at(9)?, + authorization_list: rlp.list_at(10)?, + r#type: Default::default(), + } + }, + y_parity: rlp.val_at(11)?, + r: rlp.val_at(12)?, + s: rlp.val_at(13)?, + v: None, + }) + } } impl Encodable for Transaction4844Unsigned { - fn rlp_append(&self, s: &mut rlp::RlpStream) { - s.begin_list(11); - s.append(&self.chain_id); - s.append(&self.nonce); - s.append(&self.max_priority_fee_per_gas); - s.append(&self.max_fee_per_gas); - s.append(&self.gas); - s.append(&self.to); - s.append(&self.value); - s.append(&self.input.0); - s.append_list(&self.access_list); - s.append(&self.max_fee_per_blob_gas); - s.append_list(&self.blob_versioned_hashes); - } + fn rlp_append(&self, s: &mut rlp::RlpStream) { + s.begin_list(11); + s.append(&self.chain_id); + s.append(&self.nonce); + s.append(&self.max_priority_fee_per_gas); + s.append(&self.max_fee_per_gas); + s.append(&self.gas); + s.append(&self.to); + s.append(&self.value); + s.append(&self.input.0); + s.append_list(&self.access_list); + s.append(&self.max_fee_per_blob_gas); + s.append_list(&self.blob_versioned_hashes); + } } //See https://eips.ethereum.org/EIPS/eip-7702 impl Encodable for Transaction7702Signed { - fn rlp_append(&self, s: &mut rlp::RlpStream) { - let tx = &self.transaction_7702_unsigned; - s.begin_list(13); - s.append(&tx.chain_id); - s.append(&tx.nonce); - s.append(&tx.max_priority_fee_per_gas); - s.append(&tx.max_fee_per_gas); - s.append(&tx.gas); - s.append(&tx.to); - s.append(&tx.value); - s.append(&tx.input.0); - s.append_list(&tx.access_list); - s.append_list(&tx.authorization_list); - s.append(&self.y_parity); - s.append(&self.r); - s.append(&self.s); - } + fn rlp_append(&self, s: &mut rlp::RlpStream) { + let tx = &self.transaction_7702_unsigned; + s.begin_list(13); + s.append(&tx.chain_id); + s.append(&tx.nonce); + s.append(&tx.max_priority_fee_per_gas); + s.append(&tx.max_fee_per_gas); + s.append(&tx.gas); + s.append(&tx.to); + s.append(&tx.value); + s.append(&tx.input.0); + s.append_list(&tx.access_list); + s.append_list(&tx.authorization_list); + s.append(&self.y_parity); + s.append(&self.r); + s.append(&self.s); + } } //See https://eips.ethereum.org/EIPS/eip-4844 impl Encodable for Transaction4844Signed { - fn rlp_append(&self, s: &mut rlp::RlpStream) { - let tx = &self.transaction_4844_unsigned; - s.begin_list(14); - s.append(&tx.chain_id); - s.append(&tx.nonce); - s.append(&tx.max_priority_fee_per_gas); - s.append(&tx.max_fee_per_gas); - s.append(&tx.gas); - s.append(&tx.to); - s.append(&tx.value); - s.append(&tx.input.0); - s.append_list(&tx.access_list); - s.append(&tx.max_fee_per_blob_gas); - s.append_list(&tx.blob_versioned_hashes); - s.append(&self.y_parity); - s.append(&self.r); - s.append(&self.s); - } + fn rlp_append(&self, s: &mut rlp::RlpStream) { + let tx = &self.transaction_4844_unsigned; + s.begin_list(14); + s.append(&tx.chain_id); + s.append(&tx.nonce); + s.append(&tx.max_priority_fee_per_gas); + s.append(&tx.max_fee_per_gas); + s.append(&tx.gas); + s.append(&tx.to); + s.append(&tx.value); + s.append(&tx.input.0); + s.append_list(&tx.access_list); + s.append(&tx.max_fee_per_blob_gas); + s.append_list(&tx.blob_versioned_hashes); + s.append(&self.y_parity); + s.append(&self.r); + s.append(&self.s); + } } impl Decodable for Transaction4844Signed { - fn decode(rlp: &rlp::Rlp) -> Result { - Ok(Transaction4844Signed { - transaction_4844_unsigned: { - Transaction4844Unsigned { - chain_id: rlp.val_at(0)?, - nonce: rlp.val_at(1)?, - max_priority_fee_per_gas: rlp.val_at(2)?, - max_fee_per_gas: rlp.val_at(3)?, - gas: rlp.val_at(4)?, - to: rlp.val_at(5)?, - value: rlp.val_at(6)?, - input: Bytes(rlp.val_at(7)?), - access_list: rlp.list_at(8)?, - max_fee_per_blob_gas: rlp.val_at(9)?, - blob_versioned_hashes: rlp.list_at(10)?, - ..Default::default() - } - }, - y_parity: rlp.val_at(11)?, - r: rlp.val_at(12)?, - s: rlp.val_at(13)?, - }) - } + fn decode(rlp: &rlp::Rlp) -> Result { + Ok(Transaction4844Signed { + transaction_4844_unsigned: { + Transaction4844Unsigned { + chain_id: rlp.val_at(0)?, + nonce: rlp.val_at(1)?, + max_priority_fee_per_gas: rlp.val_at(2)?, + max_fee_per_gas: rlp.val_at(3)?, + gas: rlp.val_at(4)?, + to: rlp.val_at(5)?, + value: rlp.val_at(6)?, + input: Bytes(rlp.val_at(7)?), + access_list: rlp.list_at(8)?, + max_fee_per_blob_gas: rlp.val_at(9)?, + blob_versioned_hashes: rlp.list_at(10)?, + ..Default::default() + } + }, + y_parity: rlp.val_at(11)?, + r: rlp.val_at(12)?, + s: rlp.val_at(13)?, + }) + } } /// See impl Decodable for TransactionLegacySigned { - fn decode(rlp: &rlp::Rlp) -> Result { - let v: U256 = rlp.val_at(6)?; - - let extract_chain_id = |v: U256| { - if v.ge(&35u32.into()) { - Some((v - 35) / 2) - } else { - None - } - }; - - Ok(TransactionLegacySigned { - transaction_legacy_unsigned: { - TransactionLegacyUnsigned { - nonce: rlp.val_at(0)?, - gas_price: rlp.val_at(1)?, - gas: rlp.val_at(2)?, - to: { - let to = rlp.at(3)?; - if to.is_empty() { - None - } else { - Some(to.as_val()?) - } - }, - value: rlp.val_at(4)?, - input: Bytes(rlp.val_at(5)?), - chain_id: extract_chain_id(v).map(|v| v.into()), - r#type: TypeLegacy {}, - } - }, - v, - r: rlp.val_at(7)?, - s: rlp.val_at(8)?, - }) - } + fn decode(rlp: &rlp::Rlp) -> Result { + let v: U256 = rlp.val_at(6)?; + + let extract_chain_id = |v: U256| { + if v.ge(&35u32.into()) { + Some((v - 35) / 2) + } else { + None + } + }; + + Ok(TransactionLegacySigned { + transaction_legacy_unsigned: { + TransactionLegacyUnsigned { + nonce: rlp.val_at(0)?, + gas_price: rlp.val_at(1)?, + gas: rlp.val_at(2)?, + to: { + let to = rlp.at(3)?; + if to.is_empty() { + None + } else { + Some(to.as_val()?) + } + }, + value: rlp.val_at(4)?, + input: Bytes(rlp.val_at(5)?), + chain_id: extract_chain_id(v).map(|v| v.into()), + r#type: TypeLegacy {}, + } + }, + v, + r: rlp.val_at(7)?, + s: rlp.val_at(8)?, + }) + } } #[cfg(test)] mod test { - use super::*; + use super::*; - #[test] - fn encode_decode_tx_works() { - let txs = [ + #[test] + fn encode_decode_tx_works() { + let txs = [ // Legacy ( "f86080808301e24194095e7baea6a6c7c4c2dfeb977efac326af552d87808025a0fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0a06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8", @@ -659,31 +676,31 @@ mod test { ) ]; - for (tx, json) in txs { - let raw_tx = alloy_core::hex::decode(tx).unwrap(); - let tx = TransactionSigned::decode(&raw_tx).unwrap(); - assert_eq!(tx.signed_payload(), raw_tx); - let expected_tx = serde_json::from_str(json).unwrap(); - assert_eq!(tx, expected_tx); - } - } - - #[test] - fn dummy_signed_payload_works() { - let tx: TransactionUnsigned = TransactionLegacyUnsigned { - chain_id: Some(596.into()), - gas: U256::from(21000), - nonce: U256::from(1), - gas_price: U256::from("0x640000006a"), - to: Some(Account::from(subxt_signer::eth::dev::baltathar()).address()), - value: U256::from(123123), - input: Bytes(vec![]), - r#type: TypeLegacy, - } - .into(); - - let dummy_signed_payload = tx.clone().dummy_signed_payload(); - let payload = Account::default().sign_transaction(tx).signed_payload(); - assert_eq!(dummy_signed_payload.len(), payload.len()); - } + for (tx, json) in txs { + let raw_tx = alloy_core::hex::decode(tx).unwrap(); + let tx = TransactionSigned::decode(&raw_tx).unwrap(); + assert_eq!(tx.signed_payload(), raw_tx); + let expected_tx = serde_json::from_str(json).unwrap(); + assert_eq!(tx, expected_tx); + } + } + + #[test] + fn dummy_signed_payload_works() { + let tx: TransactionUnsigned = TransactionLegacyUnsigned { + chain_id: Some(596.into()), + gas: U256::from(21000), + nonce: U256::from(1), + gas_price: U256::from("0x640000006a"), + to: Some(Account::from(subxt_signer::eth::dev::baltathar()).address()), + value: U256::from(123123), + input: Bytes(vec![]), + r#type: TypeLegacy, + } + .into(); + + let dummy_signed_payload = tx.clone().dummy_signed_payload(); + let payload = Account::default().sign_transaction(tx).signed_payload(); + assert_eq!(dummy_signed_payload.len(), payload.len()); + } } From 533a825a001d20a73a042f4fa75f47ef2d3a9e1e Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 11 Nov 2025 16:42:27 +0100 Subject: [PATCH 2/7] fmt --- .../frame/revive/src/evm/api/rlp_codec.rs | 992 +++++++++--------- 1 file changed, 494 insertions(+), 498 deletions(-) diff --git a/substrate/frame/revive/src/evm/api/rlp_codec.rs b/substrate/frame/revive/src/evm/api/rlp_codec.rs index 3e45327a66669..1ae911b8ff8d0 100644 --- a/substrate/frame/revive/src/evm/api/rlp_codec.rs +++ b/substrate/frame/revive/src/evm/api/rlp_codec.rs @@ -22,557 +22,553 @@ use alloc::vec::Vec; use rlp::{Decodable, Encodable}; impl TransactionUnsigned { - /// Return the bytes to be signed by the private key. - pub fn unsigned_payload(&self) -> Vec { - use TransactionUnsigned::*; - let mut s = rlp::RlpStream::new(); - match self { - Transaction7702Unsigned(ref tx) => { - s.append(&tx.r#type.value()); - s.append(tx); - } - Transaction2930Unsigned(ref tx) => { - s.append(&tx.r#type.value()); - s.append(tx); - } - Transaction1559Unsigned(ref tx) => { - s.append(&tx.r#type.value()); - s.append(tx); - } - Transaction4844Unsigned(ref tx) => { - s.append(&tx.r#type.value()); - s.append(tx); - } - TransactionLegacyUnsigned(ref tx) => { - s.append(tx); - } - } - - s.out().to_vec() - } + /// Return the bytes to be signed by the private key. + pub fn unsigned_payload(&self) -> Vec { + use TransactionUnsigned::*; + let mut s = rlp::RlpStream::new(); + match self { + Transaction7702Unsigned(ref tx) => { + s.append(&tx.r#type.value()); + s.append(tx); + }, + Transaction2930Unsigned(ref tx) => { + s.append(&tx.r#type.value()); + s.append(tx); + }, + Transaction1559Unsigned(ref tx) => { + s.append(&tx.r#type.value()); + s.append(tx); + }, + Transaction4844Unsigned(ref tx) => { + s.append(&tx.r#type.value()); + s.append(tx); + }, + TransactionLegacyUnsigned(ref tx) => { + s.append(tx); + }, + } + + s.out().to_vec() + } } impl TransactionSigned { - /// Extract the unsigned transaction from a signed transaction. - pub fn unsigned(self) -> TransactionUnsigned { - use TransactionSigned::*; - use TransactionUnsigned::*; - match self { - Transaction7702Signed(tx) => Transaction7702Unsigned(tx.transaction_7702_unsigned), - Transaction2930Signed(tx) => Transaction2930Unsigned(tx.transaction_2930_unsigned), - Transaction1559Signed(tx) => Transaction1559Unsigned(tx.transaction_1559_unsigned), - Transaction4844Signed(tx) => Transaction4844Unsigned(tx.transaction_4844_unsigned), - TransactionLegacySigned(tx) => { - TransactionLegacyUnsigned(tx.transaction_legacy_unsigned) - } - } - } - - /// Encode the Ethereum transaction into bytes. - pub fn signed_payload(&self) -> Vec { - use TransactionSigned::*; - let mut s = rlp::RlpStream::new(); - match self { - Transaction7702Signed(ref tx) => { - s.append(&tx.transaction_7702_unsigned.r#type.value()); - s.append(tx); - } - Transaction2930Signed(ref tx) => { - s.append(&tx.transaction_2930_unsigned.r#type.value()); - s.append(tx); - } - Transaction1559Signed(ref tx) => { - s.append(&tx.transaction_1559_unsigned.r#type.value()); - s.append(tx); - } - Transaction4844Signed(ref tx) => { - s.append(&tx.transaction_4844_unsigned.r#type.value()); - s.append(tx); - } - TransactionLegacySigned(ref tx) => { - s.append(tx); - } - } - - s.out().to_vec() - } - - /// Decode the Ethereum transaction from bytes. - /// According to EIP-2718, legacy transactions start with a byte in [0xc0, 0xff] (RLP list), - /// while typed transactions use type identifiers in [0x00, 0x7f]. - pub fn decode(data: &[u8]) -> Result { - if data.is_empty() { - return Err(rlp::DecoderError::RlpIsTooShort); - } - let first_byte = data[0]; - - // Typed transactions: first byte in [0x00, 0x7f] - if first_byte <= 0x7f { - match first_byte { - TYPE_EIP2930 => rlp::decode::(&data[1..]).map(Into::into), - TYPE_EIP1559 => rlp::decode::(&data[1..]).map(Into::into), - TYPE_EIP4844 => rlp::decode::(&data[1..]).map(Into::into), - TYPE_EIP7702 => rlp::decode::(&data[1..]).map(Into::into), - _ => Err(rlp::DecoderError::Custom("Unknown transaction type")), - } - } - // Legacy transactions: first byte in [0xc0, 0xff] - else if first_byte >= 0xc0 { - rlp::decode::(data).map(Into::into) - } else { - Err(rlp::DecoderError::Custom("Invalid transaction encoding")) - } - } + /// Extract the unsigned transaction from a signed transaction. + pub fn unsigned(self) -> TransactionUnsigned { + use TransactionSigned::*; + use TransactionUnsigned::*; + match self { + Transaction7702Signed(tx) => Transaction7702Unsigned(tx.transaction_7702_unsigned), + Transaction2930Signed(tx) => Transaction2930Unsigned(tx.transaction_2930_unsigned), + Transaction1559Signed(tx) => Transaction1559Unsigned(tx.transaction_1559_unsigned), + Transaction4844Signed(tx) => Transaction4844Unsigned(tx.transaction_4844_unsigned), + TransactionLegacySigned(tx) => + TransactionLegacyUnsigned(tx.transaction_legacy_unsigned), + } + } + + /// Encode the Ethereum transaction into bytes. + pub fn signed_payload(&self) -> Vec { + use TransactionSigned::*; + let mut s = rlp::RlpStream::new(); + match self { + Transaction7702Signed(ref tx) => { + s.append(&tx.transaction_7702_unsigned.r#type.value()); + s.append(tx); + }, + Transaction2930Signed(ref tx) => { + s.append(&tx.transaction_2930_unsigned.r#type.value()); + s.append(tx); + }, + Transaction1559Signed(ref tx) => { + s.append(&tx.transaction_1559_unsigned.r#type.value()); + s.append(tx); + }, + Transaction4844Signed(ref tx) => { + s.append(&tx.transaction_4844_unsigned.r#type.value()); + s.append(tx); + }, + TransactionLegacySigned(ref tx) => { + s.append(tx); + }, + } + + s.out().to_vec() + } + + /// Decode the Ethereum transaction from bytes. + /// According to EIP-2718, legacy transactions start with a byte in [0xc0, 0xff] (RLP list), + /// while typed transactions use type identifiers in [0x00, 0x7f]. + pub fn decode(data: &[u8]) -> Result { + if data.is_empty() { + return Err(rlp::DecoderError::RlpIsTooShort); + } + let first_byte = data[0]; + + // Typed transactions: first byte in [0x00, 0x7f] + if first_byte <= 0x7f { + match first_byte { + TYPE_EIP2930 => rlp::decode::(&data[1..]).map(Into::into), + TYPE_EIP1559 => rlp::decode::(&data[1..]).map(Into::into), + TYPE_EIP4844 => rlp::decode::(&data[1..]).map(Into::into), + TYPE_EIP7702 => rlp::decode::(&data[1..]).map(Into::into), + _ => Err(rlp::DecoderError::Custom("Unknown transaction type")), + } + } + // Legacy transactions: first byte in [0xc0, 0xff] + else if first_byte >= 0xc0 { + rlp::decode::(data).map(Into::into) + } else { + Err(rlp::DecoderError::Custom("Invalid transaction encoding")) + } + } } impl TransactionUnsigned { - /// Get a signed transaction payload with a dummy 65 bytes signature. - pub fn dummy_signed_payload(self) -> Vec { - const DUMMY_SIGNATURE: [u8; 65] = [1u8; 65]; - self.with_signature(DUMMY_SIGNATURE).signed_payload() - } + /// Get a signed transaction payload with a dummy 65 bytes signature. + pub fn dummy_signed_payload(self) -> Vec { + const DUMMY_SIGNATURE: [u8; 65] = [1u8; 65]; + self.with_signature(DUMMY_SIGNATURE).signed_payload() + } } /// See impl Encodable for TransactionLegacyUnsigned { - fn rlp_append(&self, s: &mut rlp::RlpStream) { - if let Some(chain_id) = self.chain_id { - s.begin_list(9); - s.append(&self.nonce); - s.append(&self.gas_price); - s.append(&self.gas); - match self.to { - Some(ref to) => s.append(to), - None => s.append_empty_data(), - }; - s.append(&self.value); - s.append(&self.input.0); - s.append(&chain_id); - s.append(&0u8); - s.append(&0u8); - } else { - s.begin_list(6); - s.append(&self.nonce); - s.append(&self.gas_price); - s.append(&self.gas); - match self.to { - Some(ref to) => s.append(to), - None => s.append_empty_data(), - }; - s.append(&self.value); - s.append(&self.input.0); - } - } + fn rlp_append(&self, s: &mut rlp::RlpStream) { + if let Some(chain_id) = self.chain_id { + s.begin_list(9); + s.append(&self.nonce); + s.append(&self.gas_price); + s.append(&self.gas); + match self.to { + Some(ref to) => s.append(to), + None => s.append_empty_data(), + }; + s.append(&self.value); + s.append(&self.input.0); + s.append(&chain_id); + s.append(&0u8); + s.append(&0u8); + } else { + s.begin_list(6); + s.append(&self.nonce); + s.append(&self.gas_price); + s.append(&self.gas); + match self.to { + Some(ref to) => s.append(to), + None => s.append_empty_data(), + }; + s.append(&self.value); + s.append(&self.input.0); + } + } } impl Decodable for TransactionLegacyUnsigned { - fn decode(rlp: &rlp::Rlp) -> Result { - Ok(TransactionLegacyUnsigned { - nonce: rlp.val_at(0)?, - gas_price: rlp.val_at(1)?, - gas: rlp.val_at(2)?, - to: { - let to = rlp.at(3)?; - if to.is_empty() { - None - } else { - Some(to.as_val()?) - } - }, - value: rlp.val_at(4)?, - input: Bytes(rlp.val_at(5)?), - chain_id: rlp.val_at(6).ok(), - ..Default::default() - }) - } + fn decode(rlp: &rlp::Rlp) -> Result { + Ok(TransactionLegacyUnsigned { + nonce: rlp.val_at(0)?, + gas_price: rlp.val_at(1)?, + gas: rlp.val_at(2)?, + to: { + let to = rlp.at(3)?; + if to.is_empty() { + None + } else { + Some(to.as_val()?) + } + }, + value: rlp.val_at(4)?, + input: Bytes(rlp.val_at(5)?), + chain_id: rlp.val_at(6).ok(), + ..Default::default() + }) + } } impl Encodable for TransactionLegacySigned { - fn rlp_append(&self, s: &mut rlp::RlpStream) { - let tx = &self.transaction_legacy_unsigned; - - s.begin_list(9); - s.append(&tx.nonce); - s.append(&tx.gas_price); - s.append(&tx.gas); - match tx.to { - Some(ref to) => s.append(to), - None => s.append_empty_data(), - }; - s.append(&tx.value); - s.append(&tx.input.0); - - s.append(&self.v); - s.append(&self.r); - s.append(&self.s); - } + fn rlp_append(&self, s: &mut rlp::RlpStream) { + let tx = &self.transaction_legacy_unsigned; + + s.begin_list(9); + s.append(&tx.nonce); + s.append(&tx.gas_price); + s.append(&tx.gas); + match tx.to { + Some(ref to) => s.append(to), + None => s.append_empty_data(), + }; + s.append(&tx.value); + s.append(&tx.input.0); + + s.append(&self.v); + s.append(&self.r); + s.append(&self.s); + } } impl Encodable for AccessListEntry { - fn rlp_append(&self, s: &mut rlp::RlpStream) { - s.begin_list(2); - s.append(&self.address); - s.append_list(&self.storage_keys); - } + fn rlp_append(&self, s: &mut rlp::RlpStream) { + s.begin_list(2); + s.append(&self.address); + s.append_list(&self.storage_keys); + } } impl Decodable for AccessListEntry { - fn decode(rlp: &rlp::Rlp) -> Result { - Ok(AccessListEntry { - address: rlp.val_at(0)?, - storage_keys: rlp.list_at(1)?, - }) - } + fn decode(rlp: &rlp::Rlp) -> Result { + Ok(AccessListEntry { address: rlp.val_at(0)?, storage_keys: rlp.list_at(1)? }) + } } impl Encodable for AuthorizationListEntry { - fn rlp_append(&self, s: &mut rlp::RlpStream) { - s.begin_list(6); - s.append(&self.chain_id); - s.append(&self.address); - s.append(&self.nonce); - s.append(&self.y_parity); - s.append(&self.r); - s.append(&self.s); - } + fn rlp_append(&self, s: &mut rlp::RlpStream) { + s.begin_list(6); + s.append(&self.chain_id); + s.append(&self.address); + s.append(&self.nonce); + s.append(&self.y_parity); + s.append(&self.r); + s.append(&self.s); + } } impl Decodable for AuthorizationListEntry { - fn decode(rlp: &rlp::Rlp) -> Result { - Ok(AuthorizationListEntry { - chain_id: rlp.val_at(0)?, - address: rlp.val_at(1)?, - nonce: rlp.val_at(2)?, - y_parity: rlp.val_at(3)?, - r: rlp.val_at(4)?, - s: rlp.val_at(5)?, - }) - } + fn decode(rlp: &rlp::Rlp) -> Result { + Ok(AuthorizationListEntry { + chain_id: rlp.val_at(0)?, + address: rlp.val_at(1)?, + nonce: rlp.val_at(2)?, + y_parity: rlp.val_at(3)?, + r: rlp.val_at(4)?, + s: rlp.val_at(5)?, + }) + } } /// See impl Encodable for Transaction1559Unsigned { - fn rlp_append(&self, s: &mut rlp::RlpStream) { - s.begin_list(9); - s.append(&self.chain_id); - s.append(&self.nonce); - s.append(&self.max_priority_fee_per_gas); - s.append(&self.max_fee_per_gas); - s.append(&self.gas); - match self.to { - Some(ref to) => s.append(to), - None => s.append_empty_data(), - }; - s.append(&self.value); - s.append(&self.input.0); - s.append_list(&self.access_list); - } + fn rlp_append(&self, s: &mut rlp::RlpStream) { + s.begin_list(9); + s.append(&self.chain_id); + s.append(&self.nonce); + s.append(&self.max_priority_fee_per_gas); + s.append(&self.max_fee_per_gas); + s.append(&self.gas); + match self.to { + Some(ref to) => s.append(to), + None => s.append_empty_data(), + }; + s.append(&self.value); + s.append(&self.input.0); + s.append_list(&self.access_list); + } } /// See impl Encodable for Transaction1559Signed { - fn rlp_append(&self, s: &mut rlp::RlpStream) { - let tx = &self.transaction_1559_unsigned; - s.begin_list(12); - s.append(&tx.chain_id); - s.append(&tx.nonce); - s.append(&tx.max_priority_fee_per_gas); - s.append(&tx.max_fee_per_gas); - s.append(&tx.gas); - match tx.to { - Some(ref to) => s.append(to), - None => s.append_empty_data(), - }; - s.append(&tx.value); - s.append(&tx.input.0); - s.append_list(&tx.access_list); - - s.append(&self.y_parity); - s.append(&self.r); - s.append(&self.s); - } + fn rlp_append(&self, s: &mut rlp::RlpStream) { + let tx = &self.transaction_1559_unsigned; + s.begin_list(12); + s.append(&tx.chain_id); + s.append(&tx.nonce); + s.append(&tx.max_priority_fee_per_gas); + s.append(&tx.max_fee_per_gas); + s.append(&tx.gas); + match tx.to { + Some(ref to) => s.append(to), + None => s.append_empty_data(), + }; + s.append(&tx.value); + s.append(&tx.input.0); + s.append_list(&tx.access_list); + + s.append(&self.y_parity); + s.append(&self.r); + s.append(&self.s); + } } impl Decodable for Transaction1559Signed { - fn decode(rlp: &rlp::Rlp) -> Result { - Ok(Transaction1559Signed { - transaction_1559_unsigned: { - Transaction1559Unsigned { - chain_id: rlp.val_at(0)?, - nonce: rlp.val_at(1)?, - max_priority_fee_per_gas: rlp.val_at(2)?, - max_fee_per_gas: rlp.val_at(3)?, - gas: rlp.val_at(4)?, - to: { - let to = rlp.at(5)?; - if to.is_empty() { - None - } else { - Some(to.as_val()?) - } - }, - value: rlp.val_at(6)?, - input: Bytes(rlp.val_at(7)?), - access_list: rlp.list_at(8)?, - ..Default::default() - } - }, - y_parity: rlp.val_at(9)?, - r: rlp.val_at(10)?, - s: rlp.val_at(11)?, - ..Default::default() - }) - } + fn decode(rlp: &rlp::Rlp) -> Result { + Ok(Transaction1559Signed { + transaction_1559_unsigned: { + Transaction1559Unsigned { + chain_id: rlp.val_at(0)?, + nonce: rlp.val_at(1)?, + max_priority_fee_per_gas: rlp.val_at(2)?, + max_fee_per_gas: rlp.val_at(3)?, + gas: rlp.val_at(4)?, + to: { + let to = rlp.at(5)?; + if to.is_empty() { + None + } else { + Some(to.as_val()?) + } + }, + value: rlp.val_at(6)?, + input: Bytes(rlp.val_at(7)?), + access_list: rlp.list_at(8)?, + ..Default::default() + } + }, + y_parity: rlp.val_at(9)?, + r: rlp.val_at(10)?, + s: rlp.val_at(11)?, + ..Default::default() + }) + } } //See https://eips.ethereum.org/EIPS/eip-2930 impl Encodable for Transaction2930Unsigned { - fn rlp_append(&self, s: &mut rlp::RlpStream) { - s.begin_list(8); - s.append(&self.chain_id); - s.append(&self.nonce); - s.append(&self.gas_price); - s.append(&self.gas); - match self.to { - Some(ref to) => s.append(to), - None => s.append_empty_data(), - }; - s.append(&self.value); - s.append(&self.input.0); - s.append_list(&self.access_list); - } + fn rlp_append(&self, s: &mut rlp::RlpStream) { + s.begin_list(8); + s.append(&self.chain_id); + s.append(&self.nonce); + s.append(&self.gas_price); + s.append(&self.gas); + match self.to { + Some(ref to) => s.append(to), + None => s.append_empty_data(), + }; + s.append(&self.value); + s.append(&self.input.0); + s.append_list(&self.access_list); + } } //See https://eips.ethereum.org/EIPS/eip-2930 impl Encodable for Transaction2930Signed { - fn rlp_append(&self, s: &mut rlp::RlpStream) { - let tx = &self.transaction_2930_unsigned; - s.begin_list(11); - s.append(&tx.chain_id); - s.append(&tx.nonce); - s.append(&tx.gas_price); - s.append(&tx.gas); - match tx.to { - Some(ref to) => s.append(to), - None => s.append_empty_data(), - }; - s.append(&tx.value); - s.append(&tx.input.0); - s.append_list(&tx.access_list); - s.append(&self.y_parity); - s.append(&self.r); - s.append(&self.s); - } + fn rlp_append(&self, s: &mut rlp::RlpStream) { + let tx = &self.transaction_2930_unsigned; + s.begin_list(11); + s.append(&tx.chain_id); + s.append(&tx.nonce); + s.append(&tx.gas_price); + s.append(&tx.gas); + match tx.to { + Some(ref to) => s.append(to), + None => s.append_empty_data(), + }; + s.append(&tx.value); + s.append(&tx.input.0); + s.append_list(&tx.access_list); + s.append(&self.y_parity); + s.append(&self.r); + s.append(&self.s); + } } impl Decodable for Transaction2930Signed { - fn decode(rlp: &rlp::Rlp) -> Result { - Ok(Transaction2930Signed { - transaction_2930_unsigned: { - Transaction2930Unsigned { - chain_id: rlp.val_at(0)?, - nonce: rlp.val_at(1)?, - gas_price: rlp.val_at(2)?, - gas: rlp.val_at(3)?, - to: { - let to = rlp.at(4)?; - if to.is_empty() { - None - } else { - Some(to.as_val()?) - } - }, - value: rlp.val_at(5)?, - input: Bytes(rlp.val_at(6)?), - access_list: rlp.list_at(7)?, - ..Default::default() - } - }, - y_parity: rlp.val_at(8)?, - r: rlp.val_at(9)?, - s: rlp.val_at(10)?, - ..Default::default() - }) - } + fn decode(rlp: &rlp::Rlp) -> Result { + Ok(Transaction2930Signed { + transaction_2930_unsigned: { + Transaction2930Unsigned { + chain_id: rlp.val_at(0)?, + nonce: rlp.val_at(1)?, + gas_price: rlp.val_at(2)?, + gas: rlp.val_at(3)?, + to: { + let to = rlp.at(4)?; + if to.is_empty() { + None + } else { + Some(to.as_val()?) + } + }, + value: rlp.val_at(5)?, + input: Bytes(rlp.val_at(6)?), + access_list: rlp.list_at(7)?, + ..Default::default() + } + }, + y_parity: rlp.val_at(8)?, + r: rlp.val_at(9)?, + s: rlp.val_at(10)?, + ..Default::default() + }) + } } //See https://eips.ethereum.org/EIPS/eip-7702 impl Encodable for Transaction7702Unsigned { - fn rlp_append(&self, s: &mut rlp::RlpStream) { - s.begin_list(10); - s.append(&self.chain_id); - s.append(&self.nonce); - s.append(&self.max_priority_fee_per_gas); - s.append(&self.max_fee_per_gas); - s.append(&self.gas); - s.append(&self.to); - s.append(&self.value); - s.append(&self.input.0); - s.append_list(&self.access_list); - s.append_list(&self.authorization_list); - } + fn rlp_append(&self, s: &mut rlp::RlpStream) { + s.begin_list(10); + s.append(&self.chain_id); + s.append(&self.nonce); + s.append(&self.max_priority_fee_per_gas); + s.append(&self.max_fee_per_gas); + s.append(&self.gas); + s.append(&self.to); + s.append(&self.value); + s.append(&self.input.0); + s.append_list(&self.access_list); + s.append_list(&self.authorization_list); + } } impl Decodable for Transaction7702Signed { - fn decode(rlp: &rlp::Rlp) -> Result { - Ok(Transaction7702Signed { - transaction_7702_unsigned: { - Transaction7702Unsigned { - chain_id: rlp.val_at(0)?, - nonce: rlp.val_at(1)?, - max_priority_fee_per_gas: rlp.val_at(2)?, - max_fee_per_gas: rlp.val_at(3)?, - gas_price: rlp.val_at(4)?, - gas: rlp.val_at(5)?, - to: rlp.val_at(6)?, - value: rlp.val_at(7)?, - input: Bytes(rlp.val_at(8)?), - access_list: rlp.list_at(9)?, - authorization_list: rlp.list_at(10)?, - r#type: Default::default(), - } - }, - y_parity: rlp.val_at(11)?, - r: rlp.val_at(12)?, - s: rlp.val_at(13)?, - v: None, - }) - } + fn decode(rlp: &rlp::Rlp) -> Result { + Ok(Transaction7702Signed { + transaction_7702_unsigned: { + Transaction7702Unsigned { + chain_id: rlp.val_at(0)?, + nonce: rlp.val_at(1)?, + max_priority_fee_per_gas: rlp.val_at(2)?, + max_fee_per_gas: rlp.val_at(3)?, + gas_price: rlp.val_at(4)?, + gas: rlp.val_at(5)?, + to: rlp.val_at(6)?, + value: rlp.val_at(7)?, + input: Bytes(rlp.val_at(8)?), + access_list: rlp.list_at(9)?, + authorization_list: rlp.list_at(10)?, + r#type: Default::default(), + } + }, + y_parity: rlp.val_at(11)?, + r: rlp.val_at(12)?, + s: rlp.val_at(13)?, + v: None, + }) + } } impl Encodable for Transaction4844Unsigned { - fn rlp_append(&self, s: &mut rlp::RlpStream) { - s.begin_list(11); - s.append(&self.chain_id); - s.append(&self.nonce); - s.append(&self.max_priority_fee_per_gas); - s.append(&self.max_fee_per_gas); - s.append(&self.gas); - s.append(&self.to); - s.append(&self.value); - s.append(&self.input.0); - s.append_list(&self.access_list); - s.append(&self.max_fee_per_blob_gas); - s.append_list(&self.blob_versioned_hashes); - } + fn rlp_append(&self, s: &mut rlp::RlpStream) { + s.begin_list(11); + s.append(&self.chain_id); + s.append(&self.nonce); + s.append(&self.max_priority_fee_per_gas); + s.append(&self.max_fee_per_gas); + s.append(&self.gas); + s.append(&self.to); + s.append(&self.value); + s.append(&self.input.0); + s.append_list(&self.access_list); + s.append(&self.max_fee_per_blob_gas); + s.append_list(&self.blob_versioned_hashes); + } } //See https://eips.ethereum.org/EIPS/eip-7702 impl Encodable for Transaction7702Signed { - fn rlp_append(&self, s: &mut rlp::RlpStream) { - let tx = &self.transaction_7702_unsigned; - s.begin_list(13); - s.append(&tx.chain_id); - s.append(&tx.nonce); - s.append(&tx.max_priority_fee_per_gas); - s.append(&tx.max_fee_per_gas); - s.append(&tx.gas); - s.append(&tx.to); - s.append(&tx.value); - s.append(&tx.input.0); - s.append_list(&tx.access_list); - s.append_list(&tx.authorization_list); - s.append(&self.y_parity); - s.append(&self.r); - s.append(&self.s); - } + fn rlp_append(&self, s: &mut rlp::RlpStream) { + let tx = &self.transaction_7702_unsigned; + s.begin_list(13); + s.append(&tx.chain_id); + s.append(&tx.nonce); + s.append(&tx.max_priority_fee_per_gas); + s.append(&tx.max_fee_per_gas); + s.append(&tx.gas); + s.append(&tx.to); + s.append(&tx.value); + s.append(&tx.input.0); + s.append_list(&tx.access_list); + s.append_list(&tx.authorization_list); + s.append(&self.y_parity); + s.append(&self.r); + s.append(&self.s); + } } //See https://eips.ethereum.org/EIPS/eip-4844 impl Encodable for Transaction4844Signed { - fn rlp_append(&self, s: &mut rlp::RlpStream) { - let tx = &self.transaction_4844_unsigned; - s.begin_list(14); - s.append(&tx.chain_id); - s.append(&tx.nonce); - s.append(&tx.max_priority_fee_per_gas); - s.append(&tx.max_fee_per_gas); - s.append(&tx.gas); - s.append(&tx.to); - s.append(&tx.value); - s.append(&tx.input.0); - s.append_list(&tx.access_list); - s.append(&tx.max_fee_per_blob_gas); - s.append_list(&tx.blob_versioned_hashes); - s.append(&self.y_parity); - s.append(&self.r); - s.append(&self.s); - } + fn rlp_append(&self, s: &mut rlp::RlpStream) { + let tx = &self.transaction_4844_unsigned; + s.begin_list(14); + s.append(&tx.chain_id); + s.append(&tx.nonce); + s.append(&tx.max_priority_fee_per_gas); + s.append(&tx.max_fee_per_gas); + s.append(&tx.gas); + s.append(&tx.to); + s.append(&tx.value); + s.append(&tx.input.0); + s.append_list(&tx.access_list); + s.append(&tx.max_fee_per_blob_gas); + s.append_list(&tx.blob_versioned_hashes); + s.append(&self.y_parity); + s.append(&self.r); + s.append(&self.s); + } } impl Decodable for Transaction4844Signed { - fn decode(rlp: &rlp::Rlp) -> Result { - Ok(Transaction4844Signed { - transaction_4844_unsigned: { - Transaction4844Unsigned { - chain_id: rlp.val_at(0)?, - nonce: rlp.val_at(1)?, - max_priority_fee_per_gas: rlp.val_at(2)?, - max_fee_per_gas: rlp.val_at(3)?, - gas: rlp.val_at(4)?, - to: rlp.val_at(5)?, - value: rlp.val_at(6)?, - input: Bytes(rlp.val_at(7)?), - access_list: rlp.list_at(8)?, - max_fee_per_blob_gas: rlp.val_at(9)?, - blob_versioned_hashes: rlp.list_at(10)?, - ..Default::default() - } - }, - y_parity: rlp.val_at(11)?, - r: rlp.val_at(12)?, - s: rlp.val_at(13)?, - }) - } + fn decode(rlp: &rlp::Rlp) -> Result { + Ok(Transaction4844Signed { + transaction_4844_unsigned: { + Transaction4844Unsigned { + chain_id: rlp.val_at(0)?, + nonce: rlp.val_at(1)?, + max_priority_fee_per_gas: rlp.val_at(2)?, + max_fee_per_gas: rlp.val_at(3)?, + gas: rlp.val_at(4)?, + to: rlp.val_at(5)?, + value: rlp.val_at(6)?, + input: Bytes(rlp.val_at(7)?), + access_list: rlp.list_at(8)?, + max_fee_per_blob_gas: rlp.val_at(9)?, + blob_versioned_hashes: rlp.list_at(10)?, + ..Default::default() + } + }, + y_parity: rlp.val_at(11)?, + r: rlp.val_at(12)?, + s: rlp.val_at(13)?, + }) + } } /// See impl Decodable for TransactionLegacySigned { - fn decode(rlp: &rlp::Rlp) -> Result { - let v: U256 = rlp.val_at(6)?; - - let extract_chain_id = |v: U256| { - if v.ge(&35u32.into()) { - Some((v - 35) / 2) - } else { - None - } - }; - - Ok(TransactionLegacySigned { - transaction_legacy_unsigned: { - TransactionLegacyUnsigned { - nonce: rlp.val_at(0)?, - gas_price: rlp.val_at(1)?, - gas: rlp.val_at(2)?, - to: { - let to = rlp.at(3)?; - if to.is_empty() { - None - } else { - Some(to.as_val()?) - } - }, - value: rlp.val_at(4)?, - input: Bytes(rlp.val_at(5)?), - chain_id: extract_chain_id(v).map(|v| v.into()), - r#type: TypeLegacy {}, - } - }, - v, - r: rlp.val_at(7)?, - s: rlp.val_at(8)?, - }) - } + fn decode(rlp: &rlp::Rlp) -> Result { + let v: U256 = rlp.val_at(6)?; + + let extract_chain_id = |v: U256| { + if v.ge(&35u32.into()) { + Some((v - 35) / 2) + } else { + None + } + }; + + Ok(TransactionLegacySigned { + transaction_legacy_unsigned: { + TransactionLegacyUnsigned { + nonce: rlp.val_at(0)?, + gas_price: rlp.val_at(1)?, + gas: rlp.val_at(2)?, + to: { + let to = rlp.at(3)?; + if to.is_empty() { + None + } else { + Some(to.as_val()?) + } + }, + value: rlp.val_at(4)?, + input: Bytes(rlp.val_at(5)?), + chain_id: extract_chain_id(v).map(|v| v.into()), + r#type: TypeLegacy {}, + } + }, + v, + r: rlp.val_at(7)?, + s: rlp.val_at(8)?, + }) + } } #[cfg(test)] mod test { - use super::*; + use super::*; - #[test] - fn encode_decode_tx_works() { - let txs = [ + #[test] + fn encode_decode_tx_works() { + let txs = [ // Legacy ( "f86080808301e24194095e7baea6a6c7c4c2dfeb977efac326af552d87808025a0fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0a06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8", @@ -676,31 +672,31 @@ mod test { ) ]; - for (tx, json) in txs { - let raw_tx = alloy_core::hex::decode(tx).unwrap(); - let tx = TransactionSigned::decode(&raw_tx).unwrap(); - assert_eq!(tx.signed_payload(), raw_tx); - let expected_tx = serde_json::from_str(json).unwrap(); - assert_eq!(tx, expected_tx); - } - } - - #[test] - fn dummy_signed_payload_works() { - let tx: TransactionUnsigned = TransactionLegacyUnsigned { - chain_id: Some(596.into()), - gas: U256::from(21000), - nonce: U256::from(1), - gas_price: U256::from("0x640000006a"), - to: Some(Account::from(subxt_signer::eth::dev::baltathar()).address()), - value: U256::from(123123), - input: Bytes(vec![]), - r#type: TypeLegacy, - } - .into(); - - let dummy_signed_payload = tx.clone().dummy_signed_payload(); - let payload = Account::default().sign_transaction(tx).signed_payload(); - assert_eq!(dummy_signed_payload.len(), payload.len()); - } + for (tx, json) in txs { + let raw_tx = alloy_core::hex::decode(tx).unwrap(); + let tx = TransactionSigned::decode(&raw_tx).unwrap(); + assert_eq!(tx.signed_payload(), raw_tx); + let expected_tx = serde_json::from_str(json).unwrap(); + assert_eq!(tx, expected_tx); + } + } + + #[test] + fn dummy_signed_payload_works() { + let tx: TransactionUnsigned = TransactionLegacyUnsigned { + chain_id: Some(596.into()), + gas: U256::from(21000), + nonce: U256::from(1), + gas_price: U256::from("0x640000006a"), + to: Some(Account::from(subxt_signer::eth::dev::baltathar()).address()), + value: U256::from(123123), + input: Bytes(vec![]), + r#type: TypeLegacy, + } + .into(); + + let dummy_signed_payload = tx.clone().dummy_signed_payload(); + let payload = Account::default().sign_transaction(tx).signed_payload(); + assert_eq!(dummy_signed_payload.len(), payload.len()); + } } From b68cc0ff45119fe93a35583978f0731c5883652d Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 11 Nov 2025 16:44:38 +0100 Subject: [PATCH 3/7] simplify --- substrate/frame/revive/src/evm/api/rlp_codec.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/substrate/frame/revive/src/evm/api/rlp_codec.rs b/substrate/frame/revive/src/evm/api/rlp_codec.rs index 1ae911b8ff8d0..80f3853c05d0c 100644 --- a/substrate/frame/revive/src/evm/api/rlp_codec.rs +++ b/substrate/frame/revive/src/evm/api/rlp_codec.rs @@ -115,12 +115,8 @@ impl TransactionSigned { _ => Err(rlp::DecoderError::Custom("Unknown transaction type")), } } - // Legacy transactions: first byte in [0xc0, 0xff] - else if first_byte >= 0xc0 { - rlp::decode::(data).map(Into::into) - } else { - Err(rlp::DecoderError::Custom("Invalid transaction encoding")) - } + + rlp::decode::(data).map(Into::into) } } From fc4f470b7c4be3db7a96512e4e62526f225cbd13 Mon Sep 17 00:00:00 2001 From: "cmd[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 11 Nov 2025 15:44:49 +0000 Subject: [PATCH 4/7] Update from github-actions[bot] running command 'prdoc --audience runtime_dev --bump patch' --- prdoc/pr_10290.prdoc | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 prdoc/pr_10290.prdoc diff --git a/prdoc/pr_10290.prdoc b/prdoc/pr_10290.prdoc new file mode 100644 index 0000000000000..a7b43ca132e13 --- /dev/null +++ b/prdoc/pr_10290.prdoc @@ -0,0 +1,9 @@ +title: 'pallet-revive: fix eth tx decoding' +doc: +- audience: Runtime Dev + description: 'Fix Ethereum transaction decoding + + ' +crates: +- name: pallet-revive + bump: patch From 3a77570aeecb057c17ea2cc65d4dd1a4231c645e Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 11 Nov 2025 16:47:00 +0100 Subject: [PATCH 5/7] fix --- substrate/frame/revive/src/evm/api/rlp_codec.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/substrate/frame/revive/src/evm/api/rlp_codec.rs b/substrate/frame/revive/src/evm/api/rlp_codec.rs index 80f3853c05d0c..81f7e94dfb9a7 100644 --- a/substrate/frame/revive/src/evm/api/rlp_codec.rs +++ b/substrate/frame/revive/src/evm/api/rlp_codec.rs @@ -114,9 +114,9 @@ impl TransactionSigned { TYPE_EIP7702 => rlp::decode::(&data[1..]).map(Into::into), _ => Err(rlp::DecoderError::Custom("Unknown transaction type")), } + } else { + rlp::decode::(data).map(Into::into) } - - rlp::decode::(data).map(Into::into) } } From 25759c842bbca358284d79a9ece0f6bcb633aefb Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 11 Nov 2025 16:49:06 +0100 Subject: [PATCH 6/7] nit --- substrate/frame/revive/src/evm/api/rlp_codec.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/substrate/frame/revive/src/evm/api/rlp_codec.rs b/substrate/frame/revive/src/evm/api/rlp_codec.rs index 81f7e94dfb9a7..ae3c9d1f7009e 100644 --- a/substrate/frame/revive/src/evm/api/rlp_codec.rs +++ b/substrate/frame/revive/src/evm/api/rlp_codec.rs @@ -97,15 +97,13 @@ impl TransactionSigned { } /// Decode the Ethereum transaction from bytes. - /// According to EIP-2718, legacy transactions start with a byte in [0xc0, 0xff] (RLP list), - /// while typed transactions use type identifiers in [0x00, 0x7f]. pub fn decode(data: &[u8]) -> Result { if data.is_empty() { return Err(rlp::DecoderError::RlpIsTooShort); } let first_byte = data[0]; - // Typed transactions: first byte in [0x00, 0x7f] + // EIP-2718: Typed transactions use type identifiers in [0x00, 0x7f]. if first_byte <= 0x7f { match first_byte { TYPE_EIP2930 => rlp::decode::(&data[1..]).map(Into::into), From 6003536236ed1d13707d1087218f5b1e37329866 Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Tue, 11 Nov 2025 16:51:35 +0100 Subject: [PATCH 7/7] Apply suggestion from @pgherveou --- prdoc/pr_10290.prdoc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/prdoc/pr_10290.prdoc b/prdoc/pr_10290.prdoc index a7b43ca132e13..a8d59477a60a5 100644 --- a/prdoc/pr_10290.prdoc +++ b/prdoc/pr_10290.prdoc @@ -1,9 +1,7 @@ title: 'pallet-revive: fix eth tx decoding' doc: - audience: Runtime Dev - description: 'Fix Ethereum transaction decoding - - ' + description: 'Fix Ethereum transaction decoding' crates: - name: pallet-revive bump: patch