Skip to content

Commit ee63905

Browse files
authored
chore(cheatcodes): clean up cheatcodes util.rs with new wallet and parsing (#5758)
* feat: add new wallet/parsing files with respective funcs * chore: dedupe * chore: re-use new cheatcodes * chore: move skip to ext * fmt * chore: parsing -> parse
1 parent 2f4a77e commit ee63905

File tree

5 files changed

+409
-353
lines changed

5 files changed

+409
-353
lines changed

crates/evm/src/executor/inspector/cheatcodes/ext.rs

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
use super::{bail, ensure, fmt_err, Cheatcodes, Result};
2-
use crate::{abi::HEVMCalls, executor::inspector::cheatcodes::util};
1+
use super::{bail, ensure, fmt_err, util::MAGIC_SKIP_BYTES, Cheatcodes, Error, Result};
2+
use crate::{abi::HEVMCalls, executor::inspector::cheatcodes::parse};
33
use ethers::{
44
abi::{self, AbiEncode, JsonAbi, ParamType, Token},
55
prelude::artifacts::CompactContractBytecode,
66
types::*,
77
};
88
use foundry_common::{fmt::*, fs, get_artifact_path};
99
use foundry_config::fs_permissions::FsAccessKind;
10+
use revm::{Database, EVMData};
1011
use serde::Deserialize;
1112
use serde_json::Value;
1213
use std::{collections::BTreeMap, env, path::Path, process::Command};
@@ -190,9 +191,9 @@ fn get_env(key: &str, ty: ParamType, delim: Option<&str>, default: Option<String
190191
})
191192
})?;
192193
if let Some(d) = delim {
193-
util::parse_array(val.split(d).map(str::trim), &ty)
194+
parse::parse_array(val.split(d).map(str::trim), &ty)
194195
} else {
195-
util::parse(&val, &ty)
196+
parse::parse(&val, &ty)
196197
}
197198
}
198199

@@ -342,9 +343,9 @@ fn parse_json(json_str: &str, key: &str, coerce: Option<ParamType>) -> Result {
342343
};
343344
trace!(target : "forge::evm", ?values, "parsign values");
344345
return if let Some(array) = values[0].as_array() {
345-
util::parse_array(array.iter().map(to_string), &coercion_type)
346+
parse::parse_array(array.iter().map(to_string), &coercion_type)
346347
} else {
347-
util::parse(&to_string(values[0]), &coercion_type)
348+
parse::parse(&to_string(values[0]), &coercion_type)
348349
}
349350
}
350351

@@ -482,7 +483,7 @@ fn key_exists(json_str: &str, key: &str) -> Result {
482483
let json: Value =
483484
serde_json::from_str(json_str).map_err(|e| format!("Could not convert to JSON: {e}"))?;
484485
let values = jsonpath_lib::select(&json, &canonicalize_json_key(key))?;
485-
let exists = util::parse(&(!values.is_empty()).to_string(), &ParamType::Bool)?;
486+
let exists = parse::parse(&(!values.is_empty()).to_string(), &ParamType::Bool)?;
486487
Ok(exists)
487488
}
488489

@@ -494,8 +495,28 @@ fn sleep(milliseconds: &U256) -> Result {
494495
Ok(Default::default())
495496
}
496497

