Skip to content

Commit

Permalink
feat(cheatcodes): allow dot identity key for json (foundry-rs#5426)
Browse files Browse the repository at this point in the history
  • Loading branch information
Evalir authored Jul 18, 2023
1 parent 8e365be commit c426820
Show file tree
Hide file tree
Showing 9 changed files with 79 additions and 44 deletions.
2 changes: 1 addition & 1 deletion anvil/src/eth/miner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ impl MinerInner {
self.waker.wake();
}

fn register(&self, cx: &mut Context<'_>) {
fn register(&self, cx: &Context<'_>) {
self.waker.register(cx.waker());
}
}
Expand Down
2 changes: 1 addition & 1 deletion binder/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -790,7 +790,7 @@ pub fn fetch(
}

fn fetch_with_cli(
repo: &mut git2::Repository,
repo: &git2::Repository,
url: &str,
refspecs: &[String],
tags: bool,
Expand Down
91 changes: 58 additions & 33 deletions evm/src/executor/inspector/cheatcodes/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,51 +244,76 @@ fn canonicalize_json_key(key: &str) -> String {
}
}

/// Encodes a vector of [`Token`] into a vector of bytes.
fn encode_abi_values(values: Vec<Token>) -> Vec<u8> {
if values.len() == 1 {
abi::encode(&[Token::Bytes(abi::encode(&values))])
} else {
abi::encode(&[Token::Bytes(abi::encode(&[Token::Array(values)]))])
}
}

/// Parses a vector of [`Value`]s into a vector of [`Token`]s.
fn parse_json_values(values: Vec<&Value>, key: &str) -> Result<Vec<Token>> {
values
.iter()
.map(|inner| {
value_to_token(inner).map_err(|err| fmt_err!("Failed to parse key \"{key}\": {err}"))
})
.collect::<Result<Vec<Token>>>()
}

/// Parses a JSON and returns a single value, an array or an entire JSON object encoded as tuple.
/// As the JSON object is parsed serially, with the keys ordered alphabetically, they must be
/// deserialized in the same order. That means that the solidity `struct` should order it's fields
/// alphabetically and not by efficient packing or some other taxonomy.
fn parse_json(json_str: &str, key: &str, coerce: Option<ParamType>) -> Result {
let json = serde_json::from_str(json_str)?;
let values = jsonpath_lib::select(&json, &canonicalize_json_key(key))?;

// values is an array of items. Depending on the JsonPath key, they
// can be many or a single item. An item can be a single value or
// an entire JSON object.
if let Some(coercion_type) = coerce {
ensure!(
match key {
// Handle the special case of the root key. We want to return the entire JSON object
// in this case.
"." => {
let values = jsonpath_lib::select(&json, "$")?;
let res = parse_json_values(values, key)?;

// encode the bytes as the 'bytes' solidity type
let abi_encoded = encode_abi_values(res);
Ok(abi_encoded.into())
}
_ => {
let values = jsonpath_lib::select(&json, &canonicalize_json_key(key))?;

// values is an array of items. Depending on the JsonPath key, they
// can be many or a single item. An item can be a single value or
// an entire JSON object.
if let Some(coercion_type) = coerce {
ensure!(
values.iter().all(|value| !value.is_object()),
"You can only coerce values or arrays, not JSON objects. The key '{key}' returns an object",
);

ensure!(!values.is_empty(), "No matching value or array found for key {key}");
ensure!(!values.is_empty(), "No matching value or array found for key {key}");

let to_string = |v: &Value| {
let mut s = v.to_string();
s.retain(|c: char| c != '"');
s
};
return if let Some(array) = values[0].as_array() {
util::parse_array(array.iter().map(to_string), &coercion_type)
} else {
util::parse(&to_string(values[0]), &coercion_type)
}
}
let to_string = |v: &Value| {
let mut s = v.to_string();
s.retain(|c: char| c != '"');
s
};
trace!(target : "forge::evm", ?values, "parsign values");
return if let Some(array) = values[0].as_array() {
util::parse_array(array.iter().map(to_string), &coercion_type)
} else {
util::parse(&to_string(values[0]), &coercion_type)
}
}

let res = values
.iter()
.map(|inner| {
value_to_token(inner).map_err(|err| fmt_err!("Failed to parse key \"{key}\": {err}"))
})
.collect::<Result<Vec<Token>>>()?;
let res = parse_json_values(values, key)?;

// encode the bytes as the 'bytes' solidity type
let abi_encoded = if res.len() == 1 {
abi::encode(&[Token::Bytes(abi::encode(&res))])
} else {
abi::encode(&[Token::Bytes(abi::encode(&[Token::Array(res)]))])
};
Ok(abi_encoded.into())
// encode the bytes as the 'bytes' solidity type
let abi_encoded = encode_abi_values(res);
Ok(abi_encoded.into())
}
}
}

/// Serializes a key:value pair to a specific object. By calling this function multiple times,
Expand Down Expand Up @@ -361,7 +386,7 @@ fn array_eval_to_str<T: UIfmt>(array: &Vec<T>) -> String {
/// Write an object to a new file OR replace the value of an existing JSON file with the supplied
/// object.
fn write_json(
state: &mut Cheatcodes,
state: &Cheatcodes,
object: &str,
path: impl AsRef<Path>,
json_path_or_none: Option<&str>,
Expand Down
4 changes: 2 additions & 2 deletions evm/src/executor/inspector/cheatcodes/fork.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ fn create_select_fork<DB: DatabaseExt>(

/// Creates a new fork
fn create_fork<DB: DatabaseExt>(
state: &mut Cheatcodes,
state: &Cheatcodes,
data: &mut EVMData<'_, DB>,
url_or_alias: String,
block: Option<u64>,
Expand Down Expand Up @@ -218,7 +218,7 @@ fn create_select_fork_at_transaction<DB: DatabaseExt>(

/// Creates a new fork at the given transaction
fn create_fork_at_transaction<DB: DatabaseExt>(
state: &mut Cheatcodes,
state: &Cheatcodes,
data: &mut EVMData<'_, DB>,
url_or_alias: String,
transaction: H256,
Expand Down
2 changes: 1 addition & 1 deletion evm/src/executor/inspector/fuzzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ where

impl Fuzzer {
/// Collects `stack` and `memory` values into the fuzz dictionary.
fn collect_data(&mut self, interpreter: &mut Interpreter) {
fn collect_data(&mut self, interpreter: &Interpreter) {
let mut state = self.fuzz_state.write();

for slot in interpreter.stack().data() {
Expand Down
6 changes: 3 additions & 3 deletions evm/src/executor/inspector/tracer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ impl Tracer {
}
}

fn start_step<DB: Database>(&mut self, interp: &mut Interpreter, data: &mut EVMData<'_, DB>) {
fn start_step<DB: Database>(&mut self, interp: &Interpreter, data: &EVMData<'_, DB>) {
let trace_idx =
*self.trace_stack.last().expect("can't start step without starting a trace first");
let trace = &mut self.traces.arena[trace_idx];
Expand All @@ -118,8 +118,8 @@ impl Tracer {

fn fill_step<DB: Database>(
&mut self,
interp: &mut Interpreter,
data: &mut EVMData<'_, DB>,
interp: &Interpreter,
data: &EVMData<'_, DB>,
status: InstructionResult,
) {
let (trace_idx, step_idx) =
Expand Down
2 changes: 1 addition & 1 deletion fmt/src/comments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pub struct CommentWithMetadata {

impl PartialOrd for CommentWithMetadata {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.loc.partial_cmp(&other.loc)
Some(self.cmp(other))
}
}

Expand Down
10 changes: 10 additions & 0 deletions testdata/cheats/Json.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,16 @@ contract WriteJson is DSTest {
notSimpleJson memory decodedData = abi.decode(data, (notSimpleJson));
}

function test_retrieveEntireJson() public {
string memory path = "../testdata/fixtures/Json/write_complex_test.json";
string memory json = vm.readFile(path);
bytes memory data = vm.parseJson(json, ".");
notSimpleJson memory decodedData = abi.decode(data, (notSimpleJson));
console.log(decodedData.a);
assertEq(decodedData.a, 12345);
assertEq(true, false);
}

function test_writeJson() public {
string memory json3 = "json3";
string memory path = "../testdata/fixtures/Json/write_test.json";
Expand Down
4 changes: 2 additions & 2 deletions ui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -802,7 +802,7 @@ Spans::from(Span::styled("[t]: stack labels | [m]: memory decoding | [shift + j/
current_step: usize,
area: Rect,
stack_labels: bool,
draw_memory: &mut DrawMemory,
draw_memory: &DrawMemory,
) {
let stack = &debug_steps[current_step].stack;
let stack_space =
Expand Down Expand Up @@ -874,7 +874,7 @@ Spans::from(Span::styled("[t]: stack labels | [m]: memory decoding | [shift + j/
current_step: usize,
area: Rect,
mem_utf8: bool,
draw_mem: &mut DrawMemory,
draw_mem: &DrawMemory,
) {
let memory = &debug_steps[current_step].memory;
let stack_space = Block::default()
Expand Down

0 comments on commit c426820

Please sign in to comment.