diff --git a/abi/abi/HEVM.sol b/abi/abi/HEVM.sol index 2c7094f5752ae..8dda26c9e44bf 100644 --- a/abi/abi/HEVM.sol +++ b/abi/abi/HEVM.sol @@ -180,6 +180,7 @@ writeJson(string, string) writeJson(string, string, string) parseJson(string)(bytes) parseJson(string, string)(bytes) +parseJsonKeys(string, string)(string[]) parseJsonUint(string, string)(uint256) parseJsonUintArray(string, string)(uint256[]) parseJsonInt(string, string)(int256) diff --git a/abi/src/bindings/hevm.rs b/abi/src/bindings/hevm.rs index 1a82d3bbfeddf..669b9488fb312 100644 --- a/abi/src/bindings/hevm.rs +++ b/abi/src/bindings/hevm.rs @@ -2900,6 +2900,39 @@ pub mod hevm { }, ], ), + ( + ::std::borrow::ToOwned::to_owned("parseJsonKeys"), + ::std::vec![ + ::ethers_core::abi::ethabi::Function { + name: ::std::borrow::ToOwned::to_owned("parseJsonKeys"), + inputs: ::std::vec![ + ::ethers_core::abi::ethabi::Param { + name: ::std::string::String::new(), + kind: ::ethers_core::abi::ethabi::ParamType::String, + internal_type: ::core::option::Option::None, + }, + ::ethers_core::abi::ethabi::Param { + name: ::std::string::String::new(), + kind: ::ethers_core::abi::ethabi::ParamType::String, + internal_type: ::core::option::Option::None, + }, + ], + outputs: ::std::vec![ + ::ethers_core::abi::ethabi::Param { + name: ::std::string::String::new(), + kind: ::ethers_core::abi::ethabi::ParamType::Array( + ::std::boxed::Box::new( + ::ethers_core::abi::ethabi::ParamType::String, + ), + ), + internal_type: ::core::option::Option::None, + }, + ], + constant: ::core::option::Option::None, + state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, + }, + ], + ), ( ::std::borrow::ToOwned::to_owned("parseJsonString"), ::std::vec![ @@ -6050,6 +6083,19 @@ pub mod hevm { .method_hash([153, 131, 194, 138], (p0, p1)) .expect("method not found (this should never happen)") } + ///Calls the contract's `parseJsonKeys` (0x213e4198) function + pub fn parse_json_keys( + &self, + p0: ::std::string::String, + p1: ::std::string::String, + ) -> ::ethers_contract::builders::ContractCall< + M, + ::std::vec::Vec<::std::string::String>, + > { + self.0 + .method_hash([33, 62, 65, 152], (p0, p1)) + .expect("method not found (this should never happen)") + } ///Calls the contract's `parseJsonString` (0x49c4fac8) function pub fn parse_json_string( &self, @@ -8533,6 +8579,19 @@ pub mod hevm { pub ::std::string::String, pub ::std::string::String, ); + ///Container type for all input parameters for the `parseJsonKeys` function with signature `parseJsonKeys(string,string)` and selector `0x213e4198` + #[derive( + Clone, + ::ethers_contract::EthCall, + ::ethers_contract::EthDisplay, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + #[ethcall(name = "parseJsonKeys", abi = "parseJsonKeys(string,string)")] + pub struct ParseJsonKeysCall(pub ::std::string::String, pub ::std::string::String); ///Container type for all input parameters for the `parseJsonString` function with signature `parseJsonString(string,string)` and selector `0x49c4fac8` #[derive( Clone, @@ -9822,6 +9881,7 @@ pub mod hevm { ParseJsonBytesArray(ParseJsonBytesArrayCall), ParseJsonInt(ParseJsonIntCall), ParseJsonIntArray(ParseJsonIntArrayCall), + ParseJsonKeys(ParseJsonKeysCall), ParseJsonString(ParseJsonStringCall), ParseJsonStringArray(ParseJsonStringArrayCall), ParseJsonUint(ParseJsonUintCall), @@ -10391,6 +10451,10 @@ pub mod hevm { ) { return Ok(Self::ParseJsonIntArray(decoded)); } + if let Ok(decoded) + = ::decode(data) { + return Ok(Self::ParseJsonKeys(decoded)); + } if let Ok(decoded) = ::decode(data) { return Ok(Self::ParseJsonString(decoded)); @@ -11021,6 +11085,9 @@ pub mod hevm { Self::ParseJsonIntArray(element) => { ::ethers_core::abi::AbiEncode::encode(element) } + Self::ParseJsonKeys(element) => { + ::ethers_core::abi::AbiEncode::encode(element) + } Self::ParseJsonString(element) => { ::ethers_core::abi::AbiEncode::encode(element) } @@ -11360,6 +11427,7 @@ pub mod hevm { } Self::ParseJsonInt(element) => ::core::fmt::Display::fmt(element, f), Self::ParseJsonIntArray(element) => ::core::fmt::Display::fmt(element, f), + Self::ParseJsonKeys(element) => ::core::fmt::Display::fmt(element, f), Self::ParseJsonString(element) => ::core::fmt::Display::fmt(element, f), Self::ParseJsonStringArray(element) => { ::core::fmt::Display::fmt(element, f) @@ -12024,6 +12092,11 @@ pub mod hevm { Self::ParseJsonIntArray(value) } } + impl ::core::convert::From for HEVMCalls { + fn from(value: ParseJsonKeysCall) -> Self { + Self::ParseJsonKeys(value) + } + } impl ::core::convert::From for HEVMCalls { fn from(value: ParseJsonStringCall) -> Self { Self::ParseJsonString(value) @@ -13238,6 +13311,18 @@ pub mod hevm { Hash )] pub struct ParseJsonIntArrayReturn(pub ::std::vec::Vec<::ethers_core::types::I256>); + ///Container type for all return fields from the `parseJsonKeys` function with signature `parseJsonKeys(string,string)` and selector `0x213e4198` + #[derive( + Clone, + ::ethers_contract::EthAbiType, + ::ethers_contract::EthAbiCodec, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + pub struct ParseJsonKeysReturn(pub ::std::vec::Vec<::std::string::String>); ///Container type for all return fields from the `parseJsonString` function with signature `parseJsonString(string,string)` and selector `0x49c4fac8` #[derive( Clone, diff --git a/evm/src/executor/inspector/cheatcodes/ext.rs b/evm/src/executor/inspector/cheatcodes/ext.rs index 24e228b39238c..862235fefe286 100644 --- a/evm/src/executor/inspector/cheatcodes/ext.rs +++ b/evm/src/executor/inspector/cheatcodes/ext.rs @@ -319,6 +319,36 @@ fn parse_json(json_str: &str, key: &str, coerce: Option) -> Result { } } +// returns JSON keys of given object as a string array +fn parse_json_keys(json_str: &str, key: &str) -> Result { + let json = serde_json::from_str(json_str)?; + let values = jsonpath_lib::select(&json, &canonicalize_json_key(key))?; + + // We need to check that values contains just one JSON-object and not an array of objects + ensure!( + values.len() == 1, + "You can only get keys for a single JSON-object. The key '{key}' returns a value or an array of JSON-objects", + ); + + let value = values[0]; + + ensure!( + value.is_object(), + "You can only get keys for JSON-object. The key '{key}' does not return an object", + ); + + let res = value + .as_object() + .ok_or(eyre::eyre!("Unexpected error while extracting JSON-object"))? + .keys() + .map(|key| Token::String(key.to_owned())) + .collect::>(); + + // encode the bytes as the 'bytes' solidity type + let abi_encoded = abi::encode(&[Token::Array(res)]); + Ok(abi_encoded.into()) +} + /// Serializes a key:value pair to a specific object. By calling this function multiple times, /// the user can serialize multiple KV pairs to the same object. The value can be of any type, even /// a new object in itself. The function will return @@ -523,6 +553,7 @@ pub fn apply(state: &mut Cheatcodes, call: &HEVMCalls) -> Option { HEVMCalls::ParseJson0(inner) => parse_json(&inner.0, "$", None), HEVMCalls::ParseJson1(inner) => parse_json(&inner.0, &inner.1, None), HEVMCalls::ParseJsonBool(inner) => parse_json(&inner.0, &inner.1, Some(ParamType::Bool)), + HEVMCalls::ParseJsonKeys(inner) => parse_json_keys(&inner.0, &inner.1), HEVMCalls::ParseJsonBoolArray(inner) => { parse_json(&inner.0, &inner.1, Some(ParamType::Bool)) } diff --git a/testdata/cheats/Json.t.sol b/testdata/cheats/Json.t.sol index e19bb37db573b..624e4c326674c 100644 --- a/testdata/cheats/Json.t.sol +++ b/testdata/cheats/Json.t.sol @@ -159,6 +159,27 @@ contract ParseJsonTest is DSTest { data = vm.parseJson("", "."); assertEq(0, data.length); } + + function test_parseJsonKeys() public { + string memory jsonString = + '{"some_key_to_value": "some_value", "some_key_to_array": [1, 2, 3], "some_key_to_object": {"key1": "value1", "key2": 2}}'; + string[] memory keys = vm.parseJsonKeys(jsonString, "$"); + assertEq(abi.encode(keys), abi.encode(["some_key_to_value", "some_key_to_array", "some_key_to_object"])); + + keys = vm.parseJsonKeys(jsonString, ".some_key_to_object"); + assertEq(abi.encode(keys), abi.encode(["key1", "key2"])); + + vm.expectRevert("You can only get keys for JSON-object. The key '.some_key_to_array' does not return an object"); + vm.parseJsonKeys(jsonString, ".some_key_to_array"); + + vm.expectRevert("You can only get keys for JSON-object. The key '.some_key_to_value' does not return an object"); + vm.parseJsonKeys(jsonString, ".some_key_to_value"); + + vm.expectRevert( + "You can only get keys for a single JSON-object. The key '.*' returns a value or an array of JSON-objects" + ); + vm.parseJsonKeys(jsonString, ".*"); + } } contract WriteJsonTest is DSTest { diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index 555d21fb00190..35bb56b459a41 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -525,6 +525,8 @@ interface Vm { function parseJson(string calldata) external returns (bytes memory); + function parseJsonKeys(string calldata, string calldata) external returns (string[] memory); + function parseJsonUint(string calldata, string calldata) external returns (uint256); function parseJsonUintArray(string calldata, string calldata) external returns (uint256[] memory);