Skip to content
This repository has been archived by the owner on Jul 5, 2024. It is now read-only.

[feat] support EIP 1559/2930 in testool #1762

152 changes: 142 additions & 10 deletions eth-types/src/geth_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ use crate::{
keccak256,
sign_types::{biguint_to_32bytes_le, ct_option_ok_or, recover_pk, SignData, SECP256K1_Q},
AccessList, Address, Block, Bytecode, Bytes, Error, GethExecTrace, Hash, ToBigEndian,
ToLittleEndian, ToWord, Word, U64,
ToLittleEndian, ToWord, Word, H256, U64,
};
use ethers_core::{
types::{transaction::response, NameOrAddress, TransactionRequest},
types::{
transaction::{eip2718::TypedTransaction, response},
Eip1559TransactionRequest, Eip2930TransactionRequest, NameOrAddress, TransactionRequest,
},
utils::get_contract_address,
};
use ethers_signers::{LocalWallet, Signer};
Expand All @@ -18,6 +21,117 @@ use num_bigint::BigUint;
use serde::{Serialize, Serializer};
use serde_with::serde_as;
use std::collections::HashMap;
use strum_macros::EnumIter;

/// Tx type
#[derive(Default, Debug, Copy, Clone, EnumIter, Serialize, PartialEq, Eq)]
pub enum TxType {
/// EIP 155 tx
#[default]
Eip155 = 0,
/// Pre EIP 155 tx
PreEip155,
/// EIP 1559 tx
Eip1559,
/// EIP 2930 tx
Eip2930,
}

impl From<TxType> for usize {
fn from(value: TxType) -> Self {
value as usize
}
}

impl From<TxType> for u64 {
fn from(value: TxType) -> Self {
value as u64
}
}

impl TxType {
/// If this type is PreEip155
pub fn is_pre_eip155(&self) -> bool {
matches!(*self, TxType::PreEip155)
}

/// If this type is EIP155 or not
pub fn is_eip155(&self) -> bool {
matches!(*self, TxType::Eip155)
}

/// If this type is Eip1559 or not
pub fn is_eip1559(&self) -> bool {
matches!(*self, TxType::Eip1559)
}

/// If this type is Eip2930 or not
pub fn is_eip2930(&self) -> bool {
matches!(*self, TxType::Eip2930)
}

/// Get the type of transaction
pub fn get_tx_type(tx: &crate::Transaction) -> Self {
match tx.transaction_type {
Some(x) if x == U64::from(1) => Self::Eip2930,
Some(x) if x == U64::from(2) => Self::Eip1559,
_ => match tx.v.as_u64() {
0 | 1 | 27 | 28 => Self::PreEip155,
_ => Self::Eip155,
},
}
}

/// Return the recovery id of signature for recovering the signing pk
pub fn get_recovery_id(&self, v: u64) -> u8 {
let recovery_id = match *self {
TxType::Eip155 => (v + 1) % 2,
TxType::PreEip155 => {
assert!(v == 0x1b || v == 0x1c, "v: {v}");
v - 27
}
TxType::Eip1559 => {
assert!(v <= 1);
v
}
TxType::Eip2930 => {
assert!(v <= 1);
v
}
};

recovery_id as u8
}
}

/// Get the RLP bytes for signing
pub fn get_rlp_unsigned(tx: &crate::Transaction) -> Vec<u8> {
let sig_v = tx.v;
match TxType::get_tx_type(tx) {
TxType::Eip155 => {
let mut tx: TransactionRequest = tx.into();
tx.chain_id = Some(tx.chain_id.unwrap_or_else(|| {
let recv_v = TxType::Eip155.get_recovery_id(sig_v.as_u64()) as u64;
(sig_v - recv_v - 35) / 2
}));
tx.rlp().to_vec()
}
TxType::PreEip155 => {
let tx: TransactionRequest = tx.into();
tx.rlp_unsigned().to_vec()
}
TxType::Eip1559 => {
let tx: Eip1559TransactionRequest = tx.into();
let typed_tx: TypedTransaction = tx.into();
typed_tx.rlp().to_vec()
}
TxType::Eip2930 => {
let tx: Eip2930TransactionRequest = tx.into();
let typed_tx: TypedTransaction = tx.into();
typed_tx.rlp().to_vec()
}
}
}

