Skip to content
Snippets Groups Projects
Unverified Commit d8ce5502 authored by PG Herveou's avatar PG Herveou Committed by GitHub
Browse files

[pallet-revive] Support all eth tx types (#6461)


Add support for all eth tx types
Note that js libs will continue to use the Legacy type since we don't
include base_fee_per_gas yet in the block.
We can think about setting these values after we revisit how we encode
the gas into weight & deposit_limit in a follow up PR

---------

Co-authored-by: default avatarAlexander Theißen <alex.theissen@me.com>
Co-authored-by: default avatarGitHub Action <action@github.com>
parent 6d59c3b1
No related merge requests found
Pipeline #506628 waiting for manual action with stages
in 1 hour, 10 minutes, and 30 seconds
Showing
with 824 additions and 190 deletions
# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json
title: '[pallet-revive] add support for all eth tx types'
doc:
- audience: Runtime Dev
description: Add support for 1559, 4844, and 2930 transaction types
crates:
- name: pallet-revive-eth-rpc
bump: minor
- name: pallet-revive
bump: minor
......@@ -17,13 +17,12 @@
//! The client connects to the source substrate chain
//! and is used by the rpc server to query and send transactions to the substrate chain.
use crate::{
rlp,
runtime::GAS_PRICE,
subxt_client::{
revive::{calls::types::EthTransact, events::ContractEmitted},
runtime_types::pallet_revive::storage::ContractInfo,
},
TransactionLegacySigned, LOG_TARGET,
LOG_TARGET,
};
use futures::{stream, StreamExt};
use jsonrpsee::types::{error::CALL_EXECUTION_FAILED_CODE, ErrorObjectOwned};
......@@ -269,25 +268,26 @@ impl ClientInner {
let extrinsics = extrinsics.iter().flat_map(|ext| {
let call = ext.as_extrinsic::<EthTransact>().ok()??;
let transaction_hash = H256(keccak_256(&call.payload));
let tx = rlp::decode::<TransactionLegacySigned>(&call.payload).ok()?;
let from = tx.recover_eth_address().ok()?;
let contract_address = if tx.transaction_legacy_unsigned.to.is_none() {
Some(create1(&from, tx.transaction_legacy_unsigned.nonce.try_into().ok()?))
let signed_tx = TransactionSigned::decode(&call.payload).ok()?;
let from = signed_tx.recover_eth_address().ok()?;
let tx_info = GenericTransaction::from_signed(signed_tx.clone(), Some(from));
let contract_address = if tx_info.to.is_none() {
Some(create1(&from, tx_info.nonce.unwrap_or_default().try_into().ok()?))
} else {
None
};
Some((from, tx, transaction_hash, contract_address, ext))
Some((from, signed_tx, tx_info, transaction_hash, contract_address, ext))
});
// Map each extrinsic to a receipt
stream::iter(extrinsics)
.map(|(from, tx, transaction_hash, contract_address, ext)| async move {
.map(|(from, signed_tx, tx_info, transaction_hash, contract_address, ext)| async move {
let events = ext.events().await?;
let tx_fees =
events.find_first::<TransactionFeePaid>()?.ok_or(ClientError::TxFeeNotFound)?;
let gas_price = tx.transaction_legacy_unsigned.gas_price;
let gas_price = tx_info.gas_price.unwrap_or_default();
let gas_used = (tx_fees.tip.saturating_add(tx_fees.actual_fee))
.checked_div(gas_price.as_u128())
.unwrap_or_default();
......@@ -324,16 +324,16 @@ impl ClientInner {
contract_address,
from,
logs,
tx.transaction_legacy_unsigned.to,
tx_info.to,
gas_price,
gas_used.into(),
success,
transaction_hash,
transaction_index.into(),
tx.transaction_legacy_unsigned.r#type.as_byte()
tx_info.r#type.unwrap_or_default()
);
Ok::<_, ClientError>((receipt.transaction_hash, (tx.into(), receipt)))
Ok::<_, ClientError>((receipt.transaction_hash, (signed_tx, receipt)))
})
.buffer_unordered(10)
.collect::<Vec<Result<_, _>>>()
......
......@@ -20,8 +20,7 @@
use crate::{EthRpcClient, ReceiptInfo};
use anyhow::Context;
use pallet_revive::evm::{
rlp::*, Account, BlockTag, Bytes, GenericTransaction, TransactionLegacyUnsigned, H160, H256,
U256,
Account, BlockTag, Bytes, GenericTransaction, TransactionLegacyUnsigned, H160, H256, U256,
};
/// Wait for a transaction receipt.
......@@ -169,11 +168,11 @@ impl TransactionBuilder {
mutate(&mut unsigned_tx);
let tx = signer.sign_transaction(unsigned_tx.clone());
let bytes = tx.rlp_bytes().to_vec();
let tx = signer.sign_transaction(unsigned_tx.into());
let bytes = tx.signed_payload();
let hash = client
.send_raw_transaction(bytes.clone().into())
.send_raw_transaction(bytes.into())
.await
.with_context(|| "transaction failed")?;
......
......@@ -137,7 +137,7 @@ impl EthRpcServer for EthRpcServerImpl {
async fn send_raw_transaction(&self, transaction: Bytes) -> RpcResult<H256> {
let hash = H256(keccak_256(&transaction.0));
let tx = rlp::decode::<TransactionLegacySigned>(&transaction.0).map_err(|err| {
let tx = TransactionSigned::decode(&transaction.0).map_err(|err| {
log::debug!(target: LOG_TARGET, "Failed to decode transaction: {err:?}");
EthRpcError::from(err)
})?;
......@@ -147,21 +147,10 @@ impl EthRpcServer for EthRpcServerImpl {
EthRpcError::InvalidSignature
})?;
let tx = GenericTransaction::from_signed(tx, Some(eth_addr));
// Dry run the transaction to get the weight limit and storage deposit limit
let TransactionLegacyUnsigned { to, input, value, .. } = tx.transaction_legacy_unsigned;
let dry_run = self
.client
.dry_run(
&GenericTransaction {
from: Some(eth_addr),
input: Some(input.clone()),
to,
value: Some(value),
..Default::default()
},
BlockTag::Latest.into(),
)
.await?;
let dry_run = self.client.dry_run(&tx, BlockTag::Latest.into()).await?;
let EthContractResult { gas_required, storage_deposit, .. } = dry_run;
let call = subxt_client::tx().revive().eth_transact(
......@@ -174,11 +163,10 @@ impl EthRpcServer for EthRpcServerImpl {
Ok(hash)
}
async fn send_transaction(&self, transaction: GenericTransaction) -> RpcResult<H256> {
async fn send_transaction(&self, mut transaction: GenericTransaction) -> RpcResult<H256> {
log::debug!(target: LOG_TARGET, "{transaction:#?}");
let GenericTransaction { from, gas, gas_price, input, to, value, r#type, .. } = transaction;
let Some(from) = from else {
let Some(from) = transaction.from else {
log::debug!(target: LOG_TARGET, "Transaction must have a sender");
return Err(EthRpcError::InvalidTransaction.into());
};
......@@ -189,27 +177,26 @@ impl EthRpcServer for EthRpcServerImpl {
.find(|account| account.address() == from)
.ok_or(EthRpcError::AccountNotFound(from))?;
let gas_price = gas_price.unwrap_or_else(|| U256::from(GAS_PRICE));
let chain_id = Some(self.client.chain_id().into());
let input = input.unwrap_or_default();
let value = value.unwrap_or_default();
let r#type = r#type.unwrap_or_default();
if transaction.gas.is_none() {
transaction.gas = Some(self.estimate_gas(transaction.clone(), None).await?);
}
let Some(gas) = gas else {
log::debug!(target: LOG_TARGET, "Transaction must have a gas limit");
return Err(EthRpcError::InvalidTransaction.into());
};
if transaction.gas_price.is_none() {
transaction.gas_price = Some(self.gas_price().await?);
}
let r#type = Type0::try_from_byte(r#type.clone())
.map_err(|_| EthRpcError::TransactionTypeNotSupported(r#type))?;
if transaction.nonce.is_none() {
transaction.nonce =
Some(self.get_transaction_count(from, BlockTag::Latest.into()).await?);
}
let nonce = self.get_transaction_count(from, BlockTag::Latest.into()).await?;
if transaction.chain_id.is_none() {
transaction.chain_id = Some(self.chain_id().await?);
}
let tx =
TransactionLegacyUnsigned { chain_id, gas, gas_price, input, nonce, to, value, r#type };
let tx = account.sign_transaction(tx);
let rlp_bytes = rlp::encode(&tx).to_vec();
self.send_raw_transaction(Bytes(rlp_bytes)).await
let tx = transaction.try_into_unsigned().map_err(|_| EthRpcError::InvalidTransaction)?;
let payload = account.sign_transaction(tx).signed_payload();
self.send_raw_transaction(Bytes(payload)).await
}
async fn get_block_by_hash(
......
......@@ -25,9 +25,7 @@ pub use rlp;
mod type_id;
pub use type_id::*;
#[cfg(feature = "std")]
mod rpc_types;
mod rpc_types_gen;
pub use rpc_types_gen::*;
......
......@@ -16,10 +16,9 @@
// limitations under the License.
//! Utilities for working with Ethereum accounts.
use crate::{
evm::{TransactionLegacySigned, TransactionLegacyUnsigned},
evm::{TransactionSigned, TransactionUnsigned},
H160,
};
use rlp::Encodable;
use sp_runtime::AccountId32;
/// A simple account that can sign transactions
......@@ -38,6 +37,11 @@ impl From<subxt_signer::eth::Keypair> for Account {
}
impl Account {
/// Create a new account from a secret
pub fn from_secret_key(secret_key: [u8; 32]) -> Self {
subxt_signer::eth::Keypair::from_secret_key(secret_key).unwrap().into()
}
/// Get the [`H160`] address of the account.
pub fn address(&self) -> H160 {
H160::from_slice(&self.0.public_key().to_account_id().as_ref())
......@@ -52,9 +56,21 @@ impl Account {
}
/// Sign a transaction.
pub fn sign_transaction(&self, tx: TransactionLegacyUnsigned) -> TransactionLegacySigned {
let rlp_encoded = tx.rlp_bytes();
let signature = self.0.sign(&rlp_encoded);
TransactionLegacySigned::from(tx, signature.as_ref())
pub fn sign_transaction(&self, tx: TransactionUnsigned) -> TransactionSigned {
let payload = tx.unsigned_payload();
let signature = self.0.sign(&payload).0;
tx.with_signature(signature)
}
}
#[test]
fn from_secret_key_works() {
let account = Account::from_secret_key(hex_literal::hex!(
"a872f6cbd25a0e04a08b1e21098017a9e6194d101d75e13111f71410c59cd57f"
));
assert_eq!(
account.address(),
H160::from(hex_literal::hex!("75e480db528101a381ce68544611c169ad7eb342"))
)
}
......@@ -21,6 +21,73 @@ use super::*;
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<u8> {
use TransactionUnsigned::*;
let mut s = rlp::RlpStream::new();
match self {
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 {
/// Encode the Ethereum transaction into bytes.
pub fn signed_payload(&self) -> Vec<u8> {
use TransactionSigned::*;
let mut s = rlp::RlpStream::new();
match self {
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<Self, rlp::DecoderError> {
if data.len() < 1 {
return Err(rlp::DecoderError::RlpIsTooShort);
}
match data[0] {
TYPE_EIP2930 => rlp::decode::<Transaction2930Signed>(&data[1..]).map(Into::into),
TYPE_EIP1559 => rlp::decode::<Transaction1559Signed>(&data[1..]).map(Into::into),
TYPE_EIP4844 => rlp::decode::<Transaction4844Signed>(&data[1..]).map(Into::into),
_ => rlp::decode::<TransactionLegacySigned>(data).map(Into::into),
}
}
}
impl TransactionLegacyUnsigned {
/// Get the rlp encoded bytes of a signed transaction with a dummy 65 bytes signature.
pub fn dummy_signed_payload(&self) -> Vec<u8> {
......@@ -47,8 +114,8 @@ impl Encodable for TransactionLegacyUnsigned {
s.append(&self.value);
s.append(&self.input.0);
s.append(&chain_id);
s.append(&0_u8);
s.append(&0_u8);
s.append(&0u8);
s.append(&0u8);
} else {
s.begin_list(6);
s.append(&self.nonce);
......@@ -64,7 +131,6 @@ impl Encodable for TransactionLegacyUnsigned {
}
}
/// See <https://eips.ethereum.org/EIPS/eip-155>
impl Decodable for TransactionLegacyUnsigned {
fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
Ok(TransactionLegacyUnsigned {
......@@ -95,16 +161,18 @@ impl Decodable for TransactionLegacyUnsigned {
impl Encodable for TransactionLegacySigned {
fn rlp_append(&self, s: &mut rlp::RlpStream) {
let tx = &self.transaction_legacy_unsigned;
s.begin_list(9);
s.append(&self.transaction_legacy_unsigned.nonce);
s.append(&self.transaction_legacy_unsigned.gas_price);
s.append(&self.transaction_legacy_unsigned.gas);
match self.transaction_legacy_unsigned.to {
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(&self.transaction_legacy_unsigned.value);
s.append(&self.transaction_legacy_unsigned.input.0);
s.append(&tx.value);
s.append(&tx.input.0);
s.append(&self.v);
s.append(&self.r);
......@@ -112,6 +180,232 @@ impl Encodable for TransactionLegacySigned {
}
}
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);
}
}
impl Decodable for AccessListEntry {
fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
Ok(AccessListEntry { address: rlp.val_at(0)?, storage_keys: rlp.list_at(1)? })
}
}
/// See <https://eips.ethereum.org/EIPS/eip-1559>
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);
}
}
/// See <https://eips.ethereum.org/EIPS/eip-1559>
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);
}
}
impl Decodable for Transaction1559Signed {
fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
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);
}
}
//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);
}
}
impl Decodable for Transaction2930Signed {
fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
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-4844
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);
}
}
//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);
}
}
impl Decodable for Transaction4844Signed {
fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
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 <https://eips.ethereum.org/EIPS/eip-155>
impl Decodable for TransactionLegacySigned {
fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
......@@ -142,7 +436,7 @@ impl Decodable for TransactionLegacySigned {
value: rlp.val_at(4)?,
input: Bytes(rlp.val_at(5)?),
chain_id: extract_chain_id(v).map(|v| v.into()),
r#type: Type0 {},
r#type: TypeLegacy {},
}
},
v,
......@@ -157,26 +451,118 @@ mod test {
use super::*;
#[test]
fn encode_decode_legacy_transaction_works() {
let tx = 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: Type0,
};
fn encode_decode_tx_works() {
let txs = [
// Legacy
(
"f86080808301e24194095e7baea6a6c7c4c2dfeb977efac326af552d87808025a0fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0a06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8",
r#"
{
"chainId": "0x1",
"gas": "0x1e241",
"gasPrice": "0x0",
"input": "0x",
"nonce": "0x0",
"to": "0x095e7baea6a6c7c4c2dfeb977efac326af552d87",
"type": "0x0",
"value": "0x0",
"r": "0xfe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0",
"s": "0x6de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8",
"v": "0x25"
}
"#
),
// type 1: EIP2930
(
"01f89b0180808301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080a0fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0a06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8",
r#"
{
"accessList": [
{
"address": "0x0000000000000000000000000000000000000001",
"storageKeys": ["0x0000000000000000000000000000000000000000000000000000000000000000"]
}
],
"chainId": "0x1",
"gas": "0x1e241",
"gasPrice": "0x0",
"input": "0x",
"nonce": "0x0",
"to": "0x095e7baea6a6c7c4c2dfeb977efac326af552d87",
"type": "0x1",
"value": "0x0",
"r": "0xfe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0",
"s": "0x6de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8",
"yParity": "0x0"
}
"#
),
// type 2: EIP1559
(
"02f89c018080018301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080a0fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0a06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8",
r#"
{
"accessList": [
{
"address": "0x0000000000000000000000000000000000000001",
"storageKeys": ["0x0000000000000000000000000000000000000000000000000000000000000000"]
}
],
"chainId": "0x1",
"gas": "0x1e241",
"gasPrice": "0x0",
"input": "0x",
"maxFeePerGas": "0x1",
"maxPriorityFeePerGas": "0x0",
"nonce": "0x0",
"to": "0x095e7baea6a6c7c4c2dfeb977efac326af552d87",
"type": "0x2",
"value": "0x0",
"r": "0xfe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0",
"s": "0x6de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8",
"yParity": "0x0"
let rlp_bytes = rlp::encode(&tx);
let decoded = rlp::decode::<TransactionLegacyUnsigned>(&rlp_bytes).unwrap();
assert_eq!(&tx, &decoded);
}
"#
),
// type 3: EIP4844
(
let tx = Account::default().sign_transaction(tx);
let rlp_bytes = rlp::encode(&tx);
let decoded = rlp::decode::<TransactionLegacySigned>(&rlp_bytes).unwrap();
assert_eq!(&tx, &decoded);
"03f8bf018002018301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080e1a0000000000000000000000000000000000000000000000000000000000000000080a0fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0a06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8",
r#"
{
"accessList": [
{
"address": "0x0000000000000000000000000000000000000001",
"storageKeys": ["0x0000000000000000000000000000000000000000000000000000000000000000"]
}
],
"blobVersionedHashes": ["0x0000000000000000000000000000000000000000000000000000000000000000"],
"chainId": "0x1",
"gas": "0x1e241",
"input": "0x",
"maxFeePerBlobGas": "0x0",
"maxFeePerGas": "0x1",
"maxPriorityFeePerGas": "0x2",
"nonce": "0x0",
"to": "0x095e7baea6a6c7c4c2dfeb977efac326af552d87",
"type": "0x3",
"value": "0x0",
"r": "0xfe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0",
"s": "0x6de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8",
"yParity": "0x0"
}
"#
)
];
for (tx, json) in txs {
let raw_tx = 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]
......@@ -189,31 +575,12 @@ mod test {
to: Some(Account::from(subxt_signer::eth::dev::baltathar()).address()),
value: U256::from(123123),
input: Bytes(vec![]),
r#type: Type0,
r#type: TypeLegacy,
};
let signed_tx = Account::default().sign_transaction(tx.clone());
let rlp_bytes = rlp::encode(&signed_tx);
assert_eq!(tx.dummy_signed_payload().len(), rlp_bytes.len());
}
#[test]
fn recover_address_works() {
let account = Account::default();
let unsigned_tx = TransactionLegacyUnsigned {
value: 200_000_000_000_000_000_000u128.into(),
gas_price: 100_000_000_200u64.into(),
gas: 100_107u32.into(),
nonce: 3.into(),
to: Some(Account::from(subxt_signer::eth::dev::baltathar()).address()),
chain_id: Some(596.into()),
..Default::default()
};
let tx = account.sign_transaction(unsigned_tx.clone());
let recovered_address = tx.recover_eth_address().unwrap();
assert_eq!(account.address(), recovered_address);
let dummy_signed_payload = tx.dummy_signed_payload();
let tx: TransactionUnsigned = tx.into();
let payload = Account::default().sign_transaction(tx).signed_payload();
assert_eq!(dummy_signed_payload.len(), payload.len());
}
}
......@@ -16,7 +16,8 @@
// limitations under the License.
//! Utility impl for the RPC types.
use super::*;
use sp_core::U256;
use alloc::vec::Vec;
use sp_core::{H160, U256};
impl TransactionInfo {
/// Create a new [`TransactionInfo`] from a receipt and a signed transaction.
......@@ -138,3 +139,140 @@ fn logs_bloom_works() {
.unwrap();
assert_eq!(receipt.logs_bloom, ReceiptInfo::logs_bloom(&receipt.logs));
}
impl GenericTransaction {
/// Create a new [`GenericTransaction`] from a signed transaction.
pub fn from_signed(tx: TransactionSigned, from: Option<H160>) -> Self {
use TransactionSigned::*;
match tx {
TransactionLegacySigned(tx) => {
let tx = tx.transaction_legacy_unsigned;
GenericTransaction {
from,
r#type: Some(tx.r#type.as_byte()),
chain_id: tx.chain_id,
input: Some(tx.input),
nonce: Some(tx.nonce),
value: Some(tx.value),
to: tx.to,
gas: Some(tx.gas),
gas_price: Some(tx.gas_price),
..Default::default()
}
},
Transaction4844Signed(tx) => {
let tx = tx.transaction_4844_unsigned;
GenericTransaction {
from,
r#type: Some(tx.r#type.as_byte()),
chain_id: Some(tx.chain_id),
input: Some(tx.input),
nonce: Some(tx.nonce),
value: Some(tx.value),
to: Some(tx.to),
gas: Some(tx.gas),
gas_price: Some(tx.max_fee_per_blob_gas),
access_list: Some(tx.access_list),
blob_versioned_hashes: Some(tx.blob_versioned_hashes),
max_fee_per_blob_gas: Some(tx.max_fee_per_blob_gas),
max_fee_per_gas: Some(tx.max_fee_per_gas),
max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas),
..Default::default()
}
},
Transaction1559Signed(tx) => {
let tx = tx.transaction_1559_unsigned;
GenericTransaction {
from,
r#type: Some(tx.r#type.as_byte()),
chain_id: Some(tx.chain_id),
input: Some(tx.input),
nonce: Some(tx.nonce),
value: Some(tx.value),
to: tx.to,
gas: Some(tx.gas),
gas_price: Some(tx.gas_price),
access_list: Some(tx.access_list),
max_fee_per_gas: Some(tx.max_fee_per_gas),
max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas),
..Default::default()
}
},
Transaction2930Signed(tx) => {
let tx = tx.transaction_2930_unsigned;
GenericTransaction {
from,
r#type: Some(tx.r#type.as_byte()),
chain_id: Some(tx.chain_id),
input: Some(tx.input),
nonce: Some(tx.nonce),
value: Some(tx.value),
to: tx.to,
gas: Some(tx.gas),
gas_price: Some(tx.gas_price),
access_list: Some(tx.access_list),
..Default::default()
}
},
}
}
/// Convert to a [`TransactionUnsigned`].
pub fn try_into_unsigned(self) -> Result<TransactionUnsigned, ()> {
match self.r#type.unwrap_or_default().0 {
TYPE_LEGACY => Ok(TransactionLegacyUnsigned {
r#type: TypeLegacy {},
chain_id: self.chain_id,
input: self.input.unwrap_or_default(),
nonce: self.nonce.unwrap_or_default(),
value: self.value.unwrap_or_default(),
to: self.to,
gas: self.gas.unwrap_or_default(),
gas_price: self.gas_price.unwrap_or_default(),
}
.into()),
TYPE_EIP1559 => Ok(Transaction1559Unsigned {
r#type: TypeEip1559 {},
chain_id: self.chain_id.unwrap_or_default(),
input: self.input.unwrap_or_default(),
nonce: self.nonce.unwrap_or_default(),
value: self.value.unwrap_or_default(),
to: self.to,
gas: self.gas.unwrap_or_default(),
gas_price: self.gas_price.unwrap_or_default(),
access_list: self.access_list.unwrap_or_default(),
max_fee_per_gas: self.max_fee_per_gas.unwrap_or_default(),
max_priority_fee_per_gas: self.max_priority_fee_per_gas.unwrap_or_default(),
}
.into()),
TYPE_EIP2930 => Ok(Transaction2930Unsigned {
r#type: TypeEip2930 {},
chain_id: self.chain_id.unwrap_or_default(),
input: self.input.unwrap_or_default(),
nonce: self.nonce.unwrap_or_default(),
value: self.value.unwrap_or_default(),
to: self.to,
gas: self.gas.unwrap_or_default(),
gas_price: self.gas_price.unwrap_or_default(),
access_list: self.access_list.unwrap_or_default(),
}
.into()),
TYPE_EIP4844 => Ok(Transaction4844Unsigned {
r#type: TypeEip4844 {},
chain_id: self.chain_id.unwrap_or_default(),
input: self.input.unwrap_or_default(),
nonce: self.nonce.unwrap_or_default(),
value: self.value.unwrap_or_default(),
to: self.to.unwrap_or_default(),
gas: self.gas.unwrap_or_default(),
max_fee_per_gas: self.max_fee_per_gas.unwrap_or_default(),
max_fee_per_blob_gas: self.max_fee_per_blob_gas.unwrap_or_default(),
max_priority_fee_per_gas: self.max_priority_fee_per_gas.unwrap_or_default(),
access_list: self.access_list.unwrap_or_default(),
blob_versioned_hashes: self.blob_versioned_hashes.unwrap_or_default(),
}
.into()),
_ => Err(()),
}
}
}
......@@ -17,7 +17,7 @@
//! Generated JSON-RPC types.
#![allow(missing_docs)]
use super::{byte::*, Type0, Type1, Type2, Type3};
use super::{byte::*, TypeEip1559, TypeEip2930, TypeEip4844, TypeLegacy};
use alloc::vec::Vec;
use codec::{Decode, Encode};
use derive_more::{From, TryInto};
......@@ -455,7 +455,7 @@ pub struct Transaction1559Unsigned {
/// to address
pub to: Option<Address>,
/// type
pub r#type: Type2,
pub r#type: TypeEip1559,
/// value
pub value: U256,
}
......@@ -486,7 +486,7 @@ pub struct Transaction2930Unsigned {
/// to address
pub to: Option<Address>,
/// type
pub r#type: Type1,
pub r#type: TypeEip2930,
/// value
pub value: U256,
}
......@@ -530,7 +530,7 @@ pub struct Transaction4844Unsigned {
/// to address
pub to: Address,
/// type
pub r#type: Type3,
pub r#type: TypeEip4844,
/// value
pub value: U256,
}
......@@ -557,7 +557,7 @@ pub struct TransactionLegacyUnsigned {
/// to address
pub to: Option<Address>,
/// type
pub r#type: Type0,
pub r#type: TypeLegacy,
/// value
pub value: U256,
}
......@@ -622,8 +622,8 @@ pub struct Transaction1559Signed {
pub v: Option<U256>,
/// yParity
/// The parity (0 for even, 1 for odd) of the y-value of the secp256k1 signature.
#[serde(rename = "yParity", skip_serializing_if = "Option::is_none")]
pub y_parity: Option<U256>,
#[serde(rename = "yParity")]
pub y_parity: U256,
}
/// Signed 2930 Transaction
......@@ -661,8 +661,8 @@ pub struct Transaction4844Signed {
pub s: U256,
/// yParity
/// The parity (0 for even, 1 for odd) of the y-value of the secp256k1 signature.
#[serde(rename = "yParity", skip_serializing_if = "Option::is_none")]
pub y_parity: Option<U256>,
#[serde(rename = "yParity")]
pub y_parity: U256,
}
/// Signed Legacy Transaction
......
......@@ -15,49 +15,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//! Ethereum signature utilities
use super::{TransactionLegacySigned, TransactionLegacyUnsigned};
use rlp::Encodable;
use super::*;
use sp_core::{H160, U256};
use sp_io::{crypto::secp256k1_ecdsa_recover, hashing::keccak_256};
impl TransactionLegacyUnsigned {
/// Recover the Ethereum address, from an RLP encoded transaction and a 65 bytes signature.
pub fn recover_eth_address(rlp_encoded: &[u8], signature: &[u8; 65]) -> Result<H160, ()> {
let hash = keccak_256(rlp_encoded);
let mut addr = H160::default();
let pk = secp256k1_ecdsa_recover(&signature, &hash).map_err(|_| ())?;
addr.assign_from_slice(&keccak_256(&pk[..])[12..]);
Ok(addr)
}
}
impl TransactionLegacySigned {
/// Create a signed transaction from an [`TransactionLegacyUnsigned`] and a signature.
pub fn from(
transaction_legacy_unsigned: TransactionLegacyUnsigned,
signature: &[u8; 65],
) -> TransactionLegacySigned {
let r = U256::from_big_endian(&signature[..32]);
let s = U256::from_big_endian(&signature[32..64]);
let recovery_id = signature[64] as u32;
let v = transaction_legacy_unsigned
.chain_id
.map(|chain_id| chain_id * 2 + 35 + recovery_id)
.unwrap_or_else(|| U256::from(27) + recovery_id);
TransactionLegacySigned { transaction_legacy_unsigned, r, s, v }
}
/// Get the raw 65 bytes signature from the signed transaction.
pub fn raw_signature(&self) -> Result<[u8; 65], ()> {
let mut s = [0u8; 65];
self.r.write_as_big_endian(s[0..32].as_mut());
self.s.write_as_big_endian(s[32..64].as_mut());
s[64] = self.extract_recovery_id().ok_or(())?;
Ok(s)
}
/// Get the recovery ID from the signed transaction.
/// See https://eips.ethereum.org/EIPS/eip-155
fn extract_recovery_id(&self) -> Option<u8> {
......@@ -71,10 +33,154 @@ impl TransactionLegacySigned {
self.v.try_into().ok()
}
}
}
impl TransactionUnsigned {
/// Extract the unsigned transaction from a signed transaction.
pub fn from_signed(tx: TransactionSigned) -> Self {
match tx {
TransactionSigned::TransactionLegacySigned(signed) =>
Self::TransactionLegacyUnsigned(signed.transaction_legacy_unsigned),
TransactionSigned::Transaction4844Signed(signed) =>
Self::Transaction4844Unsigned(signed.transaction_4844_unsigned),
TransactionSigned::Transaction1559Signed(signed) =>
Self::Transaction1559Unsigned(signed.transaction_1559_unsigned),
TransactionSigned::Transaction2930Signed(signed) =>
Self::Transaction2930Unsigned(signed.transaction_2930_unsigned),
}
}
/// Create a signed transaction from an [`TransactionUnsigned`] and a signature.
pub fn with_signature(self, signature: [u8; 65]) -> TransactionSigned {
let r = U256::from_big_endian(&signature[..32]);
let s = U256::from_big_endian(&signature[32..64]);
let recovery_id = signature[64];
match self {
TransactionUnsigned::Transaction2930Unsigned(transaction_2930_unsigned) =>
Transaction2930Signed {
transaction_2930_unsigned,
r,
s,
v: None,
y_parity: U256::from(recovery_id),
}
.into(),
TransactionUnsigned::Transaction1559Unsigned(transaction_1559_unsigned) =>
Transaction1559Signed {
transaction_1559_unsigned,
r,
s,
v: None,
y_parity: U256::from(recovery_id),
}
.into(),
TransactionUnsigned::Transaction4844Unsigned(transaction_4844_unsigned) =>
Transaction4844Signed {
transaction_4844_unsigned,
r,
s,
y_parity: U256::from(recovery_id),
}
.into(),
TransactionUnsigned::TransactionLegacyUnsigned(transaction_legacy_unsigned) => {
let v = transaction_legacy_unsigned
.chain_id
.map(|chain_id| {
chain_id
.saturating_mul(U256::from(2))
.saturating_add(U256::from(35u32 + recovery_id as u32))
})
.unwrap_or_else(|| U256::from(27u32 + recovery_id as u32));
TransactionLegacySigned { transaction_legacy_unsigned, r, s, v }.into()
},
}
}
}
impl TransactionSigned {
/// Get the raw 65 bytes signature from the signed transaction.
pub fn raw_signature(&self) -> Result<[u8; 65], ()> {
use TransactionSigned::*;
let (r, s, v) = match self {
TransactionLegacySigned(tx) => (tx.r, tx.s, tx.extract_recovery_id().ok_or(())?),
Transaction4844Signed(tx) => (tx.r, tx.s, tx.y_parity.try_into().map_err(|_| ())?),
Transaction1559Signed(tx) => (tx.r, tx.s, tx.y_parity.try_into().map_err(|_| ())?),
Transaction2930Signed(tx) => (tx.r, tx.s, tx.y_parity.try_into().map_err(|_| ())?),
};
let mut sig = [0u8; 65];
r.write_as_big_endian(sig[0..32].as_mut());
s.write_as_big_endian(sig[32..64].as_mut());
sig[64] = v;
Ok(sig)
}
/// Recover the Ethereum address from the signed transaction.
/// Recover the Ethereum address, from a signed transaction.
pub fn recover_eth_address(&self) -> Result<H160, ()> {
let rlp_encoded = self.transaction_legacy_unsigned.rlp_bytes();
TransactionLegacyUnsigned::recover_eth_address(&rlp_encoded, &self.raw_signature()?)
use TransactionSigned::*;
let mut s = rlp::RlpStream::new();
match self {
TransactionLegacySigned(tx) => {
let tx = &tx.transaction_legacy_unsigned;
s.append(tx);
},
Transaction4844Signed(tx) => {
let tx = &tx.transaction_4844_unsigned;
s.append(&tx.r#type.value());
s.append(tx);
},
Transaction1559Signed(tx) => {
let tx = &tx.transaction_1559_unsigned;
s.append(&tx.r#type.value());
s.append(tx);
},
Transaction2930Signed(tx) => {
let tx = &tx.transaction_2930_unsigned;
s.append(&tx.r#type.value());
s.append(tx);
},
}
let bytes = s.out().to_vec();
let signature = self.raw_signature()?;
let hash = keccak_256(&bytes);
let mut addr = H160::default();
let pk = secp256k1_ecdsa_recover(&signature, &hash).map_err(|_| ())?;
addr.assign_from_slice(&keccak_256(&pk[..])[12..]);
Ok(addr)
}
}
#[test]
fn sign_and_recover_work() {
use crate::evm::TransactionUnsigned;
let txs = [
// Legacy
"f86080808301e24194095e7baea6a6c7c4c2dfeb977efac326af552d87808026a07b2e762a17a71a46b422e60890a04512cf0d907ccf6b78b5bd6e6977efdc2bf5a01ea673d50bbe7c2236acb498ceb8346a8607c941f0b8cbcde7cf439aa9369f1f",
//// type 1: EIP2930
"01f89b0180808301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080a0c45a61b3d1d00169c649e7326e02857b850efb96e587db4b9aad29afc80d0752a070ae1eb47ab4097dbed2f19172ae286492621b46ac737ee6c32fb18a00c94c9c",
// type 2: EIP1559
"02f89c018080018301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080a055d72bbc3047d4b9d3e4b8099f187143202407746118204cc2e0cb0c85a68baea04f6ef08a1418c70450f53398d9f0f2d78d9e9d6b8a80cba886b67132c4a744f2",
// type 3: EIP4844
"03f8bf018002018301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080e1a0000000000000000000000000000000000000000000000000000000000000000001a0672b8bac466e2cf1be3148c030988d40d582763ecebbc07700dfc93bb070d8a4a07c635887005b11cb58964c04669ac2857fa633aa66f662685dadfd8bcacb0f21",
];
let account = Account::from_secret_key(hex_literal::hex!(
"a872f6cbd25a0e04a08b1e21098017a9e6194d101d75e13111f71410c59cd57f"
));
for tx in txs {
let raw_tx = hex::decode(tx).unwrap();
let tx = TransactionSigned::decode(&raw_tx).unwrap();
let address = tx.recover_eth_address();
assert_eq!(address.unwrap(), account.address());
let unsigned = TransactionUnsigned::from_signed(tx.clone());
let signed = account.sign_transaction(unsigned);
assert_eq!(tx, signed);
}
}
......@@ -17,6 +17,7 @@
//! Ethereum Typed Transaction types
use super::Byte;
use codec::{Decode, Encode};
use paste::paste;
use rlp::Decodable;
use scale_info::TypeInfo;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
......@@ -29,8 +30,14 @@ macro_rules! transaction_type {
#[derive(Clone, Default, Debug, Eq, PartialEq)]
pub struct $name;
// upper case const name
paste! {
#[doc = concat!("Transaction value for type identifier: ", $value)]
pub const [<$name:snake:upper>]: u8 = $value;
}
impl $name {
/// Get the value of the type
/// Convert to u8
pub fn value(&self) -> u8 {
$value
}
......@@ -107,7 +114,12 @@ macro_rules! transaction_type {
};
}
transaction_type!(Type0, 0);
transaction_type!(Type1, 1);
transaction_type!(Type2, 2);
transaction_type!(Type3, 3);
transaction_type!(TypeLegacy, 0);
transaction_type!(TypeEip2930, 1);
transaction_type!(TypeEip1559, 2);
transaction_type!(TypeEip4844, 3);
#[test]
fn transaction_type() {
assert_eq!(TYPE_EIP2930, 1u8);
}
......@@ -16,7 +16,7 @@
// limitations under the License.
//! Runtime types for integrating `pallet-revive` with the EVM.
use crate::{
evm::api::{TransactionLegacySigned, TransactionLegacyUnsigned},
evm::api::{GenericTransaction, TransactionSigned},
AccountIdOf, AddressMapper, BalanceOf, MomentOf, Weight, LOG_TARGET,
};
use codec::{Decode, Encode};
......@@ -293,7 +293,7 @@ pub trait EthExtra {
CallOf<Self::Config>: From<crate::Call<Self::Config>>,
<Self::Config as frame_system::Config>::Hash: frame_support::traits::IsType<H256>,
{
let tx = rlp::decode::<TransactionLegacySigned>(&payload).map_err(|err| {
let tx = TransactionSigned::decode(&payload).map_err(|err| {
log::debug!(target: LOG_TARGET, "Failed to decode transaction: {err:?}");
InvalidTransaction::Call
})?;
......@@ -305,33 +305,33 @@ pub trait EthExtra {
let signer =
<Self::Config as crate::Config>::AddressMapper::to_fallback_account_id(&signer);
let TransactionLegacyUnsigned { nonce, chain_id, to, value, input, gas, gas_price, .. } =
tx.transaction_legacy_unsigned;
let GenericTransaction { nonce, chain_id, to, value, input, gas, gas_price, .. } =
GenericTransaction::from_signed(tx, None);
if chain_id.unwrap_or_default() != <Self::Config as crate::Config>::ChainId::get().into() {
log::debug!(target: LOG_TARGET, "Invalid chain_id {chain_id:?}");
return Err(InvalidTransaction::Call);
}
let value = crate::Pallet::<Self::Config>::convert_evm_to_native(value).map_err(|err| {
log::debug!(target: LOG_TARGET, "Failed to convert value to native: {err:?}");
InvalidTransaction::Call
})?;
let value = crate::Pallet::<Self::Config>::convert_evm_to_native(value.unwrap_or_default())
.map_err(|err| {
log::debug!(target: LOG_TARGET, "Failed to convert value to native: {err:?}");
InvalidTransaction::Call
})?;
let data = input.unwrap_or_default().0;
let call = if let Some(dest) = to {
crate::Call::call::<Self::Config> {
dest,
value,
gas_limit,
storage_deposit_limit,
data: input.0,
data,
}
} else {
let blob = match polkavm::ProgramBlob::blob_length(&input.0) {
Some(blob_len) => blob_len
.try_into()
.ok()
.and_then(|blob_len| (input.0.split_at_checked(blob_len))),
let blob = match polkavm::ProgramBlob::blob_length(&data) {
Some(blob_len) =>
blob_len.try_into().ok().and_then(|blob_len| (data.split_at_checked(blob_len))),
_ => None,
};
......@@ -350,18 +350,18 @@ pub trait EthExtra {
}
};
let nonce = nonce.try_into().map_err(|_| InvalidTransaction::Call)?;
let nonce = nonce.unwrap_or_default().try_into().map_err(|_| InvalidTransaction::Call)?;
// Fees calculated with the fixed `GAS_PRICE`
// When we dry-run the transaction, we set the gas to `Fee / GAS_PRICE`
let eth_fee_no_tip = U256::from(GAS_PRICE)
.saturating_mul(gas)
.saturating_mul(gas.unwrap_or_default())
.try_into()
.map_err(|_| InvalidTransaction::Call)?;
// Fees with the actual gas_price from the transaction.
let eth_fee: BalanceOf<Self::Config> = U256::from(gas_price)
.saturating_mul(gas)
let eth_fee: BalanceOf<Self::Config> = U256::from(gas_price.unwrap_or_default())
.saturating_mul(gas.unwrap_or_default())
.try_into()
.map_err(|_| InvalidTransaction::Call)?;
......@@ -414,7 +414,6 @@ mod test {
};
use frame_support::{error::LookupError, traits::fungible::Mutate};
use pallet_revive_fixtures::compile_module;
use rlp::Encodable;
use sp_runtime::{
traits::{Checkable, DispatchTransaction},
MultiAddress, MultiSignature,
......@@ -523,7 +522,7 @@ mod test {
100_000_000_000_000,
);
let payload = account.sign_transaction(tx).rlp_bytes().to_vec();
let payload = account.sign_transaction(tx.into()).signed_payload();
let call = RuntimeCall::Contracts(crate::Call::eth_transact {
payload,
gas_limit,
......
......@@ -768,7 +768,7 @@ pub mod pallet {
///
/// # Parameters
///
/// * `payload`: The RLP-encoded [`crate::evm::TransactionLegacySigned`].
/// * `payload`: The encoded [`crate::evm::TransactionSigned`].
/// * `gas_limit`: The gas limit enforced during contract execution.
/// * `storage_deposit_limit`: The maximum balance that can be charged to the caller for
/// storage usage.
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment