Skip to content
This repository was archived by the owner on Apr 18, 2025. It is now read-only.

Commit c3fb00c

Browse files
committed
add eip1559/2930 support to json/yaml parser
1 parent 25d8ef4 commit c3fb00c

File tree

6 files changed

+387
-168
lines changed

6 files changed

+387
-168
lines changed

eth-types/src/lib.rs

Lines changed: 4 additions & 1 deletion
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
};

testool/src/statetest/executor.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ fn check_post(
138138
}
139139

140140
fn into_traceconfig(st: StateTest) -> (String, TraceConfig, StateTestResult) {
141+
let tx_type = st.tx_type();
142+
141143
let chain_id = 1;
142144
let wallet = LocalWallet::from_str(&hex::encode(st.secret_key.0)).unwrap();
143145
let mut tx = TransactionRequest::new()

testool/src/statetest/json.rs

Lines changed: 55 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,11 @@ struct JsonStateTest {
7171
#[derive(Debug, Clone, Deserialize)]
7272
#[serde(rename_all = "camelCase")]
7373
struct Transaction {
74+
access_list: Option<parse::RawAccessList>,
7475
data: Vec<String>,
7576
gas_limit: Vec<String>,
77+
max_priority_fee_per_gas: Option<String>,
78+
max_fee_per_gas: Option<String>,
7679
gas_price: String,
7780
nonce: String,
7881
secret_key: String,
@@ -109,8 +112,7 @@ impl<'a> JsonStateTestBuilder<'a> {
109112
/// generates `StateTest` vectors from a ethereum josn test specification
110113
pub fn load_json(&mut self, path: &str, source: &str) -> Result<Vec<StateTest>> {
111114
let mut state_tests = Vec::new();
112-
let tests: HashMap<String, JsonStateTest> =
113-
serde_json::from_str(&strip_json_comments(source))?;
115+
let tests: HashMap<String, JsonStateTest> = serde_json::from_str(source)?;
114116

115117
for (test_name, test) in tests {
116118
let env = Self::parse_env(&test.env)?;
@@ -120,13 +122,32 @@ impl<'a> JsonStateTestBuilder<'a> {
120122
let secret_key = parse::parse_bytes(&test.transaction.secret_key)?;
121123
let from = secret_key_to_address(&SigningKey::from_slice(&secret_key)?);
122124
let nonce = parse::parse_u64(&test.transaction.nonce)?;
123-
let gas_price = parse::parse_u256(&test.transaction.gas_price)?;
125+
126+
let max_priority_fee_per_gas = test
127+
.transaction
128+
.max_priority_fee_per_gas
129+
.map_or(Ok(None), |s| parse::parse_u256(&s).map(Some))?;
130+
let max_fee_per_gas = test
131+
.transaction
132+
.max_fee_per_gas
133+
.map_or(Ok(None), |s| parse::parse_u256(&s).map(Some))?;
134+
135+
// Set gas price to `min(max_priority_fee_per_gas + base_fee, max_fee_per_gas)` for
136+
// EIP-1559 transaction.
137+
// <https://github.com/ethereum/go-ethereum/blob/1485814f89d8206bb4a1c8e10a4a2893920f683a/core/state_transition.go#L167>
138+
let gas_price = parse::parse_u256(&test.transaction.gas_price).unwrap_or_else(|_| {
139+
max_fee_per_gas
140+
.unwrap()
141+
.min(max_priority_fee_per_gas.unwrap() + env.current_base_fee)
142+
});
143+
144+
let access_list = &test.transaction.access_list;
124145

125146
let data_s: Vec<_> = test
126147
.transaction
127148
.data
128149
.iter()
129-
.map(|item| parse::parse_calldata(self.compiler, item))
150+
.map(|item| parse::parse_calldata(self.compiler, item, access_list))
130151
.collect::<Result<_>>()?;
131152

132153
let gas_limit_s: Vec<_> = test
@@ -167,7 +188,7 @@ impl<'a> JsonStateTestBuilder<'a> {
167188
}
168189
}
169190

170-
for (idx_data, data) in data_s.iter().enumerate() {
191+
for (idx_data, calldata) in data_s.iter().enumerate() {
171192
for (idx_gas, gas_limit) in gas_limit_s.iter().enumerate() {
172193
for (idx_value, value) in value_s.iter().enumerate() {
173194
for (data_refs, gas_refs, value_refs, result) in &expects {
@@ -193,10 +214,13 @@ impl<'a> JsonStateTestBuilder<'a> {
193214
to,
194215
secret_key: secret_key.clone(),
195216
nonce,
217+
max_priority_fee_per_gas,
218+
max_fee_per_gas,
196219
gas_price,
197220
gas_limit: *gas_limit,
198221
value: *value,
199-
data: data.0.clone(),
222+
data: calldata.data.clone(),
223+
access_list: calldata.access_list.clone(),
200224
exception: false,
201225
});
202226
}
@@ -313,29 +337,10 @@ impl<'a> JsonStateTestBuilder<'a> {
313337
}
314338
}
315339

316-
fn strip_json_comments(json: &str) -> String {
317-
fn strip(value: Value) -> Value {
318-
use Value::*;
319-
match value {
320-
Array(vec) => Array(vec.into_iter().map(strip).collect()),
321-
Object(map) => Object(
322-
map.into_iter()
323-
.filter(|(k, _)| !k.starts_with("//"))
324-
.map(|(k, v)| (k, strip(v)))
325-
.collect(),
326-
),
327-
_ => value,
328-
}
329-
}
330-
331-
let value: Value = serde_json::from_str(json).unwrap();
332-
strip(value).to_string()
333-
}
334-
335340
#[cfg(test)]
336341
mod test {
337342
use super::*;
338-
use eth_types::{Bytes, H256};
343+
use eth_types::{address, AccessList, AccessListItem, Bytes, H256};
339344
use std::{collections::HashMap, str::FromStr};
340345

341346
const JSON: &str = r#"
@@ -381,6 +386,15 @@ mod test {
381386
}
382387
},
383388
"transaction" : {
389+
"accessList" : [
390+
{
391+
"address" : "0x009e7baea6a6c7c4c2dfeb977efac326af552d87",
392+
"storageKeys" : [
393+
"0x0000000000000000000000000000000000000000000000000000000000000000",
394+
"0x0000000000000000000000000000000000000000000000000000000000000001"
395+
]
396+
}
397+
],
384398
"data" : [
385399
"0x6001",
386400
"0x6002"
@@ -430,9 +444,24 @@ mod test {
430444
)?),
431445
gas_limit: 400000,
432446
gas_price: U256::from(10u64),
447+
max_fee_per_gas: None,
448+
max_priority_fee_per_gas: None,
433449
nonce: 0,
434450
value: U256::from(100000u64),
435451
data: Bytes::from(hex::decode("6001")?),
452+
access_list: Some(AccessList(vec![AccessListItem {
453+
address: address!("0x009e7baea6a6c7c4c2dfeb977efac326af552d87"),
454+
storage_keys: vec![
455+
H256::from_str(
456+
"0x0000000000000000000000000000000000000000000000000000000000000000",
457+
)
458+
.unwrap(),
459+
H256::from_str(
460+
"0x0000000000000000000000000000000000000000000000000000000000000001",
461+
)
462+
.unwrap(),
463+
],
464+
}])),
436465
pre: BTreeMap::from([(
437466
acc095e,
438467
Account {
@@ -460,13 +489,4 @@ mod test {
460489

461490
Ok(())
462491
}
463-
464-
#[test]
465-
fn test_strip() {
466-
let original = r#"{"//a":"a1","b":[{"c":"c1","//d":"d1"}]}"#;
467-
let expected = r#"{"b":[{"c":"c1"}]}"#;
468-
469-
let stripped = strip_json_comments(original);
470-
assert_eq!(expected, stripped);
471-
}
472492
}

testool/src/statetest/parse.rs

Lines changed: 107 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,52 @@
1-
use std::collections::HashMap;
2-
31
use crate::{abi, Compiler};
4-
52
use anyhow::{bail, Context, Result};
6-
use eth_types::{Address, Bytes, H256, U256};
3+
use eth_types::{address, AccessList, AccessListItem, Address, Bytes, H256, U256};
74
use log::debug;
85
use once_cell::sync::Lazy;
96
use regex::Regex;
7+
use serde::Deserialize;
8+
use std::{collections::HashMap, str::FromStr};
109

1110
type Label = String;
1211

12+
/// Raw access list to parse
13+
pub type RawAccessList = Vec<RawAccessListItem>;
14+
15+
/// Raw access list item to parse
16+
#[derive(Clone, Debug, Deserialize)]
17+
#[serde(rename_all = "camelCase")]
18+
pub struct RawAccessListItem {
19+
address: String,
20+
storage_keys: Vec<String>,
21+
}
22+
23+
impl RawAccessListItem {
24+
pub fn new(address: String, storage_keys: Vec<String>) -> Self {
25+
Self {
26+
address,
27+
storage_keys,
28+
}
29+
}
30+
}
31+
32+
/// parsed calldata
33+
#[derive(Debug)]
34+
pub struct Calldata {
35+
pub data: Bytes,
36+
pub label: Option<Label>,
37+
pub access_list: Option<AccessList>,
38+
}
39+
40+
impl Calldata {
41+
fn new(data: Bytes, label: Option<Label>, access_list: Option<AccessList>) -> Self {
42+
Self {
43+
data,
44+
label,
45+
access_list,
46+
}
47+
}
48+
}
49+
1350
static YUL_FRAGMENT_PARSER: Lazy<Regex> =
1451
Lazy::new(|| Regex::new(r#"\s*(?P<version>\w+)?\s*(?P<code>\{[\S\s]*)"#).unwrap());
1552

@@ -66,47 +103,17 @@ fn decompose_tags(expr: &str) -> HashMap<String, String> {
66103

67104
/// returns the element as calldata bytes, supports 0x, :raw, :abi, :yul and
68105
/// { LLL }
69-
pub fn parse_calldata(compiler: &Compiler, as_str: &str) -> Result<(Bytes, Option<Label>)> {
70-
let tags = decompose_tags(as_str);
106+
pub fn parse_calldata(
107+
compiler: &Compiler,
108+
data: &str,
109+
raw_access_list: &Option<RawAccessList>,
110+
) -> Result<Calldata> {
111+
let tags = decompose_tags(data);
71112
let label = tags.get(":label").cloned();
113+
let bytes = parse_call_bytes(compiler, tags)?;
114+
let access_list = parse_access_list(raw_access_list)?;
72115

73-
if let Some(notag) = tags.get("") {
74-
let notag = notag.trim();
75-
if notag.is_empty() {
76-
Ok((Bytes::default(), label))
77-
} else if notag.starts_with('{') {
78-
Ok((compiler.lll(notag)?, label))
79-
} else if let Some(hex) = notag.strip_prefix("0x") {
80-
Ok((Bytes::from(hex::decode(hex)?), label))
81-
} else {
82-
bail!("do not know what to do with calldata (1): '{:?}'", as_str);
83-
}
84-
} else if let Some(raw) = tags.get(":raw") {
85-
if let Some(hex) = raw.strip_prefix("0x") {
86-
Ok((Bytes::from(hex::decode(hex)?), label))
87-
} else {
88-
bail!("bad encoded calldata (3) {:?}", as_str)
89-
}
90-
} else if let Some(abi) = tags.get(":abi") {
91-
Ok((abi::encode_funccall(abi)?, label))
92-
} else if let Some(yul) = tags.get(":yul") {
93-
let caps = YUL_FRAGMENT_PARSER
94-
.captures(yul)
95-
.ok_or_else(|| anyhow::anyhow!("do not know what to do with code(4) '{:?}'", as_str))?;
96-
Ok((
97-
compiler.yul(
98-
caps.name("code").unwrap().as_str(),
99-
caps.name("version").map(|m| m.as_str()),
100-
)?,
101-
label,
102-
))
103-
} else {
104-
bail!(
105-
"do not know what to do with calldata: (2) {:?} '{:?}'",
106-
tags,
107-
as_str
108-
)
109-
}
116+
Ok(Calldata::new(bytes, label, access_list))
110117
}
111118

112119
/// parse entry as code, can be 0x, :raw or { LLL }
@@ -182,3 +189,60 @@ pub fn parse_u64(as_str: &str) -> Result<u64> {
182189
Ok(U256::from_str_radix(as_str, 10)?.as_u64())
183190
}
184191
}
192+
193+
// Parse calldata to bytes
194+
fn parse_call_bytes(compiler: &Compiler, tags: HashMap<String, String>) -> Result<Bytes> {
195+
if let Some(notag) = tags.get("") {
196+
let notag = notag.trim();
197+
if notag.is_empty() {
198+
Ok(Bytes::default())
199+
} else if notag.starts_with('{') {
200+
Ok(compiler.lll(notag)?)
201+
} else if let Some(hex) = notag.strip_prefix("0x") {
202+
Ok(Bytes::from(hex::decode(hex)?))
203+
} else {
204+
bail!("do not know what to do with calldata (1): '{tags:?}'");
205+
}
206+
} else if let Some(raw) = tags.get(":raw") {
207+
if let Some(hex) = raw.strip_prefix("0x") {
208+
Ok(Bytes::from(hex::decode(hex)?))
209+
} else {
210+
bail!("bad encoded calldata (3) {:?}", tags)
211+
}
212+
} else if let Some(abi) = tags.get(":abi") {
213+
Ok(abi::encode_funccall(abi)?)
214+
} else if let Some(yul) = tags.get(":yul") {
215+
let caps = YUL_FRAGMENT_PARSER
216+
.captures(yul)
217+
.ok_or_else(|| anyhow::anyhow!("do not know what to do with code(4) '{:?}'", tags))?;
218+
Ok(compiler.yul(
219+
caps.name("code").unwrap().as_str(),
220+
caps.name("version").map(|m| m.as_str()),
221+
)?)
222+
} else {
223+
bail!("do not know what to do with calldata: (2) '{:?}'", tags,)
224+
}
225+
}
226+
227+
// Parse access list
228+
fn parse_access_list(raw_access_list: &Option<RawAccessList>) -> Result<Option<AccessList>> {
229+
if let Some(raw_access_list) = raw_access_list {
230+
let mut items = Vec::with_capacity(raw_access_list.len());
231+
for raw in raw_access_list {
232+
let storage_keys = raw
233+
.storage_keys
234+
.iter()
235+
.map(|key| H256::from_str(key))
236+
.collect::<Result<_, _>>()?;
237+
238+
items.push(AccessListItem {
239+
address: address!(raw.address),
240+
storage_keys,
241+
});
242+
}
243+
244+
return Ok(Some(AccessList(items)));
245+
}
246+
247+
Ok(None)
248+
}

testool/src/statetest/spec.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,14 +89,19 @@ impl std::fmt::Display for StateTest {
8989
format!("{k} :")
9090
};
9191
let max_len = max_len - k.len();
92+
let v = v.chars().collect::<Vec<_>>();
9293
for i in 0..=v.len() / max_len {
9394
if i == 0 && !k.is_empty() {
9495
text.push_str(&k);
9596
} else {
9697
let padding: String = " ".repeat(k.len());
9798
text.push_str(&padding);
9899
}
99-
text.push_str(&v[i * max_len..std::cmp::min((i + 1) * max_len, v.len())]);
100+
text.push_str(
101+
&v[i * max_len..std::cmp::min((i + 1) * max_len, v.len())]
102+
.iter()
103+
.collect::<String>(),
104+
);
100105
text.push('\n');
101106
}
102107
text

0 commit comments

Comments
 (0)