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

Commit 82e8d8f

Browse files
authored
[feat] support EIP 1559/2930 in testool (#1762)
### Description This pr enables support of EIP 1559/2930 in testool ### Issue Link [_link issue here_] ### Type of change - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] This change requires a documentation update - [ ] Refactor (no updates to logic)
1 parent 55754a9 commit 82e8d8f

File tree

11 files changed

+695
-233
lines changed

11 files changed

+695
-233
lines changed

eth-types/src/geth_types.rs

+142-10
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@ use crate::{
55
keccak256,
66
sign_types::{biguint_to_32bytes_le, ct_option_ok_or, recover_pk, SignData, SECP256K1_Q},
77
AccessList, Address, Block, Bytecode, Bytes, Error, GethExecTrace, Hash, ToBigEndian,
8-
ToLittleEndian, ToWord, Word, U64,
8+
ToLittleEndian, ToWord, Word, H256, U64,
99
};
1010
use ethers_core::{
11-
types::{transaction::response, NameOrAddress, TransactionRequest},
11+
types::{
12+
transaction::{eip2718::TypedTransaction, response},
13+
Eip1559TransactionRequest, Eip2930TransactionRequest, NameOrAddress, TransactionRequest,
14+
},
1215
utils::get_contract_address,
1316
};
1417
use ethers_signers::{LocalWallet, Signer};
@@ -18,6 +21,117 @@ use num_bigint::BigUint;
1821
use serde::{Serialize, Serializer};
1922
use serde_with::serde_as;
2023
use std::collections::HashMap;
24+
use strum_macros::EnumIter;
25+
26+
/// Tx type
27+
#[derive(Default, Debug, Copy, Clone, EnumIter, Serialize, PartialEq, Eq)]
28+
pub enum TxType {
29+
/// EIP 155 tx
30+
#[default]
31+
Eip155 = 0,
32+
/// Pre EIP 155 tx
33+
PreEip155,
34+
/// EIP 1559 tx
35+
Eip1559,
36+
/// EIP 2930 tx
37+
Eip2930,
38+
}
39+
40+
impl From<TxType> for usize {
41+
fn from(value: TxType) -> Self {
42+
value as usize
43+
}
44+
}
45+
46+
impl From<TxType> for u64 {
47+
fn from(value: TxType) -> Self {
48+
value as u64
49+
}
50+
}
51+
52+
impl TxType {
53+
/// If this type is PreEip155
54+
pub fn is_pre_eip155(&self) -> bool {
55+
matches!(*self, TxType::PreEip155)
56+
}
57+
58+
/// If this type is EIP155 or not
59+
pub fn is_eip155(&self) -> bool {
60+
matches!(*self, TxType::Eip155)
61+
}
62+
63+
/// If this type is Eip1559 or not
64+
pub fn is_eip1559(&self) -> bool {
65+
matches!(*self, TxType::Eip1559)
66+
}
67+
68+
/// If this type is Eip2930 or not
69+
pub fn is_eip2930(&self) -> bool {
70+
matches!(*self, TxType::Eip2930)
71+
}
72+
73+
/// Get the type of transaction
74+
pub fn get_tx_type(tx: &crate::Transaction) -> Self {
75+
match tx.transaction_type {
76+
Some(x) if x == U64::from(1) => Self::Eip2930,
77+
Some(x) if x == U64::from(2) => Self::Eip1559,
78+
_ => match tx.v.as_u64() {
79+
0 | 1 | 27 | 28 => Self::PreEip155,
80+
_ => Self::Eip155,
81+
},
82+
}
83+
}
84+
85+
/// Return the recovery id of signature for recovering the signing pk
86+
pub fn get_recovery_id(&self, v: u64) -> u8 {
87+
let recovery_id = match *self {
88+
TxType::Eip155 => (v + 1) % 2,
89+
TxType::PreEip155 => {
90+
assert!(v == 0x1b || v == 0x1c, "v: {v}");
91+
v - 27
92+
}
93+
TxType::Eip1559 => {
94+
assert!(v <= 1);
95+
v
96+
}
97+
TxType::Eip2930 => {
98+
assert!(v <= 1);
99+
v
100+
}
101+
};
102+
103+
recovery_id as u8
104+
}
105+
}
106+
107+
/// Get the RLP bytes for signing
108+
pub fn get_rlp_unsigned(tx: &crate::Transaction) -> Vec<u8> {
109+
let sig_v = tx.v;
110+
match TxType::get_tx_type(tx) {
111+
TxType::Eip155 => {
112+
let mut tx: TransactionRequest = tx.into();
113+
tx.chain_id = Some(tx.chain_id.unwrap_or_else(|| {
114+
let recv_v = TxType::Eip155.get_recovery_id(sig_v.as_u64()) as u64;
115+
(sig_v - recv_v - 35) / 2
116+
}));
117+
tx.rlp().to_vec()
118+
}
119+
TxType::PreEip155 => {
120+
let tx: TransactionRequest = tx.into();
121+
tx.rlp_unsigned().to_vec()
122+
}
123+
TxType::Eip1559 => {
124+
let tx: Eip1559TransactionRequest = tx.into();
125+
let typed_tx: TypedTransaction = tx.into();
126+
typed_tx.rlp().to_vec()
127+
}
128+
TxType::Eip2930 => {
129+
let tx: Eip2930TransactionRequest = tx.into();
130+
let typed_tx: TypedTransaction = tx.into();
131+
typed_tx.rlp().to_vec()
132+
}
133+
}
134+
}
21135

22136
/// Definition of all of the data related to an account.
23137
#[serde_as]
@@ -156,6 +270,8 @@ pub struct Withdrawal {
156270
/// Definition of all of the constants related to an Ethereum transaction.
157271
#[derive(Debug, Default, Clone, Serialize)]
158272
pub struct Transaction {
273+
/// Tx type
274+
pub tx_type: TxType,
159275
/// Sender address
160276
pub from: Address,
161277
/// Recipient address (None for contract creation)
@@ -172,9 +288,9 @@ pub struct Transaction {
172288
/// Gas Price
173289
pub gas_price: Word,
174290
/// Gas fee cap
175-
pub gas_fee_cap: Word,
291+
pub gas_fee_cap: Option<Word>,
176292
/// Gas tip cap
177-
pub gas_tip_cap: Word,
293+
pub gas_tip_cap: Option<Word>,
178294
/// The compiled code of a contract OR the first 4 bytes of the hash of the
179295
/// invoked method signature and encoded parameters. For details see
180296
/// Ethereum Contract ABI
@@ -188,6 +304,14 @@ pub struct Transaction {
188304
pub r: Word,
189305
/// "s" value of the transaction signature
190306
pub s: Word,
307+
308+
/// RLP bytes
309+
pub rlp_bytes: Vec<u8>,
310+
/// RLP unsigned bytes
311+
pub rlp_unsigned_bytes: Vec<u8>,
312+
313+
/// Transaction hash
314+
pub hash: H256,
191315
}
192316

193317
impl From<&Transaction> for crate::Transaction {
@@ -199,8 +323,8 @@ impl From<&Transaction> for crate::Transaction {
199323
gas: tx.gas_limit.to_word(),
200324
value: tx.value,
201325
gas_price: Some(tx.gas_price),
202-
max_priority_fee_per_gas: Some(tx.gas_tip_cap),
203-
max_fee_per_gas: Some(tx.gas_fee_cap),
326+
max_priority_fee_per_gas: tx.gas_tip_cap,
327+
max_fee_per_gas: tx.gas_fee_cap,
204328
input: tx.call_data.clone(),
205329
access_list: tx.access_list.clone(),
206330
v: tx.v.into(),
@@ -214,19 +338,23 @@ impl From<&Transaction> for crate::Transaction {
214338
impl From<&crate::Transaction> for Transaction {
215339
fn from(tx: &crate::Transaction) -> Transaction {
216340
Transaction {
341+
tx_type: TxType::get_tx_type(tx),
217342
from: tx.from,
218343
to: tx.to,
219344
nonce: tx.nonce.as_u64().into(),
220345
gas_limit: tx.gas.as_u64().into(),
221346
value: tx.value,
222347
gas_price: tx.gas_price.unwrap_or_default(),
223-
gas_tip_cap: tx.max_priority_fee_per_gas.unwrap_or_default(),
224-
gas_fee_cap: tx.max_fee_per_gas.unwrap_or_default(),
348+
gas_tip_cap: tx.max_priority_fee_per_gas,
349+
gas_fee_cap: tx.max_fee_per_gas,
225350
call_data: tx.input.clone(),
226351
access_list: tx.access_list.clone(),
227352
v: tx.v.as_u64(),
228353
r: tx.r,
229354
s: tx.s,
355+
rlp_bytes: tx.rlp().to_vec(),
356+
rlp_unsigned_bytes: get_rlp_unsigned(tx),
357+
hash: tx.hash,
230358
}
231359
}
232360
}
@@ -256,13 +384,14 @@ impl Transaction {
256384
gas_limit: U64::zero(),
257385
value: Word::zero(),
258386
gas_price: Word::zero(),
259-
gas_tip_cap: Word::zero(),
260-
gas_fee_cap: Word::zero(),
387+
gas_tip_cap: Some(Word::zero()),
388+
gas_fee_cap: Some(Word::zero()),
261389
call_data: Bytes::new(),
262390
access_list: None,
263391
v: 0,
264392
r: Word::zero(),
265393
s: Word::zero(),
394+
..Default::default()
266395
}
267396
}
268397
/// Return the SignData associated with this Transaction.
@@ -355,6 +484,9 @@ impl Transaction {
355484
s: self.s,
356485
v: U64::from(self.v),
357486
block_number: Some(block_number),
487+
transaction_type: Some(U64::from(self.tx_type as u64)),
488+
max_priority_fee_per_gas: self.gas_tip_cap,
489+
max_fee_per_gas: self.gas_fee_cap,
358490
chain_id: Some(chain_id),
359491
..response::Transaction::default()
360492
}

eth-types/src/lib.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@ use ethers_core::types;
4141
pub use ethers_core::{
4242
abi::ethereum_types::{BigEndianHash, U512},
4343
types::{
44-
transaction::{eip2930::AccessList, response::Transaction},
44+
transaction::{
45+
eip2930::{AccessList, AccessListItem},
46+
response::Transaction,
47+
},
4548
Address, Block, Bytes, Signature, H160, H256, H64, U256, U64,
4649
},
4750
};

external-tracer/src/lib.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use eth_types::{
55
Address, Error, GethExecTrace, Word,
66
};
77
use serde::Serialize;
8-
use std::collections::HashMap;
8+
use std::collections::BTreeMap;
99

1010
/// Configuration structure for `geth_utils::trace`
1111
#[derive(Debug, Default, Clone, Serialize)]
@@ -18,7 +18,7 @@ pub struct TraceConfig {
1818
/// block constants
1919
pub block_constants: BlockConstants,
2020
/// accounts
21-
pub accounts: HashMap<Address, Account>,
21+
pub accounts: BTreeMap<Address, Account>,
2222
/// transaction
2323
pub transactions: Vec<Transaction>,
2424
/// withdrawal
@@ -78,7 +78,8 @@ pub fn trace(config: &TraceConfig) -> Result<Vec<GethExecTrace>, Error> {
7878
let allowed_cases = error.starts_with("nonce too low")
7979
|| error.starts_with("nonce too high")
8080
|| error.starts_with("intrinsic gas too low")
81-
|| error.starts_with("insufficient funds for gas * price + value");
81+
|| error.starts_with("insufficient funds for gas * price + value")
82+
|| error.starts_with("insufficient funds for transfer");
8283
if trace.invalid && !allowed_cases {
8384
return Err(Error::TracingError(error.clone()));
8485
}

geth-utils/gethutil/trace.go

+21-16
Original file line numberDiff line numberDiff line change
@@ -99,19 +99,20 @@ type Account struct {
9999
}
100100

101101
type Transaction struct {
102-
From common.Address `json:"from"`
103-
To *common.Address `json:"to"`
104-
Nonce hexutil.Uint64 `json:"nonce"`
105-
Value *hexutil.Big `json:"value"`
106-
GasLimit hexutil.Uint64 `json:"gas_limit"`
107-
GasPrice *hexutil.Big `json:"gas_price"`
108-
GasFeeCap *hexutil.Big `json:"gas_fee_cap"`
109-
GasTipCap *hexutil.Big `json:"gas_tip_cap"`
110-
CallData hexutil.Bytes `json:"call_data"`
111-
AccessList []struct {
112-
Address common.Address `json:"address"`
113-
StorageKeys []common.Hash `json:"storage_keys"`
114-
} `json:"access_list"`
102+
From common.Address `json:"from"`
103+
To *common.Address `json:"to"`
104+
Nonce hexutil.Uint64 `json:"nonce"`
105+
Value *hexutil.Big `json:"value"`
106+
GasLimit hexutil.Uint64 `json:"gas_limit"`
107+
GasPrice *hexutil.Big `json:"gas_price"`
108+
GasFeeCap *hexutil.Big `json:"gas_fee_cap"`
109+
GasTipCap *hexutil.Big `json:"gas_tip_cap"`
110+
CallData hexutil.Bytes `json:"call_data"`
111+
AccessList types.AccessList `json:"access_list"`
112+
Type string `json:"tx_type"`
113+
V int64 `json:"v"`
114+
R *hexutil.Big `json:"r"`
115+
S *hexutil.Big `json:"s"`
115116
}
116117

117118
type TraceConfig struct {
@@ -160,10 +161,14 @@ func Trace(config TraceConfig) ([]*ExecutionResult, error) {
160161
blockGasLimit := toBigInt(config.Block.GasLimit).Uint64()
161162
messages := make([]core.Message, len(config.Transactions))
162163
for i, tx := range config.Transactions {
163-
// If gas price is specified directly, the tx is treated as legacy type.
164164
if tx.GasPrice != nil {
165-
tx.GasFeeCap = tx.GasPrice
166-
tx.GasTipCap = tx.GasPrice
165+
// Set GasFeeCap and GasTipCap to GasPrice if not exist.
166+
if tx.GasFeeCap == nil {
167+
tx.GasFeeCap = tx.GasPrice
168+
}
169+
if tx.GasTipCap == nil {
170+
tx.GasTipCap = tx.GasPrice
171+
}
167172
}
168173

169174
txAccessList := make(types.AccessList, len(tx.AccessList))

mock/src/transaction.rs

+8-8
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,8 @@ pub struct MockTransaction {
133133
pub s: Option<Word>,
134134
pub transaction_type: U64,
135135
pub access_list: AccessList,
136-
pub max_priority_fee_per_gas: Word,
137-
pub max_fee_per_gas: Word,
136+
pub max_priority_fee_per_gas: Option<Word>,
137+
pub max_fee_per_gas: Option<Word>,
138138
pub chain_id: Word,
139139
pub invalid: bool,
140140
}
@@ -158,8 +158,8 @@ impl Default for MockTransaction {
158158
s: None,
159159
transaction_type: U64::zero(),
160160
access_list: AccessList::default(),
161-
max_priority_fee_per_gas: Word::zero(),
162-
max_fee_per_gas: Word::zero(),
161+
max_priority_fee_per_gas: None,
162+
max_fee_per_gas: None,
163163
chain_id: *MOCK_CHAIN_ID,
164164
invalid: false,
165165
}
@@ -185,8 +185,8 @@ impl From<MockTransaction> for Transaction {
185185
s: mock.s.unwrap_or_default(),
186186
transaction_type: Some(mock.transaction_type),
187187
access_list: Some(mock.access_list),
188-
max_priority_fee_per_gas: Some(mock.max_priority_fee_per_gas),
189-
max_fee_per_gas: Some(mock.max_fee_per_gas),
188+
max_priority_fee_per_gas: mock.max_priority_fee_per_gas,
189+
max_fee_per_gas: mock.max_fee_per_gas,
190190
chain_id: Some(mock.chain_id),
191191
other: OtherFields::default(),
192192
}
@@ -289,13 +289,13 @@ impl MockTransaction {
289289

290290
/// Set max_priority_fee_per_gas field for the MockTransaction.
291291
pub fn max_priority_fee_per_gas(&mut self, max_priority_fee_per_gas: Word) -> &mut Self {
292-
self.max_priority_fee_per_gas = max_priority_fee_per_gas;
292+
self.max_priority_fee_per_gas = Some(max_priority_fee_per_gas);
293293
self
294294
}
295295

296296
/// Set max_fee_per_gas field for the MockTransaction.
297297
pub fn max_fee_per_gas(&mut self, max_fee_per_gas: Word) -> &mut Self {
298-
self.max_fee_per_gas = max_fee_per_gas;
298+
self.max_fee_per_gas = Some(max_fee_per_gas);
299299
self
300300
}
301301

0 commit comments

Comments
 (0)