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

Commit 0a2a602

Browse files
lightsinglispc
andauthored
(1) testool: use cache skip success cases (2) testool enable parallel compilation (3) fix many corner cases (#798)
* use cache skip success cases * update comments * dynamic tx table * clippy * make skip_tests/skip_paths opt * write cached result * many fixes * write cached result * fix oob * better err log * fix write_cache * update codehash * Revert "update codehash" This reverts commit 0e3b077. * parallel compile testcase * testool: --use_cache to enable cache --------- Co-authored-by: Zhang Zhuo <[email protected]>
1 parent 2773896 commit 0a2a602

22 files changed

+182
-131
lines changed

bus-mapping/src/evm/opcodes/callop.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,6 @@ impl<const N_ARGS: usize> Opcode for CallOpcode<N_ARGS> {
104104
let callee_code_hash = callee_call.code_hash;
105105
let callee_acc = state.sdb.get_account(&callee_address).1;
106106
let callee_exists = !callee_acc.is_empty();
107-
108107
let (callee_code_hash_word, is_empty_code_hash) = if callee_exists {
109108
(
110109
callee_code_hash.to_word(),
@@ -170,14 +169,14 @@ impl<const N_ARGS: usize> Opcode for CallOpcode<N_ARGS> {
170169
let is_precompile = code_address
171170
.map(|ref addr| is_precompiled(addr))
172171
.unwrap_or(false);
173-
// TODO: What about transfer for CALLCODE?
172+
// CALLCODE does not need to do real transfer.
174173
// Transfer value only for CALL opcode, is_precheck_ok = true.
175174
if callee_call.kind == CallKind::Call && is_precheck_ok {
176175
state.transfer(
177176
&mut exec_step,
178177
callee_call.caller_address,
179178
callee_call.address,
180-
callee_exists || is_precompile,
179+
callee_exists,
181180
false,
182181
callee_call.value,
183182
)?;
@@ -473,7 +472,9 @@ impl<const N_ARGS: usize> Opcode for CallOpcode<N_ARGS> {
473472

474473
debug_assert_eq!(
475474
geth_steps[0].gas.0 - gas_cost - precompile_call_gas_cost + stipend,
476-
geth_steps[1].gas.0
475+
geth_steps[1].gas.0,
476+
"precompile_call_gas_cost wrong {:?}",
477+
precompile_step.exec_state
477478
);
478479

479480
Ok(vec![exec_step, precompile_step])

bus-mapping/src/evm/opcodes/create.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ impl<const IS_CREATE2: bool> Opcode for Create<IS_CREATE2> {
244244

245245
if !is_precheck_ok {
246246
for (field, value) in [
247-
(CallContextField::LastCalleeId, 0.into()),
247+
(CallContextField::LastCalleeId, callee.call_id.into()),
248248
(CallContextField::LastCalleeReturnDataOffset, 0.into()),
249249
(CallContextField::LastCalleeReturnDataLength, 0.into()),
250250
] {
@@ -314,12 +314,13 @@ impl<const IS_CREATE2: bool> Opcode for Create<IS_CREATE2> {
314314

315315
if length == 0 || is_address_collision {
316316
for (field, value) in [
317-
(CallContextField::LastCalleeId, 0.into()),
317+
(CallContextField::LastCalleeId, callee.call_id.into()),
318318
(CallContextField::LastCalleeReturnDataOffset, 0.into()),
319319
(CallContextField::LastCalleeReturnDataLength, 0.into()),
320320
] {
321321
state.call_context_write(&mut exec_step, caller.call_id, field, value);
322322
}
323+
state.caller_ctx_mut()?.return_data.clear();
323324
state.handle_return(&mut exec_step, geth_steps, false)?;
324325
}
325326

bus-mapping/src/evm/opcodes/returndatasize.rs

+6-3
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,16 @@ impl Opcode for Returndatasize {
2727
);
2828

2929
// TODO: fix error in deposit_ether.json...
30-
if value.as_usize() != state.call_ctx()?.return_data.len() {
30+
let real_return_data_len = value.as_usize();
31+
let local_return_data_len = state.call_ctx()?.return_data.len();
32+
if real_return_data_len != local_return_data_len {
3133
log::error!(
3234
"return_data.len() != RETURNDATASIZE value, {} != {}, step: {:?}",
33-
state.call_ctx()?.return_data.len(),
34-
value.as_usize(),
35+
local_return_data_len,
36+
real_return_data_len,
3537
geth_step
3638
);
39+
debug_assert_eq!(real_return_data_len, local_return_data_len);
3740
}
3841

3942
state.stack_write(

bus-mapping/src/evm/opcodes/stop.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ impl Opcode for Stop {
3131
CallContextField::IsSuccess,
3232
1.into(),
3333
);
34-
34+
if let Ok(caller) = state.caller_ctx_mut() {
35+
caller.return_data.clear();
36+
}
3537
state.handle_return(&mut exec_step, geth_steps, !call.is_root)?;
3638

3739
Ok(vec![exec_step])

testool/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ ctor = "0.1.22"
3939

4040
[features]
4141
default = ["ignore-test-docker", "skip-self-destruct", "shanghai"]
42+
onephase = ["zkevm-circuits/onephase"]
4243
ignore-test-docker = []
4344
skip-self-destruct = []
4445
shanghai = ["bus-mapping/shanghai", "eth-types/shanghai", "mock/shanghai", "zkevm-circuits/shanghai"]

testool/Config.toml

-9
Original file line numberDiff line numberDiff line change
@@ -167,15 +167,6 @@ tests = []
167167

168168
# skipped tests, do not need to be fixed --------------------------------------------------
169169

170-
[[skip_tests]]
171-
desc = "geth should emit ErrGasUintOverflow rather than ErrOutOfGas"
172-
tests = [
173-
"MSTORE_Bounds2_d0_g0_v0",
174-
"createNameRegistratorOutOfMemoryBonds1_d0_g0_v0",
175-
"createNameRegistratorOutOfMemoryBonds0_d0_g0_v0",
176-
"CallToNameRegistratorMemOOGAndInsufficientBalance_d0_g0_v0",
177-
]
178-
179170
# ignored paths -------------------------------------------------------------------------
180171

181172
[[skip_paths]]

testool/src/compiler.rs

+25-15
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use std::{
1010
path::PathBuf,
1111
process::{Command, Stdio},
1212
str::FromStr,
13+
sync::Mutex,
1314
};
1415

1516
struct Cache {
@@ -201,16 +202,17 @@ struct SourceLocation {
201202

202203
#[derive(Default)]
203204
pub struct Compiler {
204-
cache: Option<Cache>,
205+
cache: Option<Mutex<Cache>>,
205206
compile: bool,
206207
}
207208

208209
impl Compiler {
209210
pub fn new(compile: bool, cache_path: Option<PathBuf>) -> Result<Self> {
210-
let cache = cache_path.map(Cache::new).transpose()?;
211+
let cache = cache_path.map(Cache::new).transpose()?.map(Mutex::new);
211212
Ok(Compiler { compile, cache })
212213
}
213214

215+
/// the concurrency level of the exec is controlled by rayon parallelism
214216
fn exec(args: &[&str], stdin: &str) -> Result<String> {
215217
let mut child = Command::new("docker")
216218
.args(args)
@@ -242,7 +244,7 @@ impl Compiler {
242244
}
243245

244246
/// compiles ASM code
245-
pub fn asm(&mut self, src: &str) -> Result<Bytes> {
247+
pub fn asm(&self, src: &str) -> Result<Bytes> {
246248
let mut bytecode = Bytecode::default();
247249
for op in src.split(';') {
248250
let op = match bytecode::OpcodeWithData::from_str(op.trim()) {
@@ -256,9 +258,13 @@ impl Compiler {
256258
}
257259

258260
/// compiles LLL code
259-
pub fn lll(&mut self, src: &str) -> Result<Bytes> {
260-
if let Some(bytecode) = self.cache.as_mut().and_then(|c| c.get(src)) {
261-
return Ok(bytecode.clone());
261+
pub fn lll(&self, src: &str) -> Result<Bytes> {
262+
if let Some(bytecode) = self
263+
.cache
264+
.as_ref()
265+
.and_then(|c| c.lock().unwrap().get(src).cloned())
266+
{
267+
return Ok(bytecode);
262268
}
263269
if !self.compile {
264270
bail!("No way to compile LLLC for '{}'", src)
@@ -267,26 +273,30 @@ impl Compiler {
267273
let stdout = Self::exec(&["run", "-i", "--rm", "lllc"], src)?;
268274
let bytecode = Bytes::from(hex::decode(stdout.trim())?);
269275

270-
if let Some(cache) = &mut self.cache {
271-
cache.insert(src, bytecode.clone())?;
276+
if let Some(ref cache) = self.cache {
277+
cache.lock().unwrap().insert(src, bytecode.clone())?;
272278
}
273279

274280
Ok(bytecode)
275281
}
276282

277283
/// compiles YUL code
278-
pub fn yul(&mut self, src: &str) -> Result<Bytes> {
284+
pub fn yul(&self, src: &str) -> Result<Bytes> {
279285
self.solc(Language::Yul, src)
280286
}
281287

282288
/// compiles Solidity code
283-
pub fn solidity(&mut self, src: &str) -> Result<Bytes> {
289+
pub fn solidity(&self, src: &str) -> Result<Bytes> {
284290
self.solc(Language::Solidity, src)
285291
}
286292

287-
fn solc(&mut self, language: Language, src: &str) -> Result<Bytes> {
288-
if let Some(bytecode) = self.cache.as_mut().and_then(|c| c.get(src)) {
289-
return Ok(bytecode.clone());
293+
fn solc(&self, language: Language, src: &str) -> Result<Bytes> {
294+
if let Some(bytecode) = self
295+
.cache
296+
.as_ref()
297+
.and_then(|c| c.lock().unwrap().get(src).cloned())
298+
{
299+
return Ok(bytecode);
290300
}
291301
if !self.compile {
292302
bail!("No way to compile {:?} for '{}'", language, src)
@@ -312,8 +322,8 @@ impl Compiler {
312322

313323
let bytecode = Bytes::from(hex::decode(bytecode)?);
314324

315-
if let Some(cache) = &mut self.cache {
316-
cache.insert(src, bytecode.clone())?;
325+
if let Some(ref cache) = self.cache {
326+
cache.lock().unwrap().insert(src, bytecode.clone())?;
317327
}
318328

319329
Ok(bytecode)

testool/src/config.rs

+2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ const CONFIG_FILE: &str = "Config.toml";
77
pub struct Config {
88
pub suite: Vec<TestSuite>,
99
pub set: Vec<TestsSet>,
10+
#[serde(default)]
1011
pub skip_paths: Vec<SkipPaths>,
12+
#[serde(default)]
1113
pub skip_tests: Vec<SkipTests>,
1214
}
1315

testool/src/main.rs

+38-9
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,13 @@ struct Args {
4747
#[clap(long)]
4848
ls: bool,
4949

50-
/// Cache execution results
50+
/// Cache execution results, default to be latest result file
5151
#[clap(long)]
52-
cache: Option<String>,
52+
cache: Option<PathBuf>,
53+
54+
/// do not use cache
55+
#[clap(long)]
56+
use_cache: bool,
5357

5458
/// whitelist level from cache result
5559
#[clap(short, long, value_parser, value_delimiter = ',')]
@@ -155,30 +159,55 @@ fn go() -> Result<()> {
155159
REPORT_FOLDER, args.suite, timestamp, git_hash
156160
);
157161

162+
let cache_file_name = if !args.use_cache {
163+
None
164+
} else {
165+
let mut history_reports =
166+
glob::glob(format!("{REPORT_FOLDER}/{}.*.*.csv", args.suite).as_str())?
167+
.collect::<Result<Vec<PathBuf>, glob::GlobError>>()?
168+
.into_iter()
169+
.map(|path| {
170+
path.metadata()
171+
.and_then(|meta| meta.created())
172+
.map(|created| (path, created))
173+
})
174+
.collect::<Result<Vec<(PathBuf, SystemTime)>, std::io::Error>>()?;
175+
// sort by timestamp
176+
history_reports.sort_by_key(|(_, created)| *created);
177+
// use latest cache if exists
178+
args.cache
179+
.or_else(|| history_reports.pop().map(|(path, _)| path))
180+
};
181+
158182
// when running a report, the tests result of the containing cache file
159183
// are used, but by default removing all Ignored tests
160184
// Another way is to skip the test which level not in whitelist_levels
161-
let mut previous_results = if let Some(cache_filename) = args.cache {
185+
let mut previous_results = if let Some(cache_filename) = cache_file_name {
162186
let whitelist_levels = HashSet::<ResultLevel>::from_iter(args.levels);
163187

164-
let mut previous_results = Results::from_file(PathBuf::from(cache_filename))?;
188+
let mut previous_results = Results::from_file(cache_filename).unwrap_or_else(|_| {
189+
log::warn!("malformed cache file, won't use cache");
190+
Results::default()
191+
});
165192
if !whitelist_levels.is_empty() {
166193
// if whitelist is provided, test not in whitelist will be skip
167194
previous_results
168195
.tests
169196
.retain(|_, test| !whitelist_levels.contains(&test.level));
170197
} else {
171-
// by default only skip ignore
172-
previous_results
173-
.tests
174-
.retain(|_, test| test.level != ResultLevel::Ignored);
198+
// by default skip ignore and success tests
199+
previous_results.tests.retain(|_, test| {
200+
test.level == ResultLevel::Ignored || test.level == ResultLevel::Success
201+
});
175202
}
176203

177204
previous_results
178205
} else {
179206
Results::default()
180207
};
208+
181209
previous_results.set_cache(PathBuf::from(csv_filename));
210+
previous_results.write_cache()?;
182211
run_statetests_suite(state_tests, &circuits_config, &suite, &mut previous_results)?;
183212

184213
// filter non-csv files and files from the same commit
@@ -209,7 +238,7 @@ fn go() -> Result<()> {
209238
info!("{}", html_filename);
210239
} else {
211240
let mut results = if let Some(cache_filename) = args.cache {
212-
Results::with_cache(PathBuf::from(cache_filename))?
241+
Results::with_cache(cache_filename)?
213242
} else {
214243
Results::default()
215244
};

testool/src/statetest/executor.rs

+5-18
Original file line numberDiff line numberDiff line change
@@ -148,20 +148,7 @@ fn into_traceconfig(st: StateTest) -> (String, TraceConfig, StateTestResult) {
148148
let sig = wallet.sign_transaction_sync(&tx);
149149
let rlp_signed = tx.rlp_signed(&sig).to_vec();
150150
let tx_hash = keccak256(tx.rlp_signed(&sig));
151-
let mut accounts = st.pre;
152-
for i in 1..=9 {
153-
let mut addr_bytes = [0u8; 20];
154-
addr_bytes[19] = i as u8;
155-
let address = Address::from(addr_bytes);
156-
accounts
157-
.entry(address)
158-
.or_insert(eth_types::geth_types::Account {
159-
// balance: 1.into(),
160-
// nonce: 1.into(),
161-
address,
162-
..Default::default()
163-
});
164-
}
151+
let accounts = st.pre;
165152

166153
(
167154
st.id,
@@ -322,14 +309,14 @@ pub fn run_test(
322309
if !circuits_config.super_circuit {
323310
let circuits_params = CircuitsParams {
324311
max_txs: 1,
325-
max_rws: 0,
326-
max_calldata: 5000,
312+
max_rws: 0, // dynamic
313+
max_calldata: 0, // dynamic
327314
max_bytecode: 5000,
328315
max_mpt_rows: 5000,
329316
max_copy_rows: 55000,
330-
max_evm_rows: 0,
317+
max_evm_rows: 0, // dynamic
331318
max_exp_steps: 5000,
332-
max_keccak_rows: 0,
319+
max_keccak_rows: 0, // dynamic?
333320
max_inner_blocks: 64,
334321
max_rlp_rows: 6000,
335322
max_ec_ops: PrecompileEcParams {

testool/src/statetest/json.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,11 @@ impl Refs {
9898
}
9999

100100
pub struct JsonStateTestBuilder<'a> {
101-
compiler: &'a mut Compiler,
101+
compiler: &'a Compiler,
102102
}
103103

104104
impl<'a> JsonStateTestBuilder<'a> {
105-
pub fn new(compiler: &'a mut Compiler) -> Self {
105+
pub fn new(compiler: &'a Compiler) -> Self {
106106
Self { compiler }
107107
}
108108

@@ -379,8 +379,8 @@ mod test {
379379
"#;
380380
#[test]
381381
fn test_json_parse() -> Result<()> {
382-
let mut compiler = Compiler::new(true, None)?;
383-
let mut builder = JsonStateTestBuilder::new(&mut compiler);
382+
let compiler = Compiler::new(true, None)?;
383+
let mut builder = JsonStateTestBuilder::new(&compiler);
384384
let test = builder.load_json("test_path", JSON)?.remove(0);
385385

386386
let acc095e = Address::from_str("0x095e7baea6a6c7c4c2dfeb977efac326af552d87")?;

testool/src/statetest/parse.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ fn decompose_tags(expr: &str) -> HashMap<String, String> {
6161

6262
/// returns the element as calldata bytes, supports 0x, :raw, :abi, :yul and
6363
/// { LLL }
64-
pub fn parse_calldata(compiler: &mut Compiler, as_str: &str) -> Result<(Bytes, Option<Label>)> {
64+
pub fn parse_calldata(compiler: &Compiler, as_str: &str) -> Result<(Bytes, Option<Label>)> {
6565
let tags = decompose_tags(as_str);
6666
let label = tags.get(":label").cloned();
6767

@@ -96,7 +96,7 @@ pub fn parse_calldata(compiler: &mut Compiler, as_str: &str) -> Result<(Bytes, O
9696
}
9797

9898
/// parse entry as code, can be 0x, :raw or { LLL }
99-
pub fn parse_code(compiler: &mut Compiler, as_str: &str) -> Result<Bytes> {
99+
pub fn parse_code(compiler: &Compiler, as_str: &str) -> Result<Bytes> {
100100
let tags = decompose_tags(as_str);
101101

102102
let code = if let Some(notag) = tags.get("") {

0 commit comments

Comments
 (0)