diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index 88c65137cda27..a883771f6c27b 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -6471,6 +6471,66 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "parseTomlTypeArray", + "description": "Parses a string of TOML data at `key` and coerces it to type array corresponding to `typeDescription`.", + "declaration": "function parseTomlTypeArray(string calldata toml, string calldata key, string calldata typeDescription) external pure returns (bytes memory);", + "visibility": "external", + "mutability": "pure", + "signature": "parseTomlTypeArray(string,string,string)", + "selector": "0x49be3743", + "selectorBytes": [ + 73, + 190, + 55, + 67 + ] + }, + "group": "toml", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseTomlType_0", + "description": "Parses a string of TOML data and coerces it to type corresponding to `typeDescription`.", + "declaration": "function parseTomlType(string calldata toml, string calldata typeDescription) external pure returns (bytes memory);", + "visibility": "external", + "mutability": "pure", + "signature": "parseTomlType(string,string)", + "selector": "0x47fa5e11", + "selectorBytes": [ + 71, + 250, + 94, + 17 + ] + }, + "group": "toml", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseTomlType_1", + "description": "Parses a string of TOML data at `key` and coerces it to type corresponding to `typeDescription`.", + "declaration": "function parseTomlType(string calldata toml, string calldata key, string calldata typeDescription) external pure returns (bytes memory);", + "visibility": "external", + "mutability": "pure", + "signature": "parseTomlType(string,string,string)", + "selector": "0xf9fa5cdb", + "selectorBytes": [ + 249, + 250, + 92, + 219 + ] + }, + "group": "toml", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "parseTomlUint", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index 9c19196c82095..40d9802dba6e7 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -2134,6 +2134,19 @@ interface Vm { pure returns (bytes32[] memory); + /// Parses a string of TOML data and coerces it to type corresponding to `typeDescription`. + #[cheatcode(group = Toml)] + function parseTomlType(string calldata toml, string calldata typeDescription) external pure returns (bytes memory); + /// Parses a string of TOML data at `key` and coerces it to type corresponding to `typeDescription`. + #[cheatcode(group = Toml)] + function parseTomlType(string calldata toml, string calldata key, string calldata typeDescription) external pure returns (bytes memory); + /// Parses a string of TOML data at `key` and coerces it to type array corresponding to `typeDescription`. + #[cheatcode(group = Toml)] + function parseTomlTypeArray(string calldata toml, string calldata key, string calldata typeDescription) + external + pure + returns (bytes memory); + /// Returns an array of all the keys in a TOML table. #[cheatcode(group = Toml)] function parseTomlKeys(string calldata toml, string calldata key) external pure returns (string[] memory keys); diff --git a/crates/cheatcodes/src/json.rs b/crates/cheatcodes/src/json.rs index dad879e83b2d1..ba32b4ebe6441 100644 --- a/crates/cheatcodes/src/json.rs +++ b/crates/cheatcodes/src/json.rs @@ -654,7 +654,7 @@ fn serialize_json( } /// Resolves a [DynSolType] from user input. -fn resolve_type(type_description: &str) -> Result { +pub(super) fn resolve_type(type_description: &str) -> Result { if let Ok(ty) = DynSolType::parse(type_description) { return Ok(ty); }; diff --git a/crates/cheatcodes/src/toml.rs b/crates/cheatcodes/src/toml.rs index e83a183905472..b55ef2d16eedf 100644 --- a/crates/cheatcodes/src/toml.rs +++ b/crates/cheatcodes/src/toml.rs @@ -3,12 +3,13 @@ use crate::{ json::{ canonicalize_json_path, check_json_key_exists, parse_json, parse_json_coerce, - parse_json_keys, + parse_json_keys, resolve_type, }, Cheatcode, Cheatcodes, Result, Vm::*, }; use alloy_dyn_abi::DynSolType; +use alloy_sol_types::SolValue; use foundry_common::fs; use foundry_config::fs_permissions::FsAccessKind; use serde_json::Value as JsonValue; @@ -133,6 +134,28 @@ impl Cheatcode for parseTomlBytes32ArrayCall { } } +impl Cheatcode for parseTomlType_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, typeDescription } = self; + parse_toml_coerce(toml, "$", &resolve_type(typeDescription)?).map(|v| v.abi_encode()) + } +} + +impl Cheatcode for parseTomlType_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key, typeDescription } = self; + parse_toml_coerce(toml, key, &resolve_type(typeDescription)?).map(|v| v.abi_encode()) + } +} + +impl Cheatcode for parseTomlTypeArrayCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key, typeDescription } = self; + let ty = resolve_type(typeDescription)?; + parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(ty))).map(|v| v.abi_encode()) + } +} + impl Cheatcode for parseTomlKeysCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index 4b7f3fe42f503..7316521ecb32f 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -319,6 +319,9 @@ interface Vm { function parseTomlKeys(string calldata toml, string calldata key) external pure returns (string[] memory keys); function parseTomlString(string calldata toml, string calldata key) external pure returns (string memory); function parseTomlStringArray(string calldata toml, string calldata key) external pure returns (string[] memory); + function parseTomlTypeArray(string calldata toml, string calldata key, string calldata typeDescription) external pure returns (bytes memory); + function parseTomlType(string calldata toml, string calldata typeDescription) external pure returns (bytes memory); + function parseTomlType(string calldata toml, string calldata key, string calldata typeDescription) external pure returns (bytes memory); function parseTomlUint(string calldata toml, string calldata key) external pure returns (uint256); function parseTomlUintArray(string calldata toml, string calldata key) external pure returns (uint256[] memory); function parseToml(string calldata toml) external pure returns (bytes memory abiEncodedData); diff --git a/testdata/default/cheats/Toml.t.sol b/testdata/default/cheats/Toml.t.sol index a01b29af62cb6..4b1666d4f28a8 100644 --- a/testdata/default/cheats/Toml.t.sol +++ b/testdata/default/cheats/Toml.t.sol @@ -5,7 +5,81 @@ import "ds-test/test.sol"; import "cheats/Vm.sol"; import "../logs/console.sol"; +library TomlStructs { + address constant HEVM_ADDRESS = address(bytes20(uint160(uint256(keccak256("hevm cheat code"))))); + Vm constant vm = Vm(HEVM_ADDRESS); + + // forge eip712 testdata/default/cheats/Toml.t.sol -R 'cheats=testdata/cheats' -R 'ds-test=testdata/lib/ds-test/src' | grep ^FlatToml + string constant schema_FlatToml = + "FlatToml(uint256 a,int24[][] arr,string str,bytes b,address addr,bytes32 fixedBytes)"; + + // forge eip712 testdata/default/cheats/Toml.t.sol -R 'cheats=testdata/cheats' -R 'ds-test=testdata/lib/ds-test/src' | grep ^NestedToml + string constant schema_NestedToml = + "NestedToml(FlatToml[] members,AnotherFlatToml inner,string name)AnotherFlatToml(bytes4 fixedBytes)FlatToml(uint256 a,int24[][] arr,string str,bytes b,address addr,bytes32 fixedBytes)"; + + function deserializeFlatToml(string memory toml) internal pure returns (ParseTomlTest.FlatToml memory) { + return abi.decode(vm.parseTomlType(toml, schema_FlatToml), (ParseTomlTest.FlatToml)); + } + + function deserializeFlatToml(string memory toml, string memory path) + internal + pure + returns (ParseTomlTest.FlatToml memory) + { + return abi.decode(vm.parseTomlType(toml, path, schema_FlatToml), (ParseTomlTest.FlatToml)); + } + + function deserializeFlatTomlArray(string memory toml, string memory path) + internal + pure + returns (ParseTomlTest.FlatToml[] memory) + { + return abi.decode(vm.parseTomlTypeArray(toml, path, schema_FlatToml), (ParseTomlTest.FlatToml[])); + } + + function deserializeNestedToml(string memory toml) internal pure returns (ParseTomlTest.NestedToml memory) { + return abi.decode(vm.parseTomlType(toml, schema_NestedToml), (ParseTomlTest.NestedToml)); + } + + function deserializeNestedToml(string memory toml, string memory path) + internal + pure + returns (ParseTomlTest.NestedToml memory) + { + return abi.decode(vm.parseTomlType(toml, path, schema_NestedToml), (ParseTomlTest.NestedToml)); + } + + function deserializeNestedTomlArray(string memory toml, string memory path) + internal + pure + returns (ParseTomlTest.NestedToml[] memory) + { + return abi.decode(vm.parseTomlType(toml, path, schema_NestedToml), (ParseTomlTest.NestedToml[])); + } +} + contract ParseTomlTest is DSTest { + using TomlStructs for *; + + struct FlatToml { + uint256 a; + int24[][] arr; + string str; + bytes b; + address addr; + bytes32 fixedBytes; + } + + struct AnotherFlatToml { + bytes4 fixedBytes; + } + + struct NestedToml { + FlatToml[] members; + AnotherFlatToml inner; + string name; + } + Vm constant vm = Vm(HEVM_ADDRESS); string toml; @@ -169,20 +243,20 @@ contract ParseTomlTest is DSTest { assertEq(bytesArray[1], hex"02"); } - struct Nested { + struct NestedStruct { uint256 number; string str; } function test_nestedObject() public { bytes memory data = vm.parseToml(toml, ".nestedObject"); - Nested memory nested = abi.decode(data, (Nested)); + NestedStruct memory nested = abi.decode(data, (NestedStruct)); assertEq(nested.number, 9223372036854775807); // TOML is limited to 64-bit integers assertEq(nested.str, "NEST"); } - function test_advancedJsonPath() public { - bytes memory data = vm.parseToml(toml, ".advancedJsonPath[*].id"); + function test_advancedTomlPath() public { + bytes memory data = vm.parseToml(toml, ".advancedTomlPath[*].id"); uint256[] memory numbers = abi.decode(data, (uint256[])); assertEq(numbers[0], 1); assertEq(numbers[1], 2); @@ -225,6 +299,36 @@ contract ParseTomlTest is DSTest { vm._expectCheatcodeRevert("key \".*\" must return exactly one JSON object"); vm.parseTomlKeys(tomlString, ".*"); } + + // forge eip712 testdata/default/cheats/Toml.t.sol -R 'cheats=testdata/cheats' -R 'ds-test=testdata/lib/ds-test/src' | grep ^FlatToml + string constant schema_FlatToml = + "FlatToml(uint256 a,int24[][] arr,string str,bytes b,address addr,bytes32 fixedBytes)"; + + // forge eip712 testdata/default/cheats/Toml.t.sol -R 'cheats=testdata/cheats' -R 'ds-test=testdata/lib/ds-test/src' | grep ^NestedToml + string constant schema_NestedToml = + "NestedToml(FlatToml[] members,AnotherFlatToml inner,string name)AnotherFlatToml(bytes4 fixedBytes)FlatToml(uint256 a,int24[][] arr,string str,bytes b,address addr,bytes32 fixedBytes)"; + + function test_parseTomlType() public { + string memory readToml = vm.readFile("fixtures/Toml/nested_toml_struct.toml"); + NestedToml memory data = readToml.deserializeNestedToml(); + assertEq(data.members.length, 2); + + FlatToml memory expected = FlatToml({ + a: 200, + arr: new int24[][](0), + str: "some other string", + b: hex"0000000000000000000000000000000000000000", + addr: 0x167D91deaEEE3021161502873d3bcc6291081648, + fixedBytes: 0xed1c7beb1f00feaaaec5636950d6edb25a8d4fedc8deb2711287b64c4d27719d + }); + + assertEq(keccak256(abi.encode(data.members[1])), keccak256(abi.encode(expected))); + assertEq(bytes32(data.inner.fixedBytes), bytes32(bytes4(0x12345678))); + + FlatToml[] memory members = TomlStructs.deserializeFlatTomlArray(readToml, ".members"); + + assertEq(keccak256(abi.encode(members)), keccak256(abi.encode(data.members))); + } } contract WriteTomlTest is DSTest { @@ -238,18 +342,18 @@ contract WriteTomlTest is DSTest { json2 = "example2"; } - struct simpleJson { + struct simpleStruct { uint256 a; string b; } - struct notSimpleJson { + struct nestedStruct { uint256 a; string b; - simpleJson c; + simpleStruct c; } - function test_serializeNotSimpleToml() public { + function test_serializeNestedStructToml() public { string memory json3 = "json3"; string memory path = "fixtures/Toml/write_complex_test.toml"; vm.serializeUint(json3, "a", uint256(123)); @@ -259,14 +363,16 @@ contract WriteTomlTest is DSTest { vm.writeToml(finalJson, path); string memory toml = vm.readFile(path); bytes memory data = vm.parseToml(toml); - notSimpleJson memory decodedData = abi.decode(data, (notSimpleJson)); + nestedStruct memory decodedData = abi.decode(data, (nestedStruct)); + console.log(decodedData.a); + assertEq(decodedData.a, 123); } function test_retrieveEntireToml() public { string memory path = "fixtures/Toml/write_complex_test.toml"; string memory toml = vm.readFile(path); bytes memory data = vm.parseToml(toml, "."); - notSimpleJson memory decodedData = abi.decode(data, (notSimpleJson)); + nestedStruct memory decodedData = abi.decode(data, (nestedStruct)); console.log(decodedData.a); assertEq(decodedData.a, 123); } @@ -294,7 +400,7 @@ contract WriteTomlTest is DSTest { string memory toml = vm.readFile(path); bytes memory data = vm.parseToml(toml); - simpleJson memory decodedData = abi.decode(data, (simpleJson)); + simpleStruct memory decodedData = abi.decode(data, (simpleStruct)); assertEq(decodedData.a, 123); assertEq(decodedData.b, "test"); @@ -303,7 +409,7 @@ contract WriteTomlTest is DSTest { // read again toml = vm.readFile(path); data = vm.parseToml(toml, ".b"); - decodedData = abi.decode(data, (simpleJson)); + decodedData = abi.decode(data, (simpleStruct)); assertEq(decodedData.a, 123); assertEq(decodedData.b, "test"); diff --git a/testdata/fixtures/Toml/nested_toml_struct.toml b/testdata/fixtures/Toml/nested_toml_struct.toml new file mode 100644 index 0000000000000..3cef0b7ba1045 --- /dev/null +++ b/testdata/fixtures/Toml/nested_toml_struct.toml @@ -0,0 +1,23 @@ +name = "test" + +[[members]] +a = 100 +arr = [ + [1, -2, -5], + [1000, 2000, 0] +] +str = "some string" +b = "0x" +addr = "0x0000000000000000000000000000000000000000" +fixedBytes = "0x8ae3fc6bd1b150a73ec4afe3ef136fa2f88e9c96131c883c5e4a4714811c1598" + +[[members]] +a = 200 +arr = [] +str = "some other string" +b = "0x0000000000000000000000000000000000000000" +addr = "0x167D91deaEEE3021161502873d3bcc6291081648" +fixedBytes = "0xed1c7beb1f00feaaaec5636950d6edb25a8d4fedc8deb2711287b64c4d27719d" + +[inner] +fixedBytes = "0x12345678" diff --git a/testdata/fixtures/Toml/test.toml b/testdata/fixtures/Toml/test.toml index ce735b8f18cf9..806dc2224de46 100644 --- a/testdata/fixtures/Toml/test.toml +++ b/testdata/fixtures/Toml/test.toml @@ -43,8 +43,8 @@ bytesStringArray = ["0x01", "0x02"] number = 9223372036854775807 # TOML is limited to 64-bit integers str = "NEST" -[[advancedJsonPath]] +[[advancedTomlPath]] id = 1 -[[advancedJsonPath]] +[[advancedTomlPath]] id = 2