Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

REVM cheatcodes #841

Merged
merged 17 commits into from
Mar 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 3 additions & 26 deletions Cargo.lock

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

5 changes: 0 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,6 @@ codegen-units = 1
panic = "abort"
debug = true

## Patch sputnik with more recent primitive types
# https://github.com/rust-blockchain/evm/pulls
[patch."https://github.com/rust-blockchain/evm"]
evm = { git = "https://github.com/gakonst/evm", branch = "bump-primitive-types" }

## Patch ethers-rs with a local checkout
#[patch."https://github.com/gakonst/ethers-rs"]
#ethers = { path = "../ethers-rs" }
Expand Down
51 changes: 5 additions & 46 deletions cli/src/cmd/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@ use crate::{
};
use ansi_term::Colour;
use clap::{AppSettings, Parser};
use ethers::{
abi::RawLog,
contract::EthLogDecode,
solc::{ArtifactOutput, Project},
use ethers::solc::{ArtifactOutput, Project};
use forge::{
decode::decode_console_logs, executor::opts::EvmOpts, MultiContractRunnerBuilder, TestFilter,
TestResult,
};
use forge::{executor::opts::EvmOpts, MultiContractRunnerBuilder, TestFilter, TestResult};
use foundry_config::{figment::Figment, Config};
use regex::Regex;
use std::{collections::BTreeMap, str::FromStr, sync::mpsc::channel, thread};
Expand Down Expand Up @@ -354,9 +353,7 @@ fn test<A: ArtifactOutput + 'static>(
let mut add_newline = false;
if verbosity > 1 {
// We only decode logs from Hardhat and DS-style console events
let console_logs: Vec<String> =
result.logs.iter().filter_map(decode_console_log).collect();

let console_logs = decode_console_logs(&result.logs);
if !console_logs.is_empty() {
add_newline = true;
println!("Logs:");
Expand Down Expand Up @@ -450,41 +447,3 @@ fn test<A: ArtifactOutput + 'static>(
Ok(TestOutcome::new(results, allow_failure))
}
}

fn decode_console_log(log: &RawLog) -> Option<String> {
use forge::abi::ConsoleEvents::{self, *};

let decoded = match ConsoleEvents::decode_log(log).ok()? {
LogsFilter(inner) => format!("{}", inner.0),
LogBytesFilter(inner) => format!("{}", inner.0),
LogNamedAddressFilter(inner) => format!("{}: {:?}", inner.key, inner.val),
LogNamedBytes32Filter(inner) => {
format!("{}: 0x{}", inner.key, hex::encode(inner.val))
}
LogNamedDecimalIntFilter(inner) => {
let (sign, val) = inner.val.into_sign_and_abs();
format!(
"{}: {}{}",
inner.key,
sign,
ethers::utils::format_units(val, inner.decimals.as_u32()).unwrap()
)
}
LogNamedDecimalUintFilter(inner) => {
format!(
"{}: {}",
inner.key,
ethers::utils::format_units(inner.val, inner.decimals.as_u32()).unwrap()
)
}
LogNamedIntFilter(inner) => format!("{}: {:?}", inner.key, inner.val),
LogNamedUintFilter(inner) => format!("{}: {:?}", inner.key, inner.val),
LogNamedBytesFilter(inner) => {
format!("{}: 0x{}", inner.key, hex::encode(inner.val))
}
LogNamedStringFilter(inner) => format!("{}: {}", inner.key, inner.val),

e => e.to_string(),
};
Some(decoded)
}
2 changes: 1 addition & 1 deletion forge/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ rlp = "0.5.1"

bytes = "1.1.0"
thiserror = "1.0.29"
revm = { package = "revm", git = "https://github.com/bluealloy/revm", branch = "main" }
revm = { package = "revm", git = "https://github.com/onbjerg/revm", branch = "onbjerg/blockhashes", default-features = false, features = ["std", "k256"] }
hashbrown = "0.12"
once_cell = "1.9.0"

Expand Down
48 changes: 48 additions & 0 deletions forge/src/decode.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//! Various utilities to decode test results
use crate::abi::ConsoleEvents::{self, *};
use ethers::{abi::RawLog, contract::EthLogDecode};

/// Decode a set of logs, only returning logs from DSTest logging events and Hardhat's `console.log`
pub fn decode_console_logs(logs: &[RawLog]) -> Vec<String> {
logs.iter().filter_map(decode_console_log).collect()
}

/// Decode a single log.
///
/// This function returns [None] if it is not a DSTest log or the result of a Hardhat
/// `console.log`.
pub fn decode_console_log(log: &RawLog) -> Option<String> {
let decoded = match ConsoleEvents::decode_log(log).ok()? {
LogsFilter(inner) => format!("{}", inner.0),
LogBytesFilter(inner) => format!("{}", inner.0),
LogNamedAddressFilter(inner) => format!("{}: {:?}", inner.key, inner.val),
LogNamedBytes32Filter(inner) => {
format!("{}: 0x{}", inner.key, hex::encode(inner.val))
}
LogNamedDecimalIntFilter(inner) => {
let (sign, val) = inner.val.into_sign_and_abs();
format!(
"{}: {}{}",
inner.key,
sign,
ethers::utils::format_units(val, inner.decimals.as_u32()).unwrap()
)
}
LogNamedDecimalUintFilter(inner) => {
format!(
"{}: {}",
inner.key,
ethers::utils::format_units(inner.val, inner.decimals.as_u32()).unwrap()
)
}
LogNamedIntFilter(inner) => format!("{}: {:?}", inner.key, inner.val),
LogNamedUintFilter(inner) => format!("{}: {:?}", inner.key, inner.val),
LogNamedBytesFilter(inner) => {
format!("{}: 0x{}", inner.key, hex::encode(inner.val))
}
LogNamedStringFilter(inner) => format!("{}: {}", inner.key, inner.val),

e => e.to_string(),
};
Some(decoded)
}
41 changes: 41 additions & 0 deletions forge/src/executor/abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,47 @@ use ethers::types::{Address, Selector};
use once_cell::sync::Lazy;
use std::collections::HashMap;

/// The cheatcode handler address.
///
/// This is the same address as the one used in DappTools's HEVM.
pub static CHEATCODE_ADDRESS: Lazy<Address> = Lazy::new(|| {
Address::from_slice(&hex::decode("7109709ECfa91a80626fF3989D68f67F5b1DD12D").unwrap())
});

// Bindings for cheatcodes
ethers::contract::abigen!(
HEVM,
r#"[
roll(uint256)
warp(uint256)
fee(uint256)
store(address,bytes32,bytes32)
load(address,bytes32)(bytes32)
ffi(string[])(bytes)
addr(uint256)(address)
sign(uint256,bytes32)(uint8,bytes32,bytes32)
prank(address)
startPrank(address)
prank(address,address)
startPrank(address,address)
stopPrank()
deal(address,uint256)
etch(address,bytes)
expectRevert(bytes)
expectRevert(bytes4)
record()
accesses(address)(bytes32[],bytes32[])
expectEmit(bool,bool,bool,bool)
mockCall(address,bytes,bytes)
clearMockedCalls()
expectCall(address,bytes)
getCode(string)
label(address,string)
assume(bool)
]"#,
);
pub use hevm_mod::{HEVMCalls, HEVM_ABI};