498+
/// Skip the current test, by returning a magic value that will be checked by the test runner.
499+
pub fn skip(state: &mut Cheatcodes, depth: u64, skip: bool) -> Result {
500+
if !skip {
501+
return Ok(b"".into())
502+
}
503+
504+
// Skip should not work if called deeper than at test level.
505+
// As we're not returning the magic skip bytes, this will cause a test failure.
506+
if depth > 1 {
507+
return Err(Error::custom("The skip cheatcode can only be used at test level"))
508+
}
509+
510+
state.skip = true;
511+
Err(Error::custom_bytes(MAGIC_SKIP_BYTES))
512+
}
513+
497514
#[instrument(level = "error", name = "ext", target = "evm::cheatcodes", skip_all)]
498-
pub fn apply(state: &mut Cheatcodes, call: &HEVMCalls) -> Option<Result> {
515+
pub fn apply<DB: Database>(
516+
state: &mut Cheatcodes,
517+
data: &mut EVMData<'_, DB>,
518+
call: &HEVMCalls,
519+
) -> Option<Result> {
499520
Some(match call {
500521
HEVMCalls::Ffi(inner) => {
501522
if state.config.ffi {
@@ -680,6 +701,7 @@ pub fn apply(state: &mut Cheatcodes, call: &HEVMCalls) -> Option<Result> {
680701
HEVMCalls::WriteJson0(inner) => write_json(state, &inner.0, &inner.1, None),
681702
HEVMCalls::WriteJson1(inner) => write_json(state, &inner.0, &inner.1, Some(&inner.2)),
682703
HEVMCalls::KeyExists(inner) => key_exists(&inner.0, &inner.1),
704+
HEVMCalls::Skip(inner) => skip(state, data.journaled_state.depth(), inner.0),
683705
_ => return None,
684706
})
685707
}

crates/evm/src/executor/inspector/cheatcodes/mod.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,15 @@ mod fs;
5757
mod fuzz;
5858
/// Mapping related cheatcodes
5959
mod mapping;
60+
/// Parsing related cheatcodes.
61+
/// Does not include JSON-related cheatcodes to cut complexity.
62+
mod parse;
6063
/// Snapshot related cheatcodes
6164
mod snapshot;
62-
/// Utility cheatcodes (`sign` etc.)
65+
/// Utility functions and constants.
6366
pub mod util;
67+
/// Wallet / key management related cheatcodes
68+
mod wallet;
6469
pub use util::{BroadcastableTransaction, DEFAULT_CREATE2_DEPLOYER};
6570

6671
mod config;
@@ -219,10 +224,11 @@ impl Cheatcodes {
219224

220225
let opt = env::apply(self, data, caller, &decoded)
221226
.transpose()
222-
.or_else(|| util::apply(self, data, &decoded))
227+
.or_else(|| wallet::apply(self, data, &decoded))
228+
.or_else(|| parse::apply(self, data, &decoded))
223229
.or_else(|| expect::apply(self, data, &decoded))
224230
.or_else(|| fuzz::apply(&decoded))
225-
.or_else(|| ext::apply(self, &decoded))
231+
.or_else(|| ext::apply(self, data, &decoded))
226232
.or_else(|| fs::apply(self, &decoded))
227233
.or_else(|| snapshot::apply(data, &decoded))
228234
.or_else(|| fork::apply(self, data, &decoded));
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
use super::{fmt_err, Cheatcodes, Result};
2+
use crate::abi::HEVMCalls;
3+
use ethers::{
4+
abi::{ParamType, Token},
5+
prelude::*,
6+
};
7+
use foundry_macros::UIfmt;
8+
use revm::{Database, EVMData};
9+
10+
pub fn parse(s: &str, ty: &ParamType) -> Result {
11+
parse_token(s, ty)
12+
.map(|token| abi::encode(&[token]).into())
13+
.map_err(|e| fmt_err!("Failed to parse `{s}` as type `{ty}`: {e}"))
14+
}
15+
16+
pub fn parse_array<I, T>(values: I, ty: &ParamType) -> Result
17+
where
18+
I: IntoIterator<Item = T>,
19+
T: AsRef<str>,
20+
{
21+
let mut values = values.into_iter();
22+
match values.next() {
23+
Some(first) if !first.as_ref().is_empty() => {
24+
let tokens = std::iter::once(first)
25+
.chain(values)
26+
.map(|v| parse_token(v.as_ref(), ty))
27+
.collect::<Result<Vec<_>, _>>()?;
28+
Ok(abi::encode(&[Token::Array(tokens)]).into())
29+
}
30+
// return the empty encoded Bytes when values is empty or the first element is empty
31+
_ => Ok(abi::encode(&[Token::String(String::new())]).into()),
32+
}
33+
}
34+
35+
fn parse_token(s: &str, ty: &ParamType) -> Result<Token, String> {
36+
match ty {
37+
ParamType::Bool => {
38+
s.to_ascii_lowercase().parse().map(Token::Bool).map_err(|e| e.to_string())
39+
}
40+
ParamType::Uint(256) => parse_uint(s).map(Token::Uint),
41+
ParamType::Int(256) => parse_int(s).map(Token::Int),
42+
ParamType::Address => s.parse().map(Token::Address).map_err(|e| e.to_string()),
43+
ParamType::FixedBytes(32) => parse_bytes(s).map(Token::FixedBytes),
44+
ParamType::Bytes => parse_bytes(s).map(Token::Bytes),
45+
ParamType::String => Ok(Token::String(s.to_string())),
46+
_ => Err("unsupported type".into()),
47+
}
48+
}
49+
50+
fn parse_int(s: &str) -> Result<U256, String> {
51+
// hex string may start with "0x", "+0x", or "-0x" which needs to be stripped for
52+
// `I256::from_hex_str`
53+
if s.starts_with("0x") || s.starts_with("+0x") || s.starts_with("-0x") {
54+
s.replacen("0x", "", 1).parse::<I256>().map_err(|err| err.to_string())
55+
} else {
56+
match I256::from_dec_str(s) {
57+
Ok(val) => Ok(val),
58+
Err(dec_err) => s.parse::<I256>().map_err(|hex_err| {
59+
format!("could not parse value as decimal or hex: {dec_err}, {hex_err}")
60+
}),
61+
}
62+
}
63+
.map(|v| v.into_raw())
64+
}
65+
66+
fn parse_uint(s: &str) -> Result<U256, String> {
67+
if s.starts_with("0x") {
68+
s.parse::<U256>().map_err(|err| err.to_string())
69+
} else {
70+
match U256::from_dec_str(s) {
71+
Ok(val) => Ok(val),
72+
Err(dec_err) => s.parse::<U256>().map_err(|hex_err| {
73+
format!("could not parse value as decimal or hex: {dec_err}, {hex_err}")
74+
}),
75+
}
76+
}
77+
}
78+
79+
fn parse_bytes(s: &str) -> Result<Vec<u8>, String> {
80+
hex::decode(s).map_err(|e| e.to_string())
81+
}
82+
83+
#[instrument(level = "error", name = "util", target = "evm::cheatcodes", skip_all)]
84+
pub fn apply<DB: Database>(
85+
_state: &mut Cheatcodes,
86+
_data: &mut EVMData<'_, DB>,
87+
call: &HEVMCalls,
88+
) -> Option<Result> {
89+
Some(match call {
90+
HEVMCalls::ToString0(inner) => {
91+
Ok(ethers::abi::encode(&[Token::String(inner.0.pretty())]).into())
92+
}
93+
HEVMCalls::ToString1(inner) => {
94+
Ok(ethers::abi::encode(&[Token::String(inner.0.pretty())]).into())
95+
}
96+
HEVMCalls::ToString2(inner) => {
97+
Ok(ethers::abi::encode(&[Token::String(inner.0.pretty())]).into())
98+
}
99+
HEVMCalls::ToString3(inner) => {
100+
Ok(ethers::abi::encode(&[Token::String(inner.0.pretty())]).into())
101+
}
102+
HEVMCalls::ToString4(inner) => {
103+
Ok(ethers::abi::encode(&[Token::String(inner.0.pretty())]).into())
104+
}
105+
HEVMCalls::ToString5(inner) => {
106+
Ok(ethers::abi::encode(&[Token::String(inner.0.pretty())]).into())
107+
}
108+
HEVMCalls::ParseBytes(inner) => parse(&inner.0, &ParamType::Bytes),
109+
HEVMCalls::ParseAddress(inner) => parse(&inner.0, &ParamType::Address),
110+
HEVMCalls::ParseUint(inner) => parse(&inner.0, &ParamType::Uint(256)),
111+
HEVMCalls::ParseInt(inner) => parse(&inner.0, &ParamType::Int(256)),
112+
HEVMCalls::ParseBytes32(inner) => parse(&inner.0, &ParamType::FixedBytes(32)),
113+
HEVMCalls::ParseBool(inner) => parse(&inner.0, &ParamType::Bool),
114+
_ => return None,
115+
})
116+
}
117+
118+
#[cfg(test)]
119+
mod tests {
120+
use super::*;
121+
use ethers::abi::AbiDecode;
122+
123+
#[test]
124+
fn test_uint_env() {
125+
let pk = "0x10532cc9d0d992825c3f709c62c969748e317a549634fb2a9fa949326022e81f";
126+
let val: U256 = pk.parse().unwrap();
127+
let parsed = parse(pk, &ParamType::Uint(256)).unwrap();
128+
let decoded = U256::decode(&parsed).unwrap();
129+
assert_eq!(val, decoded);
130+
131+
let parsed = parse(pk.strip_prefix("0x").unwrap(), &ParamType::Uint(256)).unwrap();
132+
let decoded = U256::decode(&parsed).unwrap();
133+
assert_eq!(val, decoded);
134+
135+
let parsed = parse("1337", &ParamType::Uint(256)).unwrap();
136+
let decoded = U256::decode(&parsed).unwrap();
137+
assert_eq!(U256::from(1337u64), decoded);
138+
}
139+
140+
#[test]
141+
fn test_int_env() {
142+
let val = U256::from(100u64);
143+
let parsed = parse(&val.to_string(), &ParamType::Int(256)).unwrap();
144+
let decoded = I256::decode(parsed).unwrap();
145+
assert_eq!(val, decoded.try_into().unwrap());
146+
147+
let parsed = parse("100", &ParamType::Int(256)).unwrap();
148+
let decoded = I256::decode(parsed).unwrap();
149+
assert_eq!(U256::from(100u64), decoded.try_into().unwrap());
150+
}
151+
}

0 commit comments

Comments
 (0)