Skip to content

Commit

Permalink
Merge branch 'main' into nish-move-smoke-tests
Browse files Browse the repository at this point in the history
  • Loading branch information
nbaztec authored Dec 15, 2023
2 parents 439f1b7 + 1f8590e commit 329ac6f
Show file tree
Hide file tree
Showing 12 changed files with 494 additions and 44 deletions.
2 changes: 2 additions & 0 deletions crates/era-cheatcodes/.gitignore
Original file line number Diff line number Diff line change
@@ -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
Expand Down
157 changes: 155 additions & 2 deletions crates/era-cheatcodes/src/cheatcodes.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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::{
Expand Down Expand Up @@ -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();
Expand All @@ -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(
Expand Down Expand Up @@ -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<u8> = ffi_result.abi_encode();
let return_data: Vec<U256> =
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);

Expand All @@ -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::<serde_json::Value>(&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::<serde_json::Value>(&file) else {
tracing::error!("Failed to parse json");
return
};
let Ok(json) = serde_json::from_str::<serde_json::Value>(&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");
}
Expand Down
45 changes: 45 additions & 0 deletions crates/era-cheatcodes/tests/src/cheatcodes/Ffi.t.sol
Original file line number Diff line number Diff line change
@@ -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");
}
}
50 changes: 50 additions & 0 deletions crates/era-cheatcodes/tests/src/cheatcodes/Fs.t.sol
Original file line number Diff line number Diff line change
@@ -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());
}
}
66 changes: 66 additions & 0 deletions crates/era-cheatcodes/tests/src/cheatcodes/ReadCallers.t.sol
Original file line number Diff line number Diff line change
@@ -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());
}
}
Loading

0 comments on commit 329ac6f

Please sign in to comment.