/// Definition of all of the data related to an account.
#[serde_as]
Expand Down Expand Up @@ -156,6 +270,8 @@ pub struct Withdrawal {
/// Definition of all of the constants related to an Ethereum transaction.
#[derive(Debug, Default, Clone, Serialize)]
pub struct Transaction {
/// Tx type
pub tx_type: TxType,
/// Sender address
pub from: Address,
/// Recipient address (None for contract creation)
Expand All @@ -172,9 +288,9 @@ pub struct Transaction {
/// Gas Price
pub gas_price: Word,
/// Gas fee cap
pub gas_fee_cap: Word,
pub gas_fee_cap: Option<Word>,
/// Gas tip cap
pub gas_tip_cap: Word,
pub gas_tip_cap: Option<Word>,
/// The compiled code of a contract OR the first 4 bytes of the hash of the
/// invoked method signature and encoded parameters. For details see
/// Ethereum Contract ABI
Expand All @@ -188,6 +304,14 @@ pub struct Transaction {
pub r: Word,
/// "s" value of the transaction signature
pub s: Word,

/// RLP bytes
pub rlp_bytes: Vec<u8>,
/// RLP unsigned bytes
pub rlp_unsigned_bytes: Vec<u8>,

/// Transaction hash
pub hash: H256,
}

impl From<&Transaction> for crate::Transaction {
Expand All @@ -199,8 +323,8 @@ impl From<&Transaction> for crate::Transaction {
gas: tx.gas_limit.to_word(),
value: tx.value,
gas_price: Some(tx.gas_price),
max_priority_fee_per_gas: Some(tx.gas_tip_cap),
max_fee_per_gas: Some(tx.gas_fee_cap),
max_priority_fee_per_gas: tx.gas_tip_cap,
max_fee_per_gas: tx.gas_fee_cap,
input: tx.call_data.clone(),
access_list: tx.access_list.clone(),
v: tx.v.into(),
Expand All @@ -214,19 +338,23 @@ impl From<&Transaction> for crate::Transaction {
impl From<&crate::Transaction> for Transaction {
fn from(tx: &crate::Transaction) -> Transaction {
Transaction {
tx_type: TxType::get_tx_type(tx),
from: tx.from,
to: tx.to,
nonce: tx.nonce.as_u64().into(),
gas_limit: tx.gas.as_u64().into(),
value: tx.value,
gas_price: tx.gas_price.unwrap_or_default(),
gas_tip_cap: tx.max_priority_fee_per_gas.unwrap_or_default(),
gas_fee_cap: tx.max_fee_per_gas.unwrap_or_default(),
gas_tip_cap: tx.max_priority_fee_per_gas,
gas_fee_cap: tx.max_fee_per_gas,
call_data: tx.input.clone(),
access_list: tx.access_list.clone(),
v: tx.v.as_u64(),
r: tx.r,
s: tx.s,
rlp_bytes: tx.rlp().to_vec(),
rlp_unsigned_bytes: get_rlp_unsigned(tx),
hash: tx.hash,
}
}
}
Expand Down Expand Up @@ -256,13 +384,14 @@ impl Transaction {
gas_limit: U64::zero(),
value: Word::zero(),
gas_price: Word::zero(),
gas_tip_cap: Word::zero(),
gas_fee_cap: Word::zero(),
gas_tip_cap: Some(Word::zero()),
gas_fee_cap: Some(Word::zero()),
call_data: Bytes::new(),
access_list: None,
v: 0,
r: Word::zero(),
s: Word::zero(),
..Default::default()
}
}
/// Return the SignData associated with this Transaction.
Expand Down Expand Up @@ -355,6 +484,9 @@ impl Transaction {
s: self.s,
v: U64::from(self.v),
block_number: Some(block_number),
transaction_type: Some(U64::from(self.tx_type as u64)),
max_priority_fee_per_gas: self.gas_tip_cap,
max_fee_per_gas: self.gas_fee_cap,
chain_id: Some(chain_id),
..response::Transaction::default()
}
Expand Down
5 changes: 4 additions & 1 deletion eth-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ use ethers_core::types;
pub use ethers_core::{
abi::ethereum_types::{BigEndianHash, U512},
types::{
transaction::{eip2930::AccessList, response::Transaction},
transaction::{
eip2930::{AccessList, AccessListItem},
response::Transaction,
},
Address, Block, Bytes, Signature, H160, H256, H64, U256, U64,
},
};
Expand Down
7 changes: 4 additions & 3 deletions external-tracer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use eth_types::{
Address, Error, GethExecTrace, Word,
};
use serde::Serialize;
use std::collections::HashMap;
use std::collections::BTreeMap;

/// Configuration structure for `geth_utils::trace`
#[derive(Debug, Default, Clone, Serialize)]
Expand All @@ -18,7 +18,7 @@ pub struct TraceConfig {
/// block constants
pub block_constants: BlockConstants,
/// accounts
pub accounts: HashMap<Address, Account>,
pub accounts: BTreeMap<Address, Account>,
/// transaction
pub transactions: Vec<Transaction>,
/// withdrawal
Expand Down Expand Up @@ -78,7 +78,8 @@ pub fn trace(config: &TraceConfig) -> Result<Vec<GethExecTrace>, Error> {
let allowed_cases = error.starts_with("nonce too low")
|| error.starts_with("nonce too high")
|| error.starts_with("intrinsic gas too low")
|| error.starts_with("insufficient funds for gas * price + value");
|| error.starts_with("insufficient funds for gas * price + value")
|| error.starts_with("insufficient funds for transfer");
if trace.invalid && !allowed_cases {
return Err(Error::TracingError(error.clone()));
}
Expand Down
37 changes: 21 additions & 16 deletions geth-utils/gethutil/trace.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,19 +99,20 @@ type Account struct {
}

type Transaction struct {
From common.Address `json:"from"`
To *common.Address `json:"to"`
Nonce hexutil.Uint64 `json:"nonce"`
Value *hexutil.Big `json:"value"`
GasLimit hexutil.Uint64 `json:"gas_limit"`
GasPrice *hexutil.Big `json:"gas_price"`
GasFeeCap *hexutil.Big `json:"gas_fee_cap"`
GasTipCap *hexutil.Big `json:"gas_tip_cap"`
CallData hexutil.Bytes `json:"call_data"`
AccessList []struct {
Address common.Address `json:"address"`
StorageKeys []common.Hash `json:"storage_keys"`
} `json:"access_list"`
From common.Address `json:"from"`
To *common.Address `json:"to"`
Nonce hexutil.Uint64 `json:"nonce"`
Value *hexutil.Big `json:"value"`
GasLimit hexutil.Uint64 `json:"gas_limit"`
GasPrice *hexutil.Big `json:"gas_price"`
GasFeeCap *hexutil.Big `json:"gas_fee_cap"`
GasTipCap *hexutil.Big `json:"gas_tip_cap"`
CallData hexutil.Bytes `json:"call_data"`
AccessList types.AccessList `json:"access_list"`
Type string `json:"tx_type"`
V int64 `json:"v"`
R *hexutil.Big `json:"r"`
S *hexutil.Big `json:"s"`
}

type TraceConfig struct {
Expand Down Expand Up @@ -160,10 +161,14 @@ func Trace(config TraceConfig) ([]*ExecutionResult, error) {
blockGasLimit := toBigInt(config.Block.GasLimit).Uint64()
messages := make([]core.Message, len(config.Transactions))
for i, tx := range config.Transactions {
// If gas price is specified directly, the tx is treated as legacy type.
if tx.GasPrice != nil {
tx.GasFeeCap = tx.GasPrice
tx.GasTipCap = tx.GasPrice
// Set GasFeeCap and GasTipCap to GasPrice if not exist.
if tx.GasFeeCap == nil {
tx.GasFeeCap = tx.GasPrice
}
if tx.GasTipCap == nil {
tx.GasTipCap = tx.GasPrice
}
}

txAccessList := make(types.AccessList, len(tx.AccessList))
Expand Down
16 changes: 8 additions & 8 deletions mock/src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,8 @@ pub struct MockTransaction {
pub s: Option<Word>,
pub transaction_type: U64,
pub access_list: AccessList,
pub max_priority_fee_per_gas: Word,
pub max_fee_per_gas: Word,
pub max_priority_fee_per_gas: Option<Word>,
pub max_fee_per_gas: Option<Word>,
pub chain_id: Word,
pub invalid: bool,
}
Expand All @@ -158,8 +158,8 @@ impl Default for MockTransaction {
s: None,
transaction_type: U64::zero(),
access_list: AccessList::default(),
max_priority_fee_per_gas: Word::zero(),
max_fee_per_gas: Word::zero(),
max_priority_fee_per_gas: None,
max_fee_per_gas: None,
chain_id: *MOCK_CHAIN_ID,
invalid: false,
}
Expand All @@ -185,8 +185,8 @@ impl From<MockTransaction> for Transaction {
s: mock.s.unwrap_or_default(),
transaction_type: Some(mock.transaction_type),
access_list: Some(mock.access_list),
max_priority_fee_per_gas: Some(mock.max_priority_fee_per_gas),
max_fee_per_gas: Some(mock.max_fee_per_gas),
max_priority_fee_per_gas: mock.max_priority_fee_per_gas,
max_fee_per_gas: mock.max_fee_per_gas,
chain_id: Some(mock.chain_id),
other: OtherFields::default(),
}
Expand Down Expand Up @@ -289,13 +289,13 @@ impl MockTransaction {

/// Set max_priority_fee_per_gas field for the MockTransaction.
pub fn max_priority_fee_per_gas(&mut self, max_priority_fee_per_gas: Word) -> &mut Self {
self.max_priority_fee_per_gas = max_priority_fee_per_gas;
self.max_priority_fee_per_gas = Some(max_priority_fee_per_gas);
self
}

/// Set max_fee_per_gas field for the MockTransaction.
pub fn max_fee_per_gas(&mut self, max_fee_per_gas: Word) -> &mut Self {
self.max_fee_per_gas = max_fee_per_gas;
self.max_fee_per_gas = Some(max_fee_per_gas);
self
}

Expand Down
Loading
Loading