/// The Hardhat console address.
///
/// See: https://github.com/nomiclabs/hardhat/blob/master/packages/hardhat-core/console.sol
Expand Down
24 changes: 11 additions & 13 deletions forge/src/executor/builder.rs
Original file line number Diff line number Diff line change
@@ -1,46 +1,44 @@
use revm::{db::EmptyDB, Env, SpecId};

use super::Executor;
use super::{inspector::InspectorStackConfig, Executor};

#[derive(Default)]
pub struct ExecutorBuilder {
/// Whether or not cheatcodes are enabled
cheatcodes: bool,
/// Whether or not the FFI cheatcode is enabled
ffi: bool,
/// The execution environment configuration.
config: Env,
env: Env,
/// The configuration used to build an [InspectorStack].
inspector_config: InspectorStackConfig,
}

impl ExecutorBuilder {
#[must_use]
pub fn new() -> Self {
Self { cheatcodes: false, ffi: false, config: Env::default() }
Default::default()
}

/// Enables cheatcodes on the executor.
#[must_use]
pub fn with_cheatcodes(mut self, ffi: bool) -> Self {
self.cheatcodes = true;
self.ffi = ffi;
self.inspector_config.cheatcodes = true;
self.inspector_config.ffi = ffi;
self
}

pub fn with_spec(mut self, spec: SpecId) -> Self {
self.config.cfg.spec_id = spec;
self.env.cfg.spec_id = spec;
self
}

/// Configure the execution environment (gas limit, chain spec, ...)
#[must_use]
pub fn with_config(mut self, config: Env) -> Self {
self.config = config;
pub fn with_config(mut self, env: Env) -> Self {
self.env = env;
self
}

/// Builds the executor as configured.
pub fn build(self) -> Executor<EmptyDB> {
Executor::new(EmptyDB(), self.config)
Executor::new(EmptyDB(), self.env, self.inspector_config)
}

// TODO: add with_traces
Expand Down
18 changes: 9 additions & 9 deletions forge/src/executor/fuzz/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
mod strategies;

// TODO Port when we have cheatcodes again
//use crate::{Evm, ASSUME_MAGIC_RETURN_CODE};
use crate::executor::{Executor, RawCallResult};
use ethers::{
abi::{Abi, Function},
Expand All @@ -11,10 +9,13 @@ use revm::{db::DatabaseRef, Return};
use strategies::fuzz_calldata;

pub use proptest::test_runner::{Config as FuzzConfig, Reason};
use proptest::test_runner::{TestError, TestRunner};
use proptest::test_runner::{TestCaseError, TestError, TestRunner};
use serde::{Deserialize, Serialize};
use std::cell::RefCell;

/// Magic return code for the `assume` cheatcode
pub const ASSUME_MAGIC_RETURN_CODE: &[u8] = "FOUNDRY::ASSUME".as_bytes();

/// Wrapper around an [`Executor`] which provides fuzzing support using [`proptest`](https://docs.rs/proptest/1.0.0/proptest/).
///
/// After instantiation, calling `fuzz` will proceed to hammer the deployed smart contract with
Expand Down Expand Up @@ -69,13 +70,12 @@ where
.expect("could not make raw evm call");

// When assume cheat code is triggered return a special string "FOUNDRY::ASSUME"
// TODO: Re-implement when cheatcodes are ported
/*if returndata.as_ref() == ASSUME_MAGIC_RETURN_CODE {
let _ = return_reason.borrow_mut().insert(reason);
if result.as_ref() == ASSUME_MAGIC_RETURN_CODE {
*return_reason.borrow_mut() = Some(status);
let err = "ASSUME: Too many rejects";
let _ = revert_reason.borrow_mut().insert(err.to_string());
return Err(TestCaseError::Reject(err.into()));
}*/
*revert_reason.borrow_mut() = Some(err.to_string());
return Err(TestCaseError::Reject(err.into()))
}

let success = self.executor.is_success(
address,
Expand Down
Loading