|
1 | 1 | use crate::{ |
2 | 2 | abi::HEVMCalls, |
3 | | - executor::inspector::{cheatcodes::util, Cheatcodes}, |
| 3 | + executor::inspector::{ |
| 4 | + cheatcodes::util::{self}, |
| 5 | + Cheatcodes, |
| 6 | + }, |
4 | 7 | }; |
5 | 8 | use bytes::Bytes; |
6 | 9 | use ethers::{ |
7 | 10 | abi::{self, AbiEncode, ParamType, Token}, |
8 | 11 | prelude::{artifacts::CompactContractBytecode, ProjectPathsConfig}, |
9 | | - types::{Address, I256, U256}, |
| 12 | + types::*, |
10 | 13 | utils::hex::FromHex, |
11 | 14 | }; |
12 | 15 | use foundry_common::fs; |
| 16 | +use jsonpath_rust::JsonPathFinder; |
13 | 17 | use serde::Deserialize; |
| 18 | +use serde_json::Value; |
14 | 19 | use std::{ |
15 | 20 | env, |
16 | 21 | io::{BufRead, BufReader, Write}, |
@@ -127,15 +132,7 @@ fn set_env(key: &str, val: &str) -> Result<Bytes, Bytes> { |
127 | 132 | Ok(Bytes::new()) |
128 | 133 | } |
129 | 134 | } |
130 | | - |
131 | | -fn get_env(key: &str, r#type: ParamType, delim: Option<&str>) -> Result<Bytes, Bytes> { |
132 | | - let val = env::var(key).map_err::<Bytes, _>(|e| e.to_string().encode().into())?; |
133 | | - let val = if let Some(d) = delim { |
134 | | - val.split(d).map(|v| v.trim()).collect() |
135 | | - } else { |
136 | | - vec![val.as_str()] |
137 | | - }; |
138 | | - |
| 135 | +fn value_to_abi(val: Vec<String>, r#type: ParamType, is_array: bool) -> Result<Bytes, Bytes> { |
139 | 136 | let parse_bool = |v: &str| v.to_lowercase().parse::<bool>(); |
140 | 137 | let parse_uint = |v: &str| { |
141 | 138 | if v.starts_with("0x") { |
@@ -172,15 +169,26 @@ fn get_env(key: &str, r#type: ParamType, delim: Option<&str>) -> Result<Bytes, B |
172 | 169 | }) |
173 | 170 | .collect::<Result<Vec<Token>, String>>() |
174 | 171 | .map(|mut tokens| { |
175 | | - if delim.is_none() { |
176 | | - abi::encode(&[tokens.remove(0)]).into() |
177 | | - } else { |
| 172 | + if is_array { |
178 | 173 | abi::encode(&[Token::Array(tokens)]).into() |
| 174 | + } else { |
| 175 | + abi::encode(&[tokens.remove(0)]).into() |
179 | 176 | } |
180 | 177 | }) |
181 | 178 | .map_err(|e| e.into()) |
182 | 179 | } |
183 | 180 |
|
| 181 | +fn get_env(key: &str, r#type: ParamType, delim: Option<&str>) -> Result<Bytes, Bytes> { |
| 182 | + let val = env::var(key).map_err::<Bytes, _>(|e| e.to_string().encode().into())?; |
| 183 | + let val = if let Some(d) = delim { |
| 184 | + val.split(d).map(|v| v.trim().to_string()).collect() |
| 185 | + } else { |
| 186 | + vec![val] |
| 187 | + }; |
| 188 | + let is_array: bool = delim.is_some(); |
| 189 | + value_to_abi(val, r#type, is_array) |
| 190 | +} |
| 191 | + |
184 | 192 | fn full_path(state: &Cheatcodes, path: impl AsRef<Path>) -> PathBuf { |
185 | 193 | state.config.root.join(path) |
186 | 194 | } |
@@ -262,6 +270,60 @@ fn remove_file(state: &mut Cheatcodes, path: impl AsRef<Path>) -> Result<Bytes, |
262 | 270 | Ok(Bytes::new()) |
263 | 271 | } |
264 | 272 |
|
| 273 | +/// Converts a serde_json::Value to an abi::Token |
| 274 | +/// The function is designed to run recursively, so that in case of an object |
| 275 | +/// it will call itself to convert each of it's value and encode the whole as a |
| 276 | +/// Tuple |
| 277 | +fn value_to_token(value: &Value) -> Result<Token, Token> { |
| 278 | + if value.is_boolean() { |
| 279 | + Ok(Token::Bool(value.as_bool().unwrap())) |
| 280 | + } else if value.is_string() { |
| 281 | + let val = value.as_str().unwrap(); |
| 282 | + // If it can decoded as an address, it's an address |
| 283 | + if let Ok(addr) = H160::from_str(val) { |
| 284 | + Ok(Token::Address(addr)) |
| 285 | + } else { |
| 286 | + Ok(Token::String(val.to_owned())) |
| 287 | + } |
| 288 | + } else if value.is_u64() { |
| 289 | + Ok(Token::Uint(value.as_u64().unwrap().into())) |
| 290 | + } else if value.is_i64() { |
| 291 | + Ok(Token::Int(value.as_i64().unwrap().into())) |
| 292 | + } else if value.is_array() { |
| 293 | + let arr = value.as_array().unwrap(); |
| 294 | + Ok(Token::Array(arr.iter().map(|val| value_to_token(val).unwrap()).collect::<Vec<Token>>())) |
| 295 | + } else if value.is_object() { |
| 296 | + let values = value |
| 297 | + .as_object() |
| 298 | + .unwrap() |
| 299 | + .values() |
| 300 | + .map(|val| value_to_token(val).unwrap()) |
| 301 | + .collect::<Vec<Token>>(); |
| 302 | + Ok(Token::Tuple(values)) |
| 303 | + } else { |
| 304 | + Err(Token::String("Could not decode field".to_string())) |
| 305 | + } |
| 306 | +} |
| 307 | +/// Parses a JSON and returns a single value, an array or an entire JSON object encoded as tuple. |
| 308 | +/// As the JSON object is parsed serially, with the keys ordered alphabetically, they must be |
| 309 | +/// deserialized in the same order. That means that the solidity `struct` should order it's fields |
| 310 | +/// alphabetically and not by efficient packing or some other taxonomy. |
| 311 | +fn parse_json(_state: &mut Cheatcodes, json: &str, key: &str) -> Result<Bytes, Bytes> { |
| 312 | + let values: Value = JsonPathFinder::from_str(json, key)?.find(); |
| 313 | + // values is an array of items. Depending on the JsonPath key, they |
| 314 | + // can be many or a single item. An item can be a single value or |
| 315 | + // an entire JSON object. |
| 316 | + let res = values |
| 317 | + .as_array() |
| 318 | + .ok_or_else(|| util::encode_error("JsonPath did not return an array"))? |
| 319 | + .iter() |
| 320 | + .map(|inner| value_to_token(inner).map_err(util::encode_error)) |
| 321 | + .collect::<Result<Vec<Token>, Bytes>>(); |
| 322 | + // encode the bytes as the 'bytes' solidity type |
| 323 | + let abi_encoded = abi::encode(&[Token::Bytes(abi::encode(&res?))]); |
| 324 | + Ok(abi_encoded.into()) |
| 325 | +} |
| 326 | + |
265 | 327 | pub fn apply( |
266 | 328 | state: &mut Cheatcodes, |
267 | 329 | ffi_enabled: bool, |
@@ -299,6 +361,10 @@ pub fn apply( |
299 | 361 | HEVMCalls::WriteLine(inner) => write_line(state, &inner.0, &inner.1), |
300 | 362 | HEVMCalls::CloseFile(inner) => close_file(state, &inner.0), |
301 | 363 | HEVMCalls::RemoveFile(inner) => remove_file(state, &inner.0), |
| 364 | + // If no key argument is passed, return the whole JSON object. |
| 365 | + // "$" is the JSONPath key for the root of the object |
| 366 | + HEVMCalls::ParseJson0(inner) => parse_json(state, &inner.0, "$"), |
| 367 | + HEVMCalls::ParseJson1(inner) => parse_json(state, &inner.0, &inner.1), |
302 | 368 | _ => return None, |
303 | 369 | }) |
304 | 370 | } |
|
0 commit comments