From 3e971bff7be798225ef37d3d382c520dbe476e00 Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Wed, 13 Dec 2023 12:16:14 +0100 Subject: [PATCH 1/6] cache compiled files --- crates/common/src/zk_compile.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/common/src/zk_compile.rs b/crates/common/src/zk_compile.rs index 611246c1b..ba5ee43a8 100644 --- a/crates/common/src/zk_compile.rs +++ b/crates/common/src/zk_compile.rs @@ -385,8 +385,8 @@ impl ZkSolc { Ok(result) } - /// Checks if the contract has already been compiled for the given input contract hash. - /// If yes, returns the pre-compiled data. + /// Checks if the contract has already been compiled, and if yes then returns the compiled data. + /// The contents will not be persisted again. fn check_cache( &self, artifact_paths: &ZkSolcArtifactPaths, From 37f2443cbf2c55a543bf2ca98f4a1408c455d02e Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Wed, 13 Dec 2023 12:19:35 +0100 Subject: [PATCH 2/6] doc --- crates/common/src/zk_compile.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/common/src/zk_compile.rs b/crates/common/src/zk_compile.rs index ba5ee43a8..2393ce0ba 100644 --- a/crates/common/src/zk_compile.rs +++ b/crates/common/src/zk_compile.rs @@ -385,8 +385,8 @@ impl ZkSolc { Ok(result) } - /// Checks if the contract has already been compiled, and if yes then returns the compiled data. - /// The contents will not be persisted again. + /// Checks if the contract has already been compiled for the given input contract hash. + /// If yes, returns the pre-compiled data. fn check_cache( &self, artifact_paths: &ZkSolcArtifactPaths, From 0fb4a513decd3f1a53fdb0af57e0069a65216dc7 Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Wed, 13 Dec 2023 12:21:19 +0100 Subject: [PATCH 3/6] fmt --- crates/common/src/zk_compile.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/common/src/zk_compile.rs b/crates/common/src/zk_compile.rs index 2393ce0ba..611246c1b 100644 --- a/crates/common/src/zk_compile.rs +++ b/crates/common/src/zk_compile.rs @@ -385,7 +385,7 @@ impl ZkSolc { Ok(result) } - /// Checks if the contract has already been compiled for the given input contract hash. + /// Checks if the contract has already been compiled for the given input contract hash. /// If yes, returns the pre-compiled data. fn check_cache( &self, From d215e8b71aad6c82a144d666efa14517461238fc Mon Sep 17 00:00:00 2001 From: aon <21188659+aon@users.noreply.github.com> Date: Wed, 13 Dec 2023 12:44:07 -0300 Subject: [PATCH 4/6] feat: merge cheatcodes interface with foundry --- .../era-cheatcodes/tests/src/fixtures/File/write_file.txt | 1 + .../tests/src/fixtures/Json/write_test.json | 8 ++++++++ 2 files changed, 9 insertions(+) create mode 100644 crates/era-cheatcodes/tests/src/fixtures/File/write_file.txt create mode 100644 crates/era-cheatcodes/tests/src/fixtures/Json/write_test.json diff --git a/crates/era-cheatcodes/tests/src/fixtures/File/write_file.txt b/crates/era-cheatcodes/tests/src/fixtures/File/write_file.txt new file mode 100644 index 000000000..f5150d691 --- /dev/null +++ b/crates/era-cheatcodes/tests/src/fixtures/File/write_file.txt @@ -0,0 +1 @@ +hello writable world \ No newline at end of file diff --git a/crates/era-cheatcodes/tests/src/fixtures/Json/write_test.json b/crates/era-cheatcodes/tests/src/fixtures/Json/write_test.json new file mode 100644 index 000000000..26286ffd6 --- /dev/null +++ b/crates/era-cheatcodes/tests/src/fixtures/Json/write_test.json @@ -0,0 +1,8 @@ +{ + "boolean": true, + "number": 342, + "object": { + "title": "finally json serialization" + }, + "b": "test" +} \ No newline at end of file From 9017b4bb4dded6dc1b04226e6c65821e61b85d2b Mon Sep 17 00:00:00 2001 From: aon <21188659+aon@users.noreply.github.com> Date: Thu, 14 Dec 2023 12:16:04 -0300 Subject: [PATCH 5/6] feat: migrate era-revm cheatcodes --- crates/era-cheatcodes/.gitignore | 2 + crates/era-cheatcodes/src/cheatcodes.rs | 157 +++++++++++++++++- .../tests/src/cheatcodes/Ffi.t.sol | 45 +++++ .../tests/src/cheatcodes/Fs.t.sol | 50 ++++++ .../tests/src/cheatcodes/ReadCallers.t.sol | 66 ++++++++ .../tests/src/cheatcodes/Serialize.t.sol | 23 +-- .../tests/src/cheatcodes/ToString.t.sol | 31 +--- .../tests/src/cheatcodes/TryFfi.t.sol | 51 ++++++ .../tests/src/cheatcodes/Utils.sol | 20 +++ .../tests/src/cheatcodes/WriteJson.t.sol | 91 ++++++++++ .../tests/src/fixtures/File/read.txt | 2 + .../tests/src/fixtures/Json/.gitkeep | 0 12 files changed, 494 insertions(+), 44 deletions(-) create mode 100644 crates/era-cheatcodes/tests/src/cheatcodes/Ffi.t.sol create mode 100644 crates/era-cheatcodes/tests/src/cheatcodes/Fs.t.sol create mode 100644 crates/era-cheatcodes/tests/src/cheatcodes/ReadCallers.t.sol create mode 100644 crates/era-cheatcodes/tests/src/cheatcodes/TryFfi.t.sol create mode 100644 crates/era-cheatcodes/tests/src/cheatcodes/Utils.sol create mode 100644 crates/era-cheatcodes/tests/src/cheatcodes/WriteJson.t.sol create mode 100644 crates/era-cheatcodes/tests/src/fixtures/File/read.txt create mode 100644 crates/era-cheatcodes/tests/src/fixtures/Json/.gitkeep diff --git a/crates/era-cheatcodes/.gitignore b/crates/era-cheatcodes/.gitignore index c9b090484..5b9efc3a6 100644 --- a/crates/era-cheatcodes/.gitignore +++ b/crates/era-cheatcodes/.gitignore @@ -1,6 +1,8 @@ /target /Cargo.lock tests/* +tests/src/fixtures/File/write_file.txt +tests/src/fixtures/Json/write_test.json !tests/src !tests/lib !tests/test.sh diff --git a/crates/era-cheatcodes/src/cheatcodes.rs b/crates/era-cheatcodes/src/cheatcodes.rs index 02b0934b5..310b2eb02 100644 --- a/crates/era-cheatcodes/src/cheatcodes.rs +++ b/crates/era-cheatcodes/src/cheatcodes.rs @@ -1,5 +1,5 @@ use crate::utils::{ToH160, ToH256}; -use alloy_sol_types::SolInterface; +use alloy_sol_types::{SolInterface, SolValue}; use era_test_node::{fork::ForkStorage, utils::bytecode_to_factory_dep}; use ethers::utils::to_checksum; use foundry_cheatcodes_spec::Vm; @@ -17,7 +17,7 @@ use multivm::{ }, }, }; -use std::{cell::RefMut, collections::HashMap, fmt::Debug}; +use std::{cell::RefMut, collections::HashMap, fmt::Debug, fs, process::Command}; use zksync_basic_types::{AccountTreeId, H160, H256, U256}; use zksync_state::{ReadStorage, StoragePtr, StorageView, WriteStorage}; use zksync_types::{ @@ -253,6 +253,34 @@ impl CheatcodeTracer { self.store_factory_dep(hash, code); self.write_storage(code_key, u256_to_h256(hash), &mut storage.borrow_mut()); } + ffi(ffiCall { commandInput: command_input }) => { + tracing::info!("👷 Running ffi: {command_input:?}"); + let Some(first_arg) = command_input.get(0) else { + tracing::error!("Failed to run ffi: no args"); + return + }; + // TODO: set directory to root + let Ok(output) = Command::new(first_arg).args(&command_input[1..]).output() else { + tracing::error!("Failed to run ffi"); + return + }; + + // The stdout might be encoded on valid hex, or it might just be a string, + // so we need to determine which it is to avoid improperly encoding later. + let Ok(trimmed_stdout) = String::from_utf8(output.stdout) else { + tracing::error!("Failed to parse ffi output"); + return + }; + let trimmed_stdout = trimmed_stdout.trim(); + let encoded_stdout = + if let Ok(hex) = hex::decode(trimmed_stdout.trim_start_matches("0x")) { + hex + } else { + trimmed_stdout.as_bytes().to_vec() + }; + + self.add_trimmed_return_data(&encoded_stdout); + } getNonce_0(getNonce_0Call { account }) => { tracing::info!("👷 Getting nonce for {account:?}"); let mut storage = storage.borrow_mut(); @@ -275,6 +303,52 @@ impl CheatcodeTracer { let value = storage.read_value(&key); self.return_data = Some(vec![h256_to_u256(value)]); } + readCallers(readCallersCall {}) => { + tracing::info!("👷 Reading callers"); + + let current_origin = { + let key = StorageKey::new( + AccountTreeId::new(zksync_types::SYSTEM_CONTEXT_ADDRESS), + zksync_types::SYSTEM_CONTEXT_TX_ORIGIN_POSITION, + ); + + storage.borrow_mut().read_value(&key) + }; + + let mut mode = CallerMode::None; + let mut new_caller = current_origin; + + if let Some(prank) = &self.permanent_actions.start_prank { + //TODO: vm.prank -> CallerMode::Prank + println!("PRANK"); + mode = CallerMode::RecurrentPrank; + new_caller = prank.sender.into(); + } + // TODO: vm.broadcast / vm.startBroadcast section + // else if let Some(broadcast) = broadcast { + // mode = if broadcast.single_call { + // CallerMode::Broadcast + // } else { + // CallerMode::RecurrentBroadcast + // }; + // new_caller = &broadcast.new_origin; + // new_origin = &broadcast.new_origin; + // } + + let caller_mode = (mode as u8).into(); + let message_sender = h256_to_u256(new_caller); + let tx_origin = h256_to_u256(current_origin); + + self.return_data = Some(vec![caller_mode, message_sender, tx_origin]); + } + readFile(readFileCall { path }) => { + tracing::info!("👷 Reading file in path {}", path); + let Ok(data) = fs::read(path) else { + tracing::error!("Failed to read file"); + return + }; + self.add_trimmed_return_data(&data); + } roll(rollCall { newHeight: new_height }) => { tracing::info!("👷 Setting block number to {}", new_height); let key = StorageKey::new( @@ -459,6 +533,42 @@ impl CheatcodeTracer { let int_value = value.to_string(); self.add_trimmed_return_data(int_value.as_bytes()); } + tryFfi(tryFfiCall { commandInput: command_input }) => { + tracing::info!("👷 Running try ffi: {command_input:?}"); + let Some(first_arg) = command_input.get(0) else { + tracing::error!("Failed to run ffi: no args"); + return + }; + // TODO: set directory to root + let Ok(output) = Command::new(first_arg).args(&command_input[1..]).output() else { + tracing::error!("Failed to run ffi"); + return + }; + + // The stdout might be encoded on valid hex, or it might just be a string, + // so we need to determine which it is to avoid improperly encoding later. + let Ok(trimmed_stdout) = String::from_utf8(output.stdout) else { + tracing::error!("Failed to parse ffi output"); + return + }; + let trimmed_stdout = trimmed_stdout.trim(); + let encoded_stdout = + if let Ok(hex) = hex::decode(trimmed_stdout.trim_start_matches("0x")) { + hex + } else { + trimmed_stdout.as_bytes().to_vec() + }; + + let ffi_result = FfiResult { + exitCode: output.status.code().unwrap_or(69), // Default from foundry + stdout: encoded_stdout, + stderr: output.stderr, + }; + let encoded_ffi_result: Vec = ffi_result.abi_encode(); + let return_data: Vec = + encoded_ffi_result.chunks(32).map(|b| b.into()).collect_vec(); + self.return_data = Some(return_data); + } warp(warpCall { newTimestamp: new_timestamp }) => { tracing::info!("👷 Setting block timestamp {}", new_timestamp); @@ -474,6 +584,49 @@ impl CheatcodeTracer { &mut storage, ); } + writeFile(writeFileCall { path, data }) => { + tracing::info!("👷 Writing data to file in path {}", path); + if fs::write(path, data).is_err() { + tracing::error!("Failed to write file"); + } + } + writeJson_0(writeJson_0Call { json, path }) => { + tracing::info!("👷 Writing json data to file in path {}", path); + let Ok(json) = serde_json::from_str::(&json) else { + tracing::error!("Failed to parse json"); + return + }; + let Ok(formatted_json) = serde_json::to_string_pretty(&json) else { + tracing::error!("Failed to format json"); + return + }; + if fs::write(path, formatted_json).is_err() { + tracing::error!("Failed to write file"); + } + } + writeJson_1(writeJson_1Call { json, path, valueKey: value_key }) => { + tracing::info!("👷 Writing json data to file in path {path} with key {value_key}"); + let Ok(file) = fs::read_to_string(&path) else { + tracing::error!("Failed to read file"); + return + }; + let Ok(mut file_json) = serde_json::from_str::(&file) else { + tracing::error!("Failed to parse json"); + return + }; + let Ok(json) = serde_json::from_str::(&json) else { + tracing::error!("Failed to parse json"); + return + }; + file_json[value_key] = json; + let Ok(formatted_json) = serde_json::to_string_pretty(&file_json) else { + tracing::error!("Failed to format json"); + return + }; + if fs::write(path, formatted_json).is_err() { + tracing::error!("Failed to write file"); + } + } _ => { tracing::error!("👷 Unrecognized cheatcode"); } diff --git a/crates/era-cheatcodes/tests/src/cheatcodes/Ffi.t.sol b/crates/era-cheatcodes/tests/src/cheatcodes/Ffi.t.sol new file mode 100644 index 000000000..a70ba44e8 --- /dev/null +++ b/crates/era-cheatcodes/tests/src/cheatcodes/Ffi.t.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console2 as console} from "../../lib/forge-std/src/Test.sol"; +import {Constants} from "./Constants.sol"; +import {Utils} from "./Utils.sol"; + +contract FfiTest is Test { + function testFfi() public { + string[] memory inputs = new string[](3); + inputs[0] = "bash"; + inputs[1] = "-c"; + inputs[ + 2 + ] = "echo -n 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000966666920776f726b730000000000000000000000000000000000000000000000"; + + (bool success, bytes memory rawData) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("ffi(string[])", inputs) + ); + require(success, "ffi failed"); + + bytes memory data = Utils.trimReturnBytes(rawData); + string memory output = abi.decode(data, (string)); + require( + keccak256(bytes(output)) == keccak256(bytes("ffi works")), + "ffi failed" + ); + + console.log("failed?", failed()); + } + + function testFfiString() public { + string[] memory inputs = new string[](3); + inputs[0] = "echo"; + inputs[1] = "-n"; + inputs[2] = "gm"; + + (bool success, bytes memory rawData) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("ffi(string[])", inputs) + ); + require(success, "ffi failed"); + bytes memory data = Utils.trimReturnBytes(rawData); + require(keccak256(data) == keccak256(bytes("gm")), "ffi failed"); + } +} diff --git a/crates/era-cheatcodes/tests/src/cheatcodes/Fs.t.sol b/crates/era-cheatcodes/tests/src/cheatcodes/Fs.t.sol new file mode 100644 index 000000000..5f6cb6b79 --- /dev/null +++ b/crates/era-cheatcodes/tests/src/cheatcodes/Fs.t.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console2 as console} from "../../lib/forge-std/src/Test.sol"; +import {Constants} from "./Constants.sol"; +import {Utils} from "./Utils.sol"; + +contract FsTest is Test { + function testReadFile() public { + string memory path = "src/fixtures/File/read.txt"; + + (bool success, bytes memory rawData) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("readFile(string)", path) + ); + require(success, "readFile failed"); + + bytes memory data = Utils.trimReturnBytes(rawData); + + require( + keccak256(data) == + keccak256("hello readable world\nthis is the second line!\n"), + "read data did not match expected data" + ); + console.log("failed?", failed()); + } + + function testWriteFile() public { + string memory path = "src/fixtures/File/write_file.txt"; + string memory writeData = "hello writable world"; + + (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("writeFile(string,string)", path, writeData) + ); + require(success, "writeFile failed"); + + bytes memory readRawData; + (success, readRawData) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("readFile(string)", path) + ); + require(success, "readFile failed"); + + bytes memory readData = Utils.trimReturnBytes(readRawData); + + require( + keccak256(readData) == keccak256(bytes(writeData)), + "read data did not match write data" + ); + console.log("failed?", failed()); + } +} diff --git a/crates/era-cheatcodes/tests/src/cheatcodes/ReadCallers.t.sol b/crates/era-cheatcodes/tests/src/cheatcodes/ReadCallers.t.sol new file mode 100644 index 000000000..e63c72d9f --- /dev/null +++ b/crates/era-cheatcodes/tests/src/cheatcodes/ReadCallers.t.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console2 as console} from "../../lib/forge-std/src/Test.sol"; +import {Constants} from "./Constants.sol"; + +contract CheatcodeReadCallers is Test { + address constant TEST_ADDRESS = 0x6Eb28604685b1F182dAB800A1Bfa4BaFdBA8a79a; + address constant TEST_ORIGIN = 0xdEBe90b7BFD87Af696B1966082F6515a6E72F3d8; + + // enum CallerMode { + // None, + // Broadcast, + // RecurrentBroadcast, + // Prank, + // RecurrentPrank + // } + + function testNormalReadCallers() public { + (bool success, bytes memory data) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("readCallers()")); + require(success, "readCallers failed"); + + (uint8 mode, address sender, address origin) = abi.decode(data, (uint8, address, address)); + require(mode == 0, "normal call mode"); + require(sender == msg.sender, "sender not overridden"); + require(origin == tx.origin, "origin not overridden"); + } + + function testPrankedReadCallers() public { + (bool success1, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("startPrank(address)", TEST_ADDRESS) + ); + require(success1, "startPrank failed"); + + (bool success, bytes memory data) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("readCallers()")); + require(success, "readCallers failed"); + + (uint8 mode, address sender, address origin) = abi.decode(data, (uint8, address, address)); + require(mode == 4, "recurrent prank call mode"); + require(sender == TEST_ADDRESS, "sender overridden"); + require(origin == tx.origin, "origin not overridden"); + + console.log("failed?", failed()); + } + + function testFullyPrankedReadCallers() public { + (bool success1, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("startPrank(address,address)", TEST_ADDRESS, TEST_ORIGIN) + ); + require(success1, "startPrank failed"); + + (bool success, bytes memory data) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("readCallers()")); + require(success, "readCallers failed"); + + (uint8 mode, address sender, address origin) = abi.decode(data, (uint8, address, address)); + + require(mode == 4, "recurrent prank call mode"); + require(sender == TEST_ADDRESS, "sender overridden"); + require(origin == TEST_ORIGIN, "origin overridden"); + + console.log("failed?", failed()); + } +} diff --git a/crates/era-cheatcodes/tests/src/cheatcodes/Serialize.t.sol b/crates/era-cheatcodes/tests/src/cheatcodes/Serialize.t.sol index c2c95b1c8..e2109f41f 100644 --- a/crates/era-cheatcodes/tests/src/cheatcodes/Serialize.t.sol +++ b/crates/era-cheatcodes/tests/src/cheatcodes/Serialize.t.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.13; import {Test, console2 as console} from "../../lib/forge-std/src/Test.sol"; import {Constants} from "./Constants.sol"; +import {Utils} from "./Utils.sol"; contract CheatcodeSerializeTest is Test { address constant TEST_ADDRESS = 0x6Eb28604685b1F182dAB800A1Bfa4BaFdBA8a79a; @@ -17,7 +18,7 @@ contract CheatcodeSerializeTest is Test { ) ); require(success, "serializeAddress failed"); - bytes memory data = trimReturnBytes(rawData); + bytes memory data = Utils.trimReturnBytes(rawData); string memory testString = string(abi.encodePacked(data)); require( keccak256(bytes(testString)) == @@ -37,7 +38,7 @@ contract CheatcodeSerializeTest is Test { ) ); require(success, "serializeBool failed"); - bytes memory data = trimReturnBytes(rawData); + bytes memory data = Utils.trimReturnBytes(rawData); string memory testString = string(abi.encodePacked(data)); require( keccak256(bytes(testString)) == keccak256(bytes("true")), @@ -56,7 +57,7 @@ contract CheatcodeSerializeTest is Test { ) ); require(success, "serializeUint failed"); - bytes memory data = trimReturnBytes(rawData); + bytes memory data = Utils.trimReturnBytes(rawData); string memory testString = string(abi.encodePacked(data)); require( keccak256(bytes(testString)) == keccak256(bytes("99")), @@ -64,20 +65,4 @@ contract CheatcodeSerializeTest is Test { ); console.log("failed?", failed()); } - - function trimReturnBytes( - bytes memory rawData - ) internal pure returns (bytes memory) { - uint256 lengthStartingPos = rawData.length - 32; - bytes memory lengthSlice = new bytes(32); - for (uint256 i = 0; i < 32; i++) { - lengthSlice[i] = rawData[lengthStartingPos + i]; - } - uint256 length = abi.decode(lengthSlice, (uint256)); - bytes memory data = new bytes(length); - for (uint256 i = 0; i < length; i++) { - data[i] = rawData[i]; - } - return data; - } } diff --git a/crates/era-cheatcodes/tests/src/cheatcodes/ToString.t.sol b/crates/era-cheatcodes/tests/src/cheatcodes/ToString.t.sol index ff9305123..e5f78f2fc 100644 --- a/crates/era-cheatcodes/tests/src/cheatcodes/ToString.t.sol +++ b/crates/era-cheatcodes/tests/src/cheatcodes/ToString.t.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.13; import {Test, console2 as console} from "../../lib/forge-std/src/Test.sol"; import {Constants} from "./Constants.sol"; +import {Utils} from "./Utils.sol"; contract CheatcodeToStringTest is Test { function testToStringFromAddress() external { @@ -11,7 +12,7 @@ contract CheatcodeToStringTest is Test { abi.encodeWithSignature("toString(address)", testAddress) ); require(success, "toString failed"); - bytes memory data = trimReturnBytes(rawData); + bytes memory data = Utils.trimReturnBytes(rawData); string memory testString = string(abi.encodePacked(data)); require( keccak256(bytes(testString)) == @@ -26,7 +27,7 @@ contract CheatcodeToStringTest is Test { abi.encodeWithSignature("toString(bool)", false) ); require(success, "toString failed"); - bytes memory data = trimReturnBytes(rawData); + bytes memory data = Utils.trimReturnBytes(rawData); string memory testString = string(abi.encodePacked(data)); require( keccak256(bytes(testString)) == keccak256(bytes("false")), @@ -37,7 +38,7 @@ contract CheatcodeToStringTest is Test { abi.encodeWithSignature("toString(bool)", true) ); require(success, "toString failed"); - data = trimReturnBytes(rawData); + data = Utils.trimReturnBytes(rawData); testString = string(abi.encodePacked(data)); require( keccak256(bytes(testString)) == keccak256(bytes("true")), @@ -53,7 +54,7 @@ contract CheatcodeToStringTest is Test { abi.encodeWithSignature("toString(uint256)", value) ); require(success, "toString failed"); - bytes memory data = trimReturnBytes(rawData); + bytes memory data = Utils.trimReturnBytes(rawData); string memory testString = string(abi.encodePacked(data)); require( keccak256(bytes(testString)) == keccak256(bytes(stringValue)), @@ -69,7 +70,7 @@ contract CheatcodeToStringTest is Test { abi.encodeWithSignature("toString(int256)", value) ); require(success, "toString failed"); - bytes memory data = trimReturnBytes(rawData); + bytes memory data = Utils.trimReturnBytes(rawData); string memory testString = string(abi.encodePacked(data)); require( keccak256(bytes(testString)) == keccak256(bytes(stringValue)), @@ -84,7 +85,7 @@ contract CheatcodeToStringTest is Test { abi.encodeWithSignature("toString(bytes32)", testBytes) ); require(success, "toString failed"); - bytes memory data = trimReturnBytes(rawData); + bytes memory data = Utils.trimReturnBytes(rawData); string memory testString = string(abi.encodePacked(data)); require( keccak256(bytes(testString)) == @@ -105,7 +106,7 @@ contract CheatcodeToStringTest is Test { abi.encodeWithSignature("toString(bytes)", testBytes) ); require(success, "toString failed"); - bytes memory data = trimReturnBytes(rawData); + bytes memory data = Utils.trimReturnBytes(rawData); string memory testString = string(abi.encodePacked(data)); require( keccak256(bytes(testString)) == @@ -118,20 +119,4 @@ contract CheatcodeToStringTest is Test { ); console.log("failed?", failed()); } - - function trimReturnBytes( - bytes memory rawData - ) internal pure returns (bytes memory) { - uint256 lengthStartingPos = rawData.length - 32; - bytes memory lengthSlice = new bytes(32); - for (uint256 i = 0; i < 32; i++) { - lengthSlice[i] = rawData[lengthStartingPos + i]; - } - uint256 length = abi.decode(lengthSlice, (uint256)); - bytes memory data = new bytes(length); - for (uint256 i = 0; i < length; i++) { - data[i] = rawData[i]; - } - return data; - } } diff --git a/crates/era-cheatcodes/tests/src/cheatcodes/TryFfi.t.sol b/crates/era-cheatcodes/tests/src/cheatcodes/TryFfi.t.sol new file mode 100644 index 000000000..feb29769b --- /dev/null +++ b/crates/era-cheatcodes/tests/src/cheatcodes/TryFfi.t.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console2 as console} from "../../lib/forge-std/src/Test.sol"; +import {Constants} from "./Constants.sol"; +import {Utils} from "./Utils.sol"; + +contract FfiTest is Test { + struct FfiResult { + int32 exitCode; + bytes stdout; + bytes stderr; + } + + function testTryFfi() public { + string[] memory inputs = new string[](3); + inputs[0] = "bash"; + inputs[1] = "-c"; + inputs[ + 2 + ] = "echo -n 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000966666920776f726b730000000000000000000000000000000000000000000000"; + + (bool success, bytes memory data) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("tryFfi(string[])", inputs) + ); + require(success, "tryFfi failed"); + + FfiResult memory f = abi.decode(data, (FfiResult)); + string memory output = abi.decode(f.stdout, (string)); + + require( + keccak256(bytes(output)) == keccak256(bytes("ffi works")), + "ffi failed" + ); + require(f.exitCode == 0, "ffi failed"); + } + + function testTryFfiFail() public { + string[] memory inputs = new string[](2); + inputs[0] = "ls"; + inputs[1] = "wad"; + + (bool success, bytes memory data) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("tryFfi(string[])", inputs) + ); + require(success, "tryFfi failed"); + + FfiResult memory f = abi.decode(data, (FfiResult)); + require(f.exitCode != 0, "ffi failed"); + } +} diff --git a/crates/era-cheatcodes/tests/src/cheatcodes/Utils.sol b/crates/era-cheatcodes/tests/src/cheatcodes/Utils.sol new file mode 100644 index 000000000..a33c9152d --- /dev/null +++ b/crates/era-cheatcodes/tests/src/cheatcodes/Utils.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +library Utils { + function trimReturnBytes( + bytes memory rawData + ) internal pure returns (bytes memory) { + uint256 lengthStartingPos = rawData.length - 32; + bytes memory lengthSlice = new bytes(32); + for (uint256 i = 0; i < 32; i++) { + lengthSlice[i] = rawData[lengthStartingPos + i]; + } + uint256 length = abi.decode(lengthSlice, (uint256)); + bytes memory data = new bytes(length); + for (uint256 i = 0; i < length; i++) { + data[i] = rawData[i]; + } + return data; + } +} diff --git a/crates/era-cheatcodes/tests/src/cheatcodes/WriteJson.t.sol b/crates/era-cheatcodes/tests/src/cheatcodes/WriteJson.t.sol new file mode 100644 index 000000000..91f46c3f1 --- /dev/null +++ b/crates/era-cheatcodes/tests/src/cheatcodes/WriteJson.t.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console2 as console} from "../../lib/forge-std/src/Test.sol"; +import {Constants} from "./Constants.sol"; +import {Utils} from "./Utils.sol"; + +contract FsTest is Test { + function testWriteJson() public { + string + memory json = '{"boolean": true, "number": 342, "object": { "title": "finally json serialization" } }'; + string memory path = "src/fixtures/Json/write_test.json"; + + // Write json to file + (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("writeJson(string,string)", json, path) + ); + require(success, "writeJson failed"); + + bytes memory readRawData; + (success, readRawData) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("readFile(string)", path) + ); + require(success, "readFile failed"); + bytes memory readData = Utils.trimReturnBytes(readRawData); + + require( + keccak256(readData) == + keccak256( + bytes( + '{\n "boolean": true,\n "number": 342,\n "object": {\n "title": "finally json serialization"\n }\n}' + ) + ), + "read data did not match write data" + ); + + // Write json to key b + (success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "writeJson(string,string,string)", + json, + path, + "b" + ) + ); + require(success, "writeJson to key failed"); + + (success, readRawData) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("readFile(string)", path) + ); + require(success, "readFile failed"); + readData = Utils.trimReturnBytes(readRawData); + + require( + keccak256(readData) == + keccak256( + bytes( + '{\n "boolean": true,\n "number": 342,\n "object": {\n "title": "finally json serialization"\n },\n "b": {\n "boolean": true,\n "number": 342,\n "object": {\n "title": "finally json serialization"\n }\n }\n}' + ) + ), + "read data did not match write data" + ); + + // Replace the key b with single value + (success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "writeJson(string,string,string)", + '"test"', + path, + "b" + ) + ); + require(success, "writeJson to key failed"); + + (success, readRawData) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("readFile(string)", path) + ); + require(success, "readFile failed"); + readData = Utils.trimReturnBytes(readRawData); + + require( + keccak256(readData) == + keccak256( + bytes( + '{\n "boolean": true,\n "number": 342,\n "object": {\n "title": "finally json serialization"\n },\n "b": "test"\n}' + ) + ), + "read data did not match write data" + ); + } +} diff --git a/crates/era-cheatcodes/tests/src/fixtures/File/read.txt b/crates/era-cheatcodes/tests/src/fixtures/File/read.txt new file mode 100644 index 000000000..ea7ec8648 --- /dev/null +++ b/crates/era-cheatcodes/tests/src/fixtures/File/read.txt @@ -0,0 +1,2 @@ +hello readable world +this is the second line! diff --git a/crates/era-cheatcodes/tests/src/fixtures/Json/.gitkeep b/crates/era-cheatcodes/tests/src/fixtures/Json/.gitkeep new file mode 100644 index 000000000..e69de29bb From 537401620295e802fb14d331fd770110dda696b9 Mon Sep 17 00:00:00 2001 From: aon <21188659+aon@users.noreply.github.com> Date: Fri, 15 Dec 2023 10:06:11 -0300 Subject: [PATCH 6/6] fix: delete gitignored files --- .../era-cheatcodes/tests/src/fixtures/File/write_file.txt | 1 - .../tests/src/fixtures/Json/write_test.json | 8 -------- 2 files changed, 9 deletions(-) delete mode 100644 crates/era-cheatcodes/tests/src/fixtures/File/write_file.txt delete mode 100644 crates/era-cheatcodes/tests/src/fixtures/Json/write_test.json diff --git a/crates/era-cheatcodes/tests/src/fixtures/File/write_file.txt b/crates/era-cheatcodes/tests/src/fixtures/File/write_file.txt deleted file mode 100644 index f5150d691..000000000 --- a/crates/era-cheatcodes/tests/src/fixtures/File/write_file.txt +++ /dev/null @@ -1 +0,0 @@ -hello writable world \ No newline at end of file diff --git a/crates/era-cheatcodes/tests/src/fixtures/Json/write_test.json b/crates/era-cheatcodes/tests/src/fixtures/Json/write_test.json deleted file mode 100644 index 26286ffd6..000000000 --- a/crates/era-cheatcodes/tests/src/fixtures/Json/write_test.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "boolean": true, - "number": 342, - "object": { - "title": "finally json serialization" - }, - "b": "test" -} \ No newline at end of file