Skip to content

Commit

Permalink
feat: implement parseTomlType cheats (#8911)
Browse files Browse the repository at this point in the history
* feat: implement `parseTomlType` cheats

* chore: `forge fmt`

* revert: use json naming to indicate to users that they are operating on json data

* chore: nit

* chore: nit
  • Loading branch information
leovct authored Sep 20, 2024
1 parent e3120d6 commit f2c14c1
Show file tree
Hide file tree
Showing 8 changed files with 244 additions and 16 deletions.
60 changes: 60 additions & 0 deletions crates/cheatcodes/assets/cheatcodes.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions crates/cheatcodes/spec/src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion crates/cheatcodes/src/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -654,7 +654,7 @@ fn serialize_json(
}

/// Resolves a [DynSolType] from user input.
fn resolve_type(type_description: &str) -> Result<DynSolType> {
pub(super) fn resolve_type(type_description: &str) -> Result<DynSolType> {
if let Ok(ty) = DynSolType::parse(type_description) {
return Ok(ty);
};
Expand Down
25 changes: 24 additions & 1 deletion crates/cheatcodes/src/toml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions testdata/cheats/Vm.sol

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

130 changes: 118 additions & 12 deletions testdata/default/cheats/Toml.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 {
Expand All @@ -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));
Expand All @@ -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);
}
Expand Down Expand Up @@ -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");

Expand All @@ -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");

Expand Down
23 changes: 23 additions & 0 deletions testdata/fixtures/Toml/nested_toml_struct.toml
Original file line number Diff line number Diff line change
@@ -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"
4 changes: 2 additions & 2 deletions testdata/fixtures/Toml/test.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit f2c14c1

Please sign in to comment.