From b733d68395a20afeb075b57a8cb45263d68e7655 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Tue, 22 Feb 2022 21:10:41 +0100 Subject: [PATCH 01/17] feat: port fuzzer to revm --- forge/src/executor/fuzz/fuzzed_executor.rs | 0 forge/src/executor/fuzz/mod.rs | 315 +++++++++++++++++++++ forge/src/executor/fuzz/strategies.rs | 177 ++++++++++++ forge/src/executor/mod.rs | 3 + forge/src/lib.rs | 14 +- forge/src/runner.rs | 98 +++++-- 6 files changed, 583 insertions(+), 24 deletions(-) create mode 100644 forge/src/executor/fuzz/fuzzed_executor.rs create mode 100644 forge/src/executor/fuzz/mod.rs create mode 100644 forge/src/executor/fuzz/strategies.rs diff --git a/forge/src/executor/fuzz/fuzzed_executor.rs b/forge/src/executor/fuzz/fuzzed_executor.rs new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/forge/src/executor/fuzz/mod.rs b/forge/src/executor/fuzz/mod.rs new file mode 100644 index 0000000000000..871b6e35cd3e2 --- /dev/null +++ b/forge/src/executor/fuzz/mod.rs @@ -0,0 +1,315 @@ +mod strategies; + +// TODO +//use crate::{Evm, ASSUME_MAGIC_RETURN_CODE}; +use crate::executor::{Executor, RawCallResult}; +use ethers::{ + abi::{Abi, Function, ParamType, Token, Tokenizable}, + types::{Address, Bytes, I256, U256}, +}; +use revm::{db::DatabaseRef, Return}; + +pub use proptest::test_runner::{Config as FuzzConfig, Reason}; +use proptest::{ + prelude::*, + test_runner::{TestError, TestRunner}, +}; +use serde::{Deserialize, Serialize}; +use std::cell::RefCell; + +/// 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 +/// inputs, until it finds a counterexample. The provided [`TestRunner`] contains all the +/// configuration which can be overridden via [environment variables](https://docs.rs/proptest/1.0.0/proptest/test_runner/struct.Config.html) +pub struct FuzzedExecutor<'a, DB: DatabaseRef> { + /// The VM + executor: &'a Executor, + /// The fuzzer + runner: TestRunner, + /// The account that calls tests + sender: Address, +} + +impl<'a, DB> FuzzedExecutor<'a, DB> +where + DB: DatabaseRef, +{ + /// Instantiates a fuzzed executor given a testrunner + pub fn new(executor: &'a Executor, runner: TestRunner, sender: Address) -> Self { + Self { executor, runner, sender } + } + + /// Fuzzes the provided function, assuming it is available at the contract at `address` + /// If `should_fail` is set to `true`, then it will stop only when there's a success + /// test case. + /// + /// Returns a list of all the consumed gas and calldata of every fuzz case + pub fn fuzz( + &self, + func: &Function, + address: Address, + should_fail: bool, + abi: Option<&Abi>, + ) -> FuzzTestResult { + let strat = fuzz_calldata(func); + + // Stores the consumed gas and calldata of every successful fuzz call + let fuzz_cases: RefCell> = RefCell::new(Default::default()); + + // Stores the latest return and revert reason of a test call + let return_reason: RefCell> = RefCell::new(None); + let revert_reason = RefCell::new(None); + + let mut runner = self.runner.clone(); + tracing::debug!(func = ?func.name, should_fail, "fuzzing"); + let test_error = runner + .run(&strat, |calldata| { + let RawCallResult { status, result, gas, state_changeset, .. } = self + .executor + .call_raw(self.sender, address, calldata.0.clone(), 0.into()) + .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); + let err = "ASSUME: Too many rejects"; + let _ = revert_reason.borrow_mut().insert(err.to_string()); + return Err(TestCaseError::Reject(err.into())); + }*/ + + // We must check success before resetting the state, otherwise resetting the state + // will also reset the `failed` state variable back to false. + let success = self.executor.is_success( + address, + status, + state_changeset.expect("we should have a state changeset"), + should_fail, + ); + + // Store the result of this test case + let _ = return_reason.borrow_mut().insert(status); + if !success { + let revert = + foundry_utils::decode_revert(result.as_ref(), abi).unwrap_or_default(); + let _ = revert_reason.borrow_mut().insert(revert); + } + + // This will panic and get caught by the executor + proptest::prop_assert!( + success, + "{}, expected failure: {}, reason: '{}'", + func.name, + should_fail, + match foundry_utils::decode_revert(result.as_ref(), abi) { + Ok(e) => e, + Err(e) => e.to_string(), + } + ); + + // push test case to the case set + fuzz_cases.borrow_mut().push(FuzzCase { calldata, gas }); + + Ok(()) + }) + .err() + .map(|test_error| FuzzError { + test_error, + return_reason: return_reason.into_inner().expect("Reason must be set"), + revert_reason: revert_reason.into_inner().expect("Revert error string must be set"), + }); + + FuzzTestResult { cases: FuzzedCases::new(fuzz_cases.into_inner()), test_error } + } +} + +/// The outcome of a fuzz test +pub struct FuzzTestResult { + /// Every successful fuzz test case + pub cases: FuzzedCases, + /// if there was a case that resulted in an error, this contains the error and the return + /// reason of the failed call + pub test_error: Option, +} + +impl FuzzTestResult { + /// Returns `true` if all test cases succeeded + pub fn is_ok(&self) -> bool { + self.test_error.is_none() + } + + /// Returns `true` if a test case failed + pub fn is_err(&self) -> bool { + self.test_error.is_some() + } +} + +pub struct FuzzError { + /// The proptest error occurred as a result of a test case + pub test_error: TestError, + /// The return reason of the offending call + pub return_reason: Return, + /// The revert string of the offending call + pub revert_reason: String, +} + +/// Container type for all successful test cases +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(transparent)] +pub struct FuzzedCases { + cases: Vec, +} + +impl FuzzedCases { + pub fn new(mut cases: Vec) -> Self { + cases.sort_by_key(|c| c.gas); + Self { cases } + } + + pub fn cases(&self) -> &[FuzzCase] { + &self.cases + } + + pub fn into_cases(self) -> Vec { + self.cases + } + + /// Returns the median gas of all test cases + pub fn median_gas(&self) -> u64 { + let mid = self.cases.len() / 2; + self.cases.get(mid).map(|c| c.gas).unwrap_or_default() + } + + /// Returns the average gas use of all test cases + pub fn mean_gas(&self) -> u64 { + if self.cases.is_empty() { + return 0 + } + + (self.cases.iter().map(|c| c.gas as u128).sum::() / self.cases.len() as u128) as u64 + } + + pub fn highest(&self) -> Option<&FuzzCase> { + self.cases.last() + } + + pub fn lowest(&self) -> Option<&FuzzCase> { + self.cases.first() + } + + pub fn highest_gas(&self) -> u64 { + self.highest().map(|c| c.gas).unwrap_or_default() + } + + pub fn lowest_gas(&self) -> u64 { + self.lowest().map(|c| c.gas).unwrap_or_default() + } +} + +/// Data of a single fuzz test case +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct FuzzCase { + /// The calldata used for this fuzz test + pub calldata: Bytes, + // Consumed gas + pub gas: u64, +} + +/// Given a function, it returns a proptest strategy which generates valid abi-encoded calldata +/// for that function's input types. +pub fn fuzz_calldata(func: &Function) -> impl Strategy + '_ { + // We need to compose all the strategies generated for each parameter in all + // possible combinations + let strats = func.inputs.iter().map(|input| fuzz_param(&input.kind)).collect::>(); + + strats.prop_map(move |tokens| { + tracing::trace!(input = ?tokens); + func.encode_input(&tokens).unwrap().into() + }) +} + +/// The max length of arrays we fuzz for is 256. +const MAX_ARRAY_LEN: usize = 256; + +/// Given an ethabi parameter type, returns a proptest strategy for generating values for that +/// datatype. Works with ABI Encoder v2 tuples. +fn fuzz_param(param: &ParamType) -> impl Strategy { + match param { + ParamType::Address => { + // The key to making this work is the `boxed()` call which type erases everything + // https://altsysrq.github.io/proptest-book/proptest/tutorial/transforming-strategies.html + any::<[u8; 20]>().prop_map(|x| Address::from_slice(&x).into_token()).boxed() + } + ParamType::Bytes => any::>().prop_map(|x| Bytes::from(x).into_token()).boxed(), + // For ints and uints we sample from a U256, then wrap it to the correct size with a + // modulo operation. Note that this introduces modulo bias, but it can be removed with + // rejection sampling if it's determined the bias is too severe. Rejection sampling may + // slow down tests as it resamples bad values, so may want to benchmark the performance + // hit and weigh that against the current bias before implementing + ParamType::Int(n) => match n / 8 { + 32 => any::<[u8; 32]>() + .prop_map(move |x| I256::from_raw(U256::from(&x)).into_token()) + .boxed(), + y @ 1..=31 => any::<[u8; 32]>() + .prop_map(move |x| { + // Generate a uintN in the correct range, then shift it to the range of intN + // by subtracting 2^(N-1) + let uint = U256::from(&x) % U256::from(2).pow(U256::from(y * 8)); + let max_int_plus1 = U256::from(2).pow(U256::from(y * 8 - 1)); + let num = I256::from_raw(uint.overflowing_sub(max_int_plus1).0); + num.into_token() + }) + .boxed(), + _ => panic!("unsupported solidity type int{}", n), + }, + ParamType::Uint(n) => { + strategies::UintStrategy::new(*n, vec![]).prop_map(|x| x.into_token()).boxed() + } + ParamType::Bool => any::().prop_map(|x| x.into_token()).boxed(), + ParamType::String => any::>() + .prop_map(|x| Token::String(unsafe { std::str::from_utf8_unchecked(&x).to_string() })) + .boxed(), + ParamType::Array(param) => proptest::collection::vec(fuzz_param(param), 0..MAX_ARRAY_LEN) + .prop_map(Token::Array) + .boxed(), + ParamType::FixedBytes(size) => (0..*size as u64) + .map(|_| any::()) + .collect::>() + .prop_map(Token::FixedBytes) + .boxed(), + ParamType::FixedArray(param, size) => (0..*size as u64) + .map(|_| fuzz_param(param).prop_map(|param| param.into_token())) + .collect::>() + .prop_map(Token::FixedArray) + .boxed(), + ParamType::Tuple(params) => { + params.iter().map(fuzz_param).collect::>().prop_map(Token::Tuple).boxed() + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::test_helpers::{fuzz_executor, test_executor, COMPILED}; + + #[test] + fn prints_fuzzed_revert_reasons() { + let mut executor = test_executor(); + + let compiled = COMPILED.find("FuzzTests").expect("could not find contract"); + let (addr, _, _, _) = executor + .deploy(Address::zero(), compiled.bytecode().unwrap().0.clone(), 0.into()) + .unwrap(); + + let executor = fuzz_executor(&executor); + + let func = compiled.abi.unwrap().function("testFuzzedRevert").unwrap(); + let res = executor.fuzz(func, addr, false, compiled.abi); + let error = res.test_error.unwrap(); + let revert_reason = error.revert_reason; + assert_eq!(revert_reason, "fuzztest-revert"); + } +} diff --git a/forge/src/executor/fuzz/strategies.rs b/forge/src/executor/fuzz/strategies.rs new file mode 100644 index 0000000000000..75c54aa77f327 --- /dev/null +++ b/forge/src/executor/fuzz/strategies.rs @@ -0,0 +1,177 @@ +use ethers::core::rand::Rng; +use proptest::{ + strategy::{NewTree, Strategy, ValueTree}, + test_runner::TestRunner, +}; + +use ethers::types::U256; + +/// Value tree for unsigned ints (up to uint256). +/// This is very similar to proptest::BinarySearch +pub struct UintValueTree { + /// Lower base + lo: U256, + /// Current value + curr: U256, + /// Higher base + hi: U256, + /// If true cannot be simplified or complexified + fixed: bool, +} + +impl UintValueTree { + /// Create a new tree + /// # Arguments + /// * `start` - Starting value for the tree + /// * `fixed` - If `true` the tree would only contain one element and won't be simplified. + fn new(start: U256, fixed: bool) -> Self { + Self { lo: 0.into(), curr: start, hi: start, fixed } + } + + fn reposition(&mut self) -> bool { + let interval = self.hi - self.lo; + let new_mid = self.lo + interval / 2; + + if new_mid == self.curr { + false + } else { + self.curr = new_mid; + true + } + } +} + +impl ValueTree for UintValueTree { + type Value = U256; + + fn current(&self) -> Self::Value { + self.curr + } + + fn simplify(&mut self) -> bool { + if self.fixed || (self.hi <= self.lo) { + return false + } + + self.hi = self.curr; + self.reposition() + } + + fn complicate(&mut self) -> bool { + if self.fixed || (self.hi <= self.lo) { + return false + } + + self.lo = self.curr + 1; + self.reposition() + } +} + +/// Value tree for unsigned ints (up to uint256). +/// The strategy combines 3 different strategies, each assigned a specific weight: +/// 1. Generate purely random value in a range. This will first choose bit size uniformly (up `bits` +/// param). Then generate a value for this bit size. +/// 2. Generate a random value around the edges (+/- 3 around 0 and max possible value) +/// 3. Generate a value from a predefined fixtures set +#[derive(Debug)] +pub struct UintStrategy { + /// Bit sise of uint (e.g. 256) + bits: usize, + /// A set of fixtures to be generated + fixtures: Vec, + /// The weight for edge cases (+/- 3 around 0 and max possible value) + edge_weight: usize, + /// The weight for fixtures + fixtures_weight: usize, + /// The weight for purely random values + random_weight: usize, +} + +impl UintStrategy { + /// Create a new strategy. + /// #Arguments + /// * `bits` - Size of uint in bits + /// * `fixtures` - A set of fixed values to be generated (according to fixtures weight) + pub fn new(bits: usize, fixtures: Vec) -> Self { + Self { + bits, + fixtures, + edge_weight: 10usize, + fixtures_weight: 40usize, + random_weight: 50usize, + } + } + + fn generate_edge_tree(&self, runner: &mut TestRunner) -> NewTree { + let rng = runner.rng(); + + // Choose if we want values around 0 or max + let is_min = rng.gen_bool(0.5); + let offset = U256::from(rng.gen_range(0..4)); + let max = if self.bits < 256 { + (U256::from(1u8) << U256::from(self.bits)) - 1 + } else { + U256::MAX + }; + let start = if is_min { offset } else { max - offset }; + + Ok(UintValueTree::new(start, false)) + } + + fn generate_fixtures_tree(&self, runner: &mut TestRunner) -> NewTree { + // generate edge cases if there's no fixtures + if self.fixtures.is_empty() { + return self.generate_edge_tree(runner) + } + let idx = runner.rng().gen_range(0..self.fixtures.len()); + + Ok(UintValueTree::new(self.fixtures[idx], false)) + } + + fn generate_random_tree(&self, runner: &mut TestRunner) -> NewTree { + let rng = runner.rng(); + // generate random number of bits uniformly + let bits = rng.gen_range(0..=self.bits); + + // init 2 128-bit randoms + let mut higher: u128 = rng.gen_range(0..=u128::MAX); + let mut lower: u128 = rng.gen_range(0..=u128::MAX); + + // cut 2 randoms according to bits size + match bits { + x if x < 128 => { + lower &= (1u128 << x) - 1; + higher = 0; + } + x if (128..256).contains(&x) => higher &= (1u128 << (x - 128)) - 1, + _ => {} + }; + + // init U256 from 2 randoms + let mut inner: [u64; 4] = [0; 4]; + let mask64 = (1 << 65) - 1; + inner[0] = (lower & mask64) as u64; + inner[1] = (lower >> 64) as u64; + inner[2] = (higher & mask64) as u64; + inner[3] = (higher >> 64) as u64; + let start: U256 = U256(inner); + + Ok(UintValueTree::new(start, false)) + } +} + +impl Strategy for UintStrategy { + type Tree = UintValueTree; + type Value = U256; + + fn new_tree(&self, runner: &mut TestRunner) -> NewTree { + let total_weight = self.random_weight + self.fixtures_weight + self.edge_weight; + let bias = runner.rng().gen_range(0..total_weight); + // randomly selecty one of 3 strategies + match bias { + x if x < self.edge_weight => self.generate_edge_tree(runner), + x if x < self.edge_weight + self.fixtures_weight => self.generate_fixtures_tree(runner), + _ => self.generate_random_tree(runner), + } + } +} diff --git a/forge/src/executor/mod.rs b/forge/src/executor/mod.rs index f12eede50c5f7..cb6e988fe78d5 100644 --- a/forge/src/executor/mod.rs +++ b/forge/src/executor/mod.rs @@ -19,6 +19,9 @@ pub mod inspector; pub mod builder; pub use builder::ExecutorBuilder; +/// Fuzzing wrapper for executors +pub mod fuzz; + /// Executor EVM spec identifiers pub use revm::SpecId; diff --git a/forge/src/lib.rs b/forge/src/lib.rs index f91bcef7f993c..e3487e292de20 100644 --- a/forge/src/lib.rs +++ b/forge/src/lib.rs @@ -17,6 +17,8 @@ pub trait TestFilter { #[cfg(test)] pub mod test_helpers { + use crate::executor::fuzz::FuzzedExecutor; + use super::{ executor::{ opts::{Env, EvmOpts}, @@ -27,10 +29,10 @@ pub mod test_helpers { use ethers::{ prelude::Lazy, solc::{AggregatedCompilerOutput, Project, ProjectPathsConfig}, - types::U256, + types::{Address, U256}, }; use regex::Regex; - use revm::db::EmptyDB; + use revm::db::{DatabaseRef, EmptyDB}; pub static COMPILED: Lazy = Lazy::new(|| { let paths = @@ -49,6 +51,14 @@ pub mod test_helpers { ExecutorBuilder::new().with_cheatcodes(false).with_config((*EVM_OPTS).env.evm_env()).build() } + pub fn fuzz_executor<'a, DB: DatabaseRef>( + executor: &'a Executor, + ) -> FuzzedExecutor<'a, DB> { + let cfg = proptest::test_runner::Config { failure_persistence: None, ..Default::default() }; + + FuzzedExecutor::new(executor, proptest::test_runner::TestRunner::new(cfg), Address::zero()) + } + pub struct Filter { test_regex: Regex, contract_regex: Regex, diff --git a/forge/src/runner.rs b/forge/src/runner.rs index 9c71bb55e0383..98eace2587c8a 100644 --- a/forge/src/runner.rs +++ b/forge/src/runner.rs @@ -1,5 +1,8 @@ use crate::{ - executor::{CallResult, EvmError, Executor}, + executor::{ + fuzz::{FuzzError, FuzzTestResult, FuzzedCases, FuzzedExecutor}, + CallResult, EvmError, Executor, RawCallResult, + }, TestFilter, }; use rayon::iter::ParallelIterator; @@ -13,7 +16,7 @@ use ethers::{ use eyre::Result; use std::{collections::BTreeMap, fmt, time::Instant}; -use proptest::test_runner::TestRunner; +use proptest::test_runner::{TestError, TestRunner}; use rayon::iter::IntoParallelRefIterator; use serde::{Deserialize, Serialize}; @@ -84,7 +87,7 @@ pub struct TestResult { impl TestResult { /// Returns `true` if this is the result of a fuzz test pub fn is_fuzz(&self) -> bool { - matches!(self.kind, TestKind::Fuzz) + matches!(self.kind, TestKind::Fuzz(_)) } } @@ -113,7 +116,7 @@ impl TestKindGas { pub fn gas(&self) -> u64 { match self { TestKindGas::Standard(gas) => *gas, - // we use the median for comparisons + // We use the median for comparisons TestKindGas::Fuzz { median, .. } => *median, } } @@ -127,9 +130,7 @@ pub enum TestKind { /// Holds the consumed gas Standard(u64), /// A solidity fuzz test, that stores all test cases - // TODO - //Fuzz(FuzzedCases), - Fuzz, + Fuzz(FuzzedCases), } impl TestKind { @@ -137,7 +138,11 @@ impl TestKind { pub fn gas_used(&self) -> TestKindGas { match self { TestKind::Standard(gas) => TestKindGas::Standard(*gas), - TestKind::Fuzz => TestKindGas::Fuzz { runs: 0, median: 0, mean: 0 }, + TestKind::Fuzz(fuzzed) => TestKindGas::Fuzz { + runs: fuzzed.cases().len(), + median: fuzzed.median_gas(), + mean: fuzzed.mean_gas(), + }, } } } @@ -311,10 +316,8 @@ impl<'a, DB: DatabaseRef + Clone + Send + Sync> ContractRunner<'a, DB> { mut logs: Vec, ) -> Result { let start = Instant::now(); - // the expected result depends on the function name - // DAppTools' ds-test will not revert inside its `assertEq`-like functions - // which allows to test multiple assertions in 1 test function while also - // preserving logs. + // The expected result depends on the function name. + // TODO: Dedupe (`TestDescriptor`?) let should_fail = func.name.starts_with("testFail"); tracing::debug!(func = ?func.signature(), should_fail, "unit-testing"); @@ -351,6 +354,9 @@ impl<'a, DB: DatabaseRef + Clone + Send + Sync> ContractRunner<'a, DB> { }, }; + // DSTest will not revert inside its `assertEq`-like functions + // which allows to test multiple assertions in 1 test function while also + // preserving logs - instead it sets `failed` to `true` which we must check. let success = self.executor.is_success( address, status, @@ -378,20 +384,68 @@ impl<'a, DB: DatabaseRef + Clone + Send + Sync> ContractRunner<'a, DB> { pub fn run_fuzz_test( &self, func: &Function, - _runner: TestRunner, + runner: TestRunner, _known_contracts: Option<&BTreeMap)>>, - _address: Address, - _init_logs: Vec, + address: Address, + mut logs: Vec, ) -> Result { + // We do not trace in fuzz tests as it is a big performance hit + let start = Instant::now(); + // TODO: Dedupe (`TestDescriptor`?) + let should_fail = func.name.starts_with("testFail"); + + let identified_contracts: Option> = None; + + // Wrap the executor in a fuzzed version + // TODO: When tracing is ported, we should disable it here. + let executor = FuzzedExecutor::new(&self.executor, runner, self.sender); + let FuzzTestResult { cases, test_error } = + executor.fuzz(func, address, should_fail, Some(self.contract)); + + // Rerun the failed fuzz case to get more information like traces and logs + if let Some(FuzzError { test_error: TestError::Fail(_, ref calldata), .. }) = test_error { + // TODO: When tracing is ported, we should re-enable it here to get traces. + let RawCallResult { logs: execution_logs, .. } = + self.executor.call_raw(self.sender, address, calldata.0.clone(), 0.into())?; + logs.extend(execution_logs); + } + + let (success, counterexample, reason) = match test_error { + Some(err) => { + let (counterexample, reason) = match err.test_error { + TestError::Abort(r) if r == "Too many global rejects".into() => { + (None, Some(r.message().to_string())) + } + TestError::Fail(_, calldata) => { + // Skip the function selector when decoding + let args = func.decode_input(&calldata.as_ref()[4..])?; + + (Some(CounterExample { calldata, args }), None) + } + e => panic!("Unexpected test error: {:?}", e), + }; + + if !err.revert_reason.is_empty() { + (false, counterexample, Some(err.revert_reason)) + } else { + (false, counterexample, reason) + } + } + _ => (true, None, None), + }; + + let duration = Instant::now().duration_since(start); + tracing::debug!(?duration, %success); + Ok(TestResult { - success: false, - reason: Some("Fuzz tests not implemented".to_string()), - gas_used: 0, - counterexample: None, - logs: vec![], - kind: TestKind::Fuzz, + success, + reason, + gas_used: cases.median_gas(), + counterexample, + logs, + kind: TestKind::Fuzz(cases), traces: Default::default(), - identified_contracts: Default::default(), + identified_contracts, debug_calls: None, labeled_addresses: Default::default(), }) From 81a21a1c64d9b90e30eaf498b81e0c3a138c3230 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Tue, 22 Feb 2022 21:11:07 +0100 Subject: [PATCH 02/17] chore: pull over some tests from `evm-adapters` --- forge/testdata/Fuzz.sol | 7 +++ forge/testdata/LargeContract.sol | 5 +++ forge/testdata/Logs.sol | 45 +++++++++++++++++++ forge/testdata/TestNumbers.sol | 41 +++++++++++++++++ forge/testdata/Trace.sol | 75 ++++++++++++++++++++++++++++++++ 5 files changed, 173 insertions(+) create mode 100644 forge/testdata/Fuzz.sol create mode 100644 forge/testdata/LargeContract.sol create mode 100644 forge/testdata/Logs.sol create mode 100644 forge/testdata/TestNumbers.sol create mode 100644 forge/testdata/Trace.sol diff --git a/forge/testdata/Fuzz.sol b/forge/testdata/Fuzz.sol new file mode 100644 index 0000000000000..e5552b4a41598 --- /dev/null +++ b/forge/testdata/Fuzz.sol @@ -0,0 +1,7 @@ +pragma solidity ^0.8.10; + +contract FuzzTests { + function testFuzzedRevert(uint256 x) public { + require(x == 5, "fuzztest-revert"); + } +} diff --git a/forge/testdata/LargeContract.sol b/forge/testdata/LargeContract.sol new file mode 100644 index 0000000000000..f642885cf9f0f --- /dev/null +++ b/forge/testdata/LargeContract.sol @@ -0,0 +1,5 @@ +pragma solidity >=0.4.0; + +contract LargeContract { + string public foo = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; +} diff --git a/forge/testdata/Logs.sol b/forge/testdata/Logs.sol new file mode 100644 index 0000000000000..091bf7558cdf9 --- /dev/null +++ b/forge/testdata/Logs.sol @@ -0,0 +1,45 @@ +pragma solidity ^0.8.0; + +import "./DsTest.sol"; + +contract DebugLogs is DSTest { + function test_log() public { + emit log("Hi"); + emit logs(hex"1234"); + emit log_address(0x1111111111111111111111111111111111111111); + emit log_bytes32(keccak256(abi.encodePacked("foo"))); + emit log_int(123); + emit log_uint(1234); + emit log_bytes(hex"4567"); + emit log_string("lol"); + emit log_named_address("addr", 0x2222222222222222222222222222222222222222); + emit log_named_bytes32("key", keccak256(abi.encodePacked("foo"))); + emit log_named_decimal_int("key", 123, 18); + emit log_named_decimal_int("key", -123, 18); + emit log_named_decimal_int("key", 1.0e18, 18); + emit log_named_decimal_int("key", -1.0e18, 18); + emit log_named_decimal_int("key", -123, 12); + emit log_named_decimal_int("key", -1.0e18, 12); + emit log_named_decimal_uint("key", 1234, 18); + emit log_named_decimal_uint("key", 1.0e18, 18); + emit log_named_decimal_uint("key", 1234, 12); + emit log_named_decimal_uint("key", 1.0e18, 12); + emit log_named_int("key", 123); + emit log_named_uint("key", 1234); + emit log_named_bytes("key", hex"4567"); + emit log_named_string("key", "lol"); + } + + function test_log_elsewhere() public { + OtherContract otherContract = new OtherContract(); + otherContract.test_log(); + } +} + +contract OtherContract is DSTest { + function test_log() public { + emit log_address(0x1111111111111111111111111111111111111111); + emit log("Hi"); + } +} + diff --git a/forge/testdata/TestNumbers.sol b/forge/testdata/TestNumbers.sol new file mode 100644 index 0000000000000..6be8929dab733 --- /dev/null +++ b/forge/testdata/TestNumbers.sol @@ -0,0 +1,41 @@ +pragma solidity ^0.8.0; + +import "./DsTest.sol"; + +contract TestNumbers is DSTest { + function testPositive(uint256) public { + assertTrue(true); + } + + function testNegativeHalf(uint256 val) public { + assertTrue(val < 2 ** 128 - 1); + } + + function testNegative0(uint256 val) public { + assertTrue(val != 0); + } + + function testNegative2(uint256 val) public { + assertTrue(val != 2); + } + + function testNegative2Max(uint256 val) public { + assertTrue(val != type(uint256).max - 2); + } + + function testNegativeMax(uint256 val) public { + assertTrue(val != type(uint256).max); + } + + function testEquality(uint256 x, uint256 y) public { + uint256 xy; + + unchecked { + xy = x * y; + } + + if ((x != 0 && xy / x != y)) return; + + assertEq(((xy - 1) / 1e18) + 1, (xy - 1) / (1e18 + 1)); + } +} diff --git a/forge/testdata/Trace.sol b/forge/testdata/Trace.sol new file mode 100644 index 0000000000000..5b905aa44d63a --- /dev/null +++ b/forge/testdata/Trace.sol @@ -0,0 +1,75 @@ +pragma solidity ^0.8.0; + + +interface RecursiveCallee { + function recurseCall(uint256 neededDepth, uint256 depth) external returns (uint256); + function recurseCreate(uint256 neededDepth, uint256 depth) external returns (uint256); + function someCall() external; + function negativeNum() external returns (int256); +} + +contract RecursiveCall { + event Depth(uint256 depth); + event ChildDepth(uint256 childDepth); + event CreatedChild(uint256 childDepth); + Trace factory; + + constructor(address _factory) { + factory = Trace(_factory); + } + + function recurseCall(uint256 neededDepth, uint256 depth) public returns (uint256) { + if (depth == neededDepth) { + RecursiveCallee(address(this)).negativeNum(); + return neededDepth; + } + uint256 childDepth = RecursiveCallee(address(this)).recurseCall(neededDepth, depth + 1); + emit ChildDepth(childDepth); + RecursiveCallee(address(this)).someCall(); + emit Depth(depth); + return depth; + } + + function recurseCreate(uint256 neededDepth, uint256 depth) public returns (uint256) { + if (depth == neededDepth) { + return neededDepth; + } + RecursiveCall child = factory.create(); + emit CreatedChild(depth + 1); + uint256 childDepth = child.recurseCreate(neededDepth, depth + 1); + emit Depth(depth); + return depth; + } + + function someCall() public {} + + function negativeNum() public returns (int256) { + return -1000000000; + } +} + +contract Trace { + RecursiveCall first; + + function create() public returns (RecursiveCall) { + if (address(first) == address(0)) { + first = new RecursiveCall(address(this)); + return first; + } + return new RecursiveCall(address(this)); + } + + function recurseCall(uint256 neededDepth, uint256 depth) public returns (uint256) { + if (address(first) == address(0)) { + first = new RecursiveCall(address(this)); + } + return first.recurseCall(neededDepth, depth); + } + + function recurseCreate(uint256 neededDepth, uint256 depth) public returns (uint256) { + if (address(first) == address(0)) { + first = new RecursiveCall(address(this)); + } + return first.recurseCreate(neededDepth, depth); + } +} From a40d32e92bda29f267802ef1cd4506a6e3809a0a Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Tue, 22 Feb 2022 21:11:22 +0100 Subject: [PATCH 03/17] test: re-enable tests for runner and multirunner --- forge/src/multi_runner.rs | 43 ++++++++++++++++----------------------- forge/src/runner.rs | 42 +++++++++++++++++--------------------- 2 files changed, 36 insertions(+), 49 deletions(-) diff --git a/forge/src/multi_runner.rs b/forge/src/multi_runner.rs index e4f8aa8bb9202..52718b598cfa2 100644 --- a/forge/src/multi_runner.rs +++ b/forge/src/multi_runner.rs @@ -40,8 +40,7 @@ impl MultiContractRunnerBuilder { /// against that evm pub fn build(self, project: Project, evm_opts: EvmOpts) -> Result where - // TODO: Can we remove the static? It's due to the `into_artifacts()` call below - A: ArtifactOutput + 'static, + A: ArtifactOutput, { println!("compiling..."); let output = project.compile()?; @@ -234,45 +233,37 @@ mod tests { use super::*; use crate::test_helpers::{Filter, EVM_OPTS}; use ethers::solc::ProjectPathsConfig; - use std::path::PathBuf; - use std::collections::HashMap; + fn project() -> Project { - let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata"); - - let paths = ProjectPathsConfig::builder().root(&root).sources(&root).build().unwrap(); - - Project::builder() - // need to explicitly allow a path outside the project - .allowed_path(root.join("../../evm-adapters/testdata")) - .paths(paths) - .ephemeral() - .no_artifacts() - .build() - .unwrap() + let paths = + ProjectPathsConfig::builder().root("testdata").sources("testdata").build().unwrap(); + + Project::builder().paths(paths).ephemeral().no_artifacts().build().unwrap() } fn runner() -> MultiContractRunner { MultiContractRunnerBuilder::default().build(project(), EVM_OPTS.clone()).unwrap() } - // TODO: This test is currently ignored since it tries to run fuzz tests as well, which we have - // not implemented yet. We should re-enable when we have that capability. #[test] - #[ignore] fn test_multi_runner() { let mut runner = runner(); let results = runner.test(&Filter::new(".*", ".*")).unwrap(); // 9 contracts being built - assert_eq!(results.keys().len(), 9); + assert_eq!(results.keys().len(), 11); for (key, contract_tests) in results { - // for a bad setup, we dont want a successful test - if key == "SetupTest.json:SetupTest" { - assert!(contract_tests.iter().all(|(_, result)| !result.success)); - } else { - assert_ne!(contract_tests.keys().len(), 0); - assert!(contract_tests.iter().all(|(_, result)| result.success)); + match key.as_str() { + // Tests that should revert + "SetupTest.json:SetupTest" | "FuzzTests.json:FuzzTests" => { + assert!(contract_tests.iter().all(|(_, result)| !result.success)) + } + // The rest should pass + _ => { + assert_ne!(contract_tests.keys().len(), 0); + assert!(contract_tests.iter().all(|(k, result)| { result.success })) + } } } diff --git a/forge/src/runner.rs b/forge/src/runner.rs index 98eace2587c8a..9b116c0eef0ce 100644 --- a/forge/src/runner.rs +++ b/forge/src/runner.rs @@ -454,10 +454,9 @@ impl<'a, DB: DatabaseRef + Clone + Send + Sync> ContractRunner<'a, DB> { #[cfg(test)] mod tests { - use super::*; use crate::test_helpers::{test_executor, Filter, COMPILED, EVM_OPTS}; - use ethers::solc::artifacts::CompactContractRef; + use super::*; use proptest::test_runner::Config as FuzzConfig; use revm::db::EmptyDB; @@ -477,8 +476,7 @@ mod tests { ) } - // TODO: Port these tests when implementing fuzzing - /*#[test] + #[test] fn test_function_overriding() { let compiled = COMPILED.find("GreeterTest").expect("could not find contract"); @@ -519,15 +517,16 @@ mod tests { let compiled = COMPILED.find("GreeterTest").expect("could not find contract"); let (_, code, _) = compiled.into_parts_or_default(); let mut libs = vec![]; - let runner = runner(compiled.abi.as_ref().unwrap(), code, &mut libs); + let mut runner = runner(compiled.abi.as_ref().unwrap(), code, &mut libs); let mut cfg = FuzzConfig::default(); cfg.failure_persistence = None; let fuzzer = TestRunner::new(cfg); - let func = get_func("testStringFuzz(string)").unwrap(); - let res = runner.run_fuzz_test(&func, true, fuzzer, None).unwrap(); - assert!(res.success); - assert!(res.counterexample.is_none()); + let res = + runner.run_tests(&Filter::new("testStringFuzz.*", ".*"), Some(fuzzer), None).unwrap(); + assert_eq!(res.len(), 1); + assert!(res["testStringFuzz(string)"].success); + assert!(res["testStringFuzz(string)"].counterexample.is_none()); } #[test] @@ -535,13 +534,16 @@ mod tests { let compiled = COMPILED.find("GreeterTest").expect("could not find contract"); let (_, code, _) = compiled.into_parts_or_default(); let mut libs = vec![]; - let runner = runner(compiled.abi.as_ref().unwrap(), code, &mut libs); + let mut runner = runner(compiled.abi.as_ref().unwrap(), code, &mut libs); let mut cfg = FuzzConfig::default(); cfg.failure_persistence = None; let fuzzer = TestRunner::new(cfg); - let func = get_func("function testShrinking(uint256 x, uint256 y) public").unwrap(); - let res = runner.run_fuzz_test(&func, fuzzer, None, Vec::new()).unwrap(); + let res = + runner.run_tests(&Filter::new("testShrinking.*", ".*"), Some(fuzzer), None).unwrap(); + assert_eq!(res.len(), 1); + + let res = res["testShrinking(uint256,uint256)"].clone(); assert!(!res.success); // get the counterexample with shrinking enabled by default @@ -557,7 +559,11 @@ mod tests { // we reduce the shrinking iters and observe a larger result cfg.max_shrink_iters = 5; let fuzzer = TestRunner::new(cfg); - let res = runner.run_fuzz_test(&func, fuzzer, None, Vec::new()).unwrap(); + let res = + runner.run_tests(&Filter::new("testShrinking.*", ".*"), Some(fuzzer), None).unwrap(); + assert_eq!(res.len(), 1); + + let res = res["testShrinking(uint256,uint256)"].clone(); assert!(!res.success); // get the non-shrunk result @@ -566,15 +572,5 @@ mod tests { counterexample.args.into_iter().map(|x| x.into_uint().unwrap()).collect::>(); let product_without_shrinking = args[0].saturating_mul(args[1]); assert!(product_without_shrinking > product_with_shrinking.into()); - }*/ - - pub fn test_runner(compiled: CompactContractRef) { - let (_, code, _) = compiled.into_parts_or_default(); - let mut libs = vec![]; - let mut runner = runner(compiled.abi.as_ref().unwrap(), code, &mut libs); - let res = runner.run_tests(&Filter::new(".*", ".*"), None, None).unwrap(); - - assert!(!res.is_empty()); - assert!(res.iter().all(|(_, result)| result.success)); } } From 8057e1f8cb3a3e7c850dd10c69ca1760fc6214a0 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Tue, 22 Feb 2022 21:12:10 +0100 Subject: [PATCH 04/17] chore: update revm --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5260fc6fb09ef..bd8c0f7dfc366 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3162,7 +3162,7 @@ dependencies = [ [[package]] name = "revm" version = "1.2.0" -source = "git+https://github.com/bluealloy/revm?branch=main#739b348ca5112b06e2b71f7f03fbacc115bc43c0" +source = "git+https://github.com/bluealloy/revm?branch=main#6e9209cbdb267dac25b88757bdcf27a8340b3a69" dependencies = [ "arrayref", "auto_impl", @@ -3179,7 +3179,7 @@ dependencies = [ [[package]] name = "revm_precompiles" version = "0.4.0" -source = "git+https://github.com/bluealloy/revm?branch=main#739b348ca5112b06e2b71f7f03fbacc115bc43c0" +source = "git+https://github.com/bluealloy/revm?branch=main#6e9209cbdb267dac25b88757bdcf27a8340b3a69" dependencies = [ "bytes", "num 0.4.0", From 30324ac9afe0b7e631f9b643f21163a4ee0fab63 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Tue, 22 Feb 2022 21:15:29 +0100 Subject: [PATCH 05/17] refactor: use upstream `CacheDB` The `DatabaseRef` is now implemented in upstream REVM --- forge/src/executor/db/cache.rs | 144 --------------------------------- forge/src/executor/db/mod.rs | 2 - forge/src/executor/mod.rs | 6 +- 3 files changed, 1 insertion(+), 151 deletions(-) delete mode 100644 forge/src/executor/db/cache.rs delete mode 100644 forge/src/executor/db/mod.rs diff --git a/forge/src/executor/db/cache.rs b/forge/src/executor/db/cache.rs deleted file mode 100644 index aa91e8adc6240..0000000000000 --- a/forge/src/executor/db/cache.rs +++ /dev/null @@ -1,144 +0,0 @@ -use bytes::Bytes; -use ethers::{ - types::{Address, H256, U256}, - utils::keccak256, -}; -use hashbrown::hash_map::{Entry, HashMap}; -use revm::{ - db::{Database, DatabaseCommit, DatabaseRef}, - Account, AccountInfo, Filth, KECCAK_EMPTY, -}; - -pub struct CacheDB { - cache: HashMap, - storage: HashMap>, - contracts: HashMap, - db: D, -} - -impl CacheDB { - pub fn new(db: D) -> Self { - let mut contracts = HashMap::new(); - contracts.insert(KECCAK_EMPTY, Bytes::new()); - contracts.insert(H256::zero(), Bytes::new()); - Self { cache: HashMap::new(), storage: HashMap::new(), contracts, db } - } - - pub fn insert_cache(&mut self, address: Address, mut account: AccountInfo) { - let code = core::mem::take(&mut account.code); - if let Some(code) = code { - if !code.is_empty() { - let code_hash = H256::from_slice(&keccak256(&code)); - account.code_hash = code_hash; - self.contracts.insert(code_hash, code); - } - } - if account.code_hash.is_zero() { - account.code_hash = KECCAK_EMPTY; - } - self.cache.insert(address, account); - } -} - -impl DatabaseCommit for CacheDB { - fn commit(&mut self, changes: HashMap) { - for (add, acc) in changes { - if acc.is_empty() || matches!(acc.filth, Filth::Destroyed) { - self.cache.remove(&add); - self.storage.remove(&add); - } else { - self.insert_cache(add, acc.info); - let storage = self.storage.entry(add).or_default(); - if acc.filth.abandon_old_storage() { - storage.clear(); - } - for (index, value) in acc.storage { - if value.is_zero() { - storage.remove(&index); - } else { - storage.insert(index, value); - } - } - if storage.is_empty() { - self.storage.remove(&add); - } - } - } - } -} - -impl Database for CacheDB { - fn block_hash(&mut self, number: U256) -> H256 { - self.db.block_hash(number) - } - - fn basic(&mut self, address: Address) -> AccountInfo { - match self.cache.entry(address) { - Entry::Occupied(entry) => entry.get().clone(), - Entry::Vacant(entry) => { - let acc = self.db.basic(address); - if !acc.is_empty() { - entry.insert(acc.clone()); - } - acc - } - } - } - - fn storage(&mut self, address: Address, index: U256) -> U256 { - match self.storage.entry(address) { - Entry::Occupied(mut entry) => match entry.get_mut().entry(index) { - Entry::Occupied(entry) => *entry.get(), - Entry::Vacant(entry) => { - let slot = self.db.storage(address, index); - entry.insert(slot); - slot - } - }, - Entry::Vacant(entry) => { - let mut storage = HashMap::new(); - let slot = self.db.storage(address, index); - storage.insert(index, slot); - entry.insert(storage); - slot - } - } - } - - fn code_by_hash(&mut self, code_hash: H256) -> Bytes { - match self.contracts.entry(code_hash) { - Entry::Occupied(entry) => entry.get().clone(), - Entry::Vacant(entry) => entry.insert(self.db.code_by_hash(code_hash)).clone(), - } - } -} - -impl DatabaseRef for CacheDB { - fn block_hash(&self, number: U256) -> H256 { - self.db.block_hash(number) - } - - fn basic(&self, address: Address) -> AccountInfo { - match self.cache.get(&address) { - Some(info) => info.clone(), - None => self.db.basic(address), - } - } - - fn storage(&self, address: Address, index: U256) -> U256 { - match self.storage.get(&address) { - Some(entry) => match entry.get(&index) { - Some(entry) => *entry, - None => self.db.storage(address, index), - }, - None => self.db.storage(address, index), - } - } - - fn code_by_hash(&self, code_hash: H256) -> Bytes { - match self.contracts.get(&code_hash) { - Some(entry) => entry.clone(), - None => self.db.code_by_hash(code_hash), - } - } -} diff --git a/forge/src/executor/db/mod.rs b/forge/src/executor/db/mod.rs deleted file mode 100644 index a7be0e49d989f..0000000000000 --- a/forge/src/executor/db/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod cache; -pub use cache::CacheDB; diff --git a/forge/src/executor/mod.rs b/forge/src/executor/mod.rs index cb6e988fe78d5..88ac0355f530d 100644 --- a/forge/src/executor/mod.rs +++ b/forge/src/executor/mod.rs @@ -8,10 +8,6 @@ pub use abi::{ /// Executor configuration pub mod opts; -/// Executor databases -pub mod db; -pub use db::CacheDB; - /// Executor inspectors pub mod inspector; @@ -35,7 +31,7 @@ use foundry_utils::IntoFunction; use hashbrown::HashMap; use inspector::{ExecutorState, LogCollector}; use revm::{ - db::{DatabaseCommit, DatabaseRef, EmptyDB}, + db::{CacheDB, DatabaseCommit, DatabaseRef, EmptyDB}, return_ok, Account, CreateScheme, Env, Return, TransactOut, TransactTo, TxEnv, EVM, }; use std::{cell::RefCell, rc::Rc}; From 76b98fa75b61b4699a0ae1032ebf83790e130877 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Tue, 22 Feb 2022 21:25:45 +0100 Subject: [PATCH 06/17] refactor: clean up fuzz executor file --- forge/src/executor/fuzz/mod.rs | 89 ++----------------- .../src/executor/fuzz/strategies/calldata.rs | 16 ++++ forge/src/executor/fuzz/strategies/mod.rs | 8 ++ forge/src/executor/fuzz/strategies/param.rs | 65 ++++++++++++++ .../{strategies.rs => strategies/uint.rs} | 0 5 files changed, 98 insertions(+), 80 deletions(-) create mode 100644 forge/src/executor/fuzz/strategies/calldata.rs create mode 100644 forge/src/executor/fuzz/strategies/mod.rs create mode 100644 forge/src/executor/fuzz/strategies/param.rs rename forge/src/executor/fuzz/{strategies.rs => strategies/uint.rs} (100%) diff --git a/forge/src/executor/fuzz/mod.rs b/forge/src/executor/fuzz/mod.rs index 871b6e35cd3e2..7e29416fa1ec8 100644 --- a/forge/src/executor/fuzz/mod.rs +++ b/forge/src/executor/fuzz/mod.rs @@ -1,19 +1,17 @@ mod strategies; -// TODO +// TODO Port when we have cheatcodes again //use crate::{Evm, ASSUME_MAGIC_RETURN_CODE}; use crate::executor::{Executor, RawCallResult}; use ethers::{ - abi::{Abi, Function, ParamType, Token, Tokenizable}, - types::{Address, Bytes, I256, U256}, + abi::{Abi, Function}, + types::{Address, Bytes}, }; use revm::{db::DatabaseRef, Return}; +use strategies::fuzz_calldata; pub use proptest::test_runner::{Config as FuzzConfig, Reason}; -use proptest::{ - prelude::*, - test_runner::{TestError, TestRunner}, -}; +use proptest::test_runner::{TestError, TestRunner}; use serde::{Deserialize, Serialize}; use std::cell::RefCell; @@ -190,18 +188,22 @@ impl FuzzedCases { (self.cases.iter().map(|c| c.gas as u128).sum::() / self.cases.len() as u128) as u64 } + /// Returns the case with the highest gas usage pub fn highest(&self) -> Option<&FuzzCase> { self.cases.last() } + /// Returns the case with the lowest gas usage pub fn lowest(&self) -> Option<&FuzzCase> { self.cases.first() } + /// Returns the highest amount of gas spent on a fuzz case pub fn highest_gas(&self) -> u64 { self.highest().map(|c| c.gas).unwrap_or_default() } + /// Returns the lowest amount of gas spent on a fuzz case pub fn lowest_gas(&self) -> u64 { self.lowest().map(|c| c.gas).unwrap_or_default() } @@ -216,79 +218,6 @@ pub struct FuzzCase { pub gas: u64, } -/// Given a function, it returns a proptest strategy which generates valid abi-encoded calldata -/// for that function's input types. -pub fn fuzz_calldata(func: &Function) -> impl Strategy + '_ { - // We need to compose all the strategies generated for each parameter in all - // possible combinations - let strats = func.inputs.iter().map(|input| fuzz_param(&input.kind)).collect::>(); - - strats.prop_map(move |tokens| { - tracing::trace!(input = ?tokens); - func.encode_input(&tokens).unwrap().into() - }) -} - -/// The max length of arrays we fuzz for is 256. -const MAX_ARRAY_LEN: usize = 256; - -/// Given an ethabi parameter type, returns a proptest strategy for generating values for that -/// datatype. Works with ABI Encoder v2 tuples. -fn fuzz_param(param: &ParamType) -> impl Strategy { - match param { - ParamType::Address => { - // The key to making this work is the `boxed()` call which type erases everything - // https://altsysrq.github.io/proptest-book/proptest/tutorial/transforming-strategies.html - any::<[u8; 20]>().prop_map(|x| Address::from_slice(&x).into_token()).boxed() - } - ParamType::Bytes => any::>().prop_map(|x| Bytes::from(x).into_token()).boxed(), - // For ints and uints we sample from a U256, then wrap it to the correct size with a - // modulo operation. Note that this introduces modulo bias, but it can be removed with - // rejection sampling if it's determined the bias is too severe. Rejection sampling may - // slow down tests as it resamples bad values, so may want to benchmark the performance - // hit and weigh that against the current bias before implementing - ParamType::Int(n) => match n / 8 { - 32 => any::<[u8; 32]>() - .prop_map(move |x| I256::from_raw(U256::from(&x)).into_token()) - .boxed(), - y @ 1..=31 => any::<[u8; 32]>() - .prop_map(move |x| { - // Generate a uintN in the correct range, then shift it to the range of intN - // by subtracting 2^(N-1) - let uint = U256::from(&x) % U256::from(2).pow(U256::from(y * 8)); - let max_int_plus1 = U256::from(2).pow(U256::from(y * 8 - 1)); - let num = I256::from_raw(uint.overflowing_sub(max_int_plus1).0); - num.into_token() - }) - .boxed(), - _ => panic!("unsupported solidity type int{}", n), - }, - ParamType::Uint(n) => { - strategies::UintStrategy::new(*n, vec![]).prop_map(|x| x.into_token()).boxed() - } - ParamType::Bool => any::().prop_map(|x| x.into_token()).boxed(), - ParamType::String => any::>() - .prop_map(|x| Token::String(unsafe { std::str::from_utf8_unchecked(&x).to_string() })) - .boxed(), - ParamType::Array(param) => proptest::collection::vec(fuzz_param(param), 0..MAX_ARRAY_LEN) - .prop_map(Token::Array) - .boxed(), - ParamType::FixedBytes(size) => (0..*size as u64) - .map(|_| any::()) - .collect::>() - .prop_map(Token::FixedBytes) - .boxed(), - ParamType::FixedArray(param, size) => (0..*size as u64) - .map(|_| fuzz_param(param).prop_map(|param| param.into_token())) - .collect::>() - .prop_map(Token::FixedArray) - .boxed(), - ParamType::Tuple(params) => { - params.iter().map(fuzz_param).collect::>().prop_map(Token::Tuple).boxed() - } - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/forge/src/executor/fuzz/strategies/calldata.rs b/forge/src/executor/fuzz/strategies/calldata.rs new file mode 100644 index 0000000000000..f3c3321388092 --- /dev/null +++ b/forge/src/executor/fuzz/strategies/calldata.rs @@ -0,0 +1,16 @@ +use super::fuzz_param; +use ethers::{abi::Function, types::Bytes}; +use proptest::prelude::Strategy; + +/// Given a function, it returns a proptest strategy which generates valid abi-encoded calldata +/// for that function's input types. +pub fn fuzz_calldata(func: &Function) -> impl Strategy + '_ { + // We need to compose all the strategies generated for each parameter in all + // possible combinations + let strats = func.inputs.iter().map(|input| fuzz_param(&input.kind)).collect::>(); + + strats.prop_map(move |tokens| { + tracing::trace!(input = ?tokens); + func.encode_input(&tokens).unwrap().into() + }) +} diff --git a/forge/src/executor/fuzz/strategies/mod.rs b/forge/src/executor/fuzz/strategies/mod.rs new file mode 100644 index 0000000000000..10a4a3bb5578b --- /dev/null +++ b/forge/src/executor/fuzz/strategies/mod.rs @@ -0,0 +1,8 @@ +mod uint; +pub use uint::UintStrategy; + +mod param; +pub use param::fuzz_param; + +mod calldata; +pub use calldata::fuzz_calldata; diff --git a/forge/src/executor/fuzz/strategies/param.rs b/forge/src/executor/fuzz/strategies/param.rs new file mode 100644 index 0000000000000..cc4376c8045dc --- /dev/null +++ b/forge/src/executor/fuzz/strategies/param.rs @@ -0,0 +1,65 @@ +use ethers::{ + abi::{ParamType, Token, Tokenizable}, + types::{Address, Bytes, I256, U256}, +}; +use proptest::prelude::*; + +/// The max length of arrays we fuzz for is 256. +pub const MAX_ARRAY_LEN: usize = 256; + +/// Given an ethabi parameter type, returns a proptest strategy for generating values for that +/// datatype. Works with ABI Encoder v2 tuples. +pub fn fuzz_param(param: &ParamType) -> impl Strategy { + match param { + ParamType::Address => { + // The key to making this work is the `boxed()` call which type erases everything + // https://altsysrq.github.io/proptest-book/proptest/tutorial/transforming-strategies.html + any::<[u8; 20]>().prop_map(|x| Address::from_slice(&x).into_token()).boxed() + } + ParamType::Bytes => any::>().prop_map(|x| Bytes::from(x).into_token()).boxed(), + // For ints and uints we sample from a U256, then wrap it to the correct size with a + // modulo operation. Note that this introduces modulo bias, but it can be removed with + // rejection sampling if it's determined the bias is too severe. Rejection sampling may + // slow down tests as it resamples bad values, so may want to benchmark the performance + // hit and weigh that against the current bias before implementing + ParamType::Int(n) => match n / 8 { + 32 => any::<[u8; 32]>() + .prop_map(move |x| I256::from_raw(U256::from(&x)).into_token()) + .boxed(), + y @ 1..=31 => any::<[u8; 32]>() + .prop_map(move |x| { + // Generate a uintN in the correct range, then shift it to the range of intN + // by subtracting 2^(N-1) + let uint = U256::from(&x) % U256::from(2).pow(U256::from(y * 8)); + let max_int_plus1 = U256::from(2).pow(U256::from(y * 8 - 1)); + let num = I256::from_raw(uint.overflowing_sub(max_int_plus1).0); + num.into_token() + }) + .boxed(), + _ => panic!("unsupported solidity type int{}", n), + }, + ParamType::Uint(n) => { + super::UintStrategy::new(*n, vec![]).prop_map(|x| x.into_token()).boxed() + } + ParamType::Bool => any::().prop_map(|x| x.into_token()).boxed(), + ParamType::String => any::>() + .prop_map(|x| Token::String(unsafe { std::str::from_utf8_unchecked(&x).to_string() })) + .boxed(), + ParamType::Array(param) => proptest::collection::vec(fuzz_param(param), 0..MAX_ARRAY_LEN) + .prop_map(Token::Array) + .boxed(), + ParamType::FixedBytes(size) => (0..*size as u64) + .map(|_| any::()) + .collect::>() + .prop_map(Token::FixedBytes) + .boxed(), + ParamType::FixedArray(param, size) => (0..*size as u64) + .map(|_| fuzz_param(param).prop_map(|param| param.into_token())) + .collect::>() + .prop_map(Token::FixedArray) + .boxed(), + ParamType::Tuple(params) => { + params.iter().map(fuzz_param).collect::>().prop_map(Token::Tuple).boxed() + } + } +} diff --git a/forge/src/executor/fuzz/strategies.rs b/forge/src/executor/fuzz/strategies/uint.rs similarity index 100% rename from forge/src/executor/fuzz/strategies.rs rename to forge/src/executor/fuzz/strategies/uint.rs From 8b07a36702821792791bab041384d17b3eeffe5a Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Tue, 22 Feb 2022 21:27:11 +0100 Subject: [PATCH 07/17] chore: adjust docs --- forge/src/executor/fuzz/mod.rs | 7 ++----- forge/src/executor/fuzz/strategies/uint.rs | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/forge/src/executor/fuzz/mod.rs b/forge/src/executor/fuzz/mod.rs index 7e29416fa1ec8..bc431286b53e9 100644 --- a/forge/src/executor/fuzz/mod.rs +++ b/forge/src/executor/fuzz/mod.rs @@ -77,8 +77,6 @@ where return Err(TestCaseError::Reject(err.into())); }*/ - // We must check success before resetting the state, otherwise resetting the state - // will also reset the `failed` state variable back to false. let success = self.executor.is_success( address, status, @@ -106,9 +104,8 @@ where } ); - // push test case to the case set + // Push test case to the case set fuzz_cases.borrow_mut().push(FuzzCase { calldata, gas }); - Ok(()) }) .err() @@ -214,7 +211,7 @@ impl FuzzedCases { pub struct FuzzCase { /// The calldata used for this fuzz test pub calldata: Bytes, - // Consumed gas + /// Consumed gas pub gas: u64, } diff --git a/forge/src/executor/fuzz/strategies/uint.rs b/forge/src/executor/fuzz/strategies/uint.rs index 75c54aa77f327..eb8db24482622 100644 --- a/forge/src/executor/fuzz/strategies/uint.rs +++ b/forge/src/executor/fuzz/strategies/uint.rs @@ -7,7 +7,7 @@ use proptest::{ use ethers::types::U256; /// Value tree for unsigned ints (up to uint256). -/// This is very similar to proptest::BinarySearch +/// This is very similar to [proptest::BinarySearch] pub struct UintValueTree { /// Lower base lo: U256, From aec3aecf56da8c36c97e551034aa6e2c5e59caba Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Tue, 22 Feb 2022 21:28:46 +0100 Subject: [PATCH 08/17] chore: remove unused file --- forge/src/executor/fuzz/fuzzed_executor.rs | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 forge/src/executor/fuzz/fuzzed_executor.rs diff --git a/forge/src/executor/fuzz/fuzzed_executor.rs b/forge/src/executor/fuzz/fuzzed_executor.rs deleted file mode 100644 index e69de29bb2d1d..0000000000000 From e4272d165c9f792975510358a706ca64ac10ee9c Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Wed, 23 Feb 2022 11:25:41 +0100 Subject: [PATCH 09/17] fix: set deployer nonce to 1 See #446 --- forge/src/executor/mod.rs | 8 ++++++++ forge/src/runner.rs | 3 +++ 2 files changed, 11 insertions(+) diff --git a/forge/src/executor/mod.rs b/forge/src/executor/mod.rs index 88ac0355f530d..0f2409468041c 100644 --- a/forge/src/executor/mod.rs +++ b/forge/src/executor/mod.rs @@ -124,6 +124,14 @@ where self.db.insert_cache(address, account); } + /// Set the nonce of an account. + pub fn set_nonce(&mut self, address: Address, nonce: u64) { + let mut account = self.db.basic(address); + account.nonce = nonce; + + self.db.insert_cache(address, account); + } + /// Calls the `setUp()` function on a contract. pub fn setup( &mut self, diff --git a/forge/src/runner.rs b/forge/src/runner.rs index 9b116c0eef0ce..26fe5894f27f2 100644 --- a/forge/src/runner.rs +++ b/forge/src/runner.rs @@ -202,6 +202,9 @@ impl<'a, DB: DatabaseRef + Clone + Send + Sync> ContractRunner<'a, DB> { /// Deploys the test contract inside the runner from the sending account, and optionally runs /// the `setUp` function on the test contract. pub fn deploy(&mut self, setup: bool) -> Result<(Address, Vec, bool, Option)> { + // We set the nonce of the deployer account to 1 to get the same addresses as DappTools + self.executor.set_nonce(self.sender, 1); + // Deploy libraries self.predeploy_libs.iter().for_each(|code| { self.executor From 9dee317c72ec7a6cd11dbcd534ebb03072a78a86 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Wed, 23 Feb 2022 11:26:07 +0100 Subject: [PATCH 10/17] fix: only test contracts with matching test fns Since we now run the setup before tests start, we would deploy tests and run their setup function even if the contract had no test functions that matched the filter. This was problematic for the `MultiRunner` tests since one of the tests are supposed to fail. --- forge/src/multi_runner.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/forge/src/multi_runner.rs b/forge/src/multi_runner.rs index 52718b598cfa2..b854432681d19 100644 --- a/forge/src/multi_runner.rs +++ b/forge/src/multi_runner.rs @@ -182,6 +182,7 @@ impl MultiContractRunner { .contracts .par_iter() .filter(|(name, _)| filter.matches_contract(name)) + .filter(|(_, (abi, _, _))| abi.functions().any(|func| filter.matches_test(&func.name))) .map(|(name, (abi, deploy_code, libs))| { // TODO: Fork mode and "vicinity" let executor = ExecutorBuilder::new() @@ -262,7 +263,7 @@ mod tests { // The rest should pass _ => { assert_ne!(contract_tests.keys().len(), 0); - assert!(contract_tests.iter().all(|(k, result)| { result.success })) + assert!(contract_tests.iter().all(|(_, result)| { result.success })) } } } From c72792fdff32039d933ecd12db7e14054f30311b Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Wed, 23 Feb 2022 13:46:27 +0100 Subject: [PATCH 11/17] fix: deploy libs from zero address This ensures the test address matches DappTools provided `self.sender` is the same as DappTools If we deploy from `self.sender` then we have to set the nonce to 1 on every deployment. --- forge/src/runner.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/forge/src/runner.rs b/forge/src/runner.rs index 26fe5894f27f2..20cc252404c7f 100644 --- a/forge/src/runner.rs +++ b/forge/src/runner.rs @@ -202,13 +202,14 @@ impl<'a, DB: DatabaseRef + Clone + Send + Sync> ContractRunner<'a, DB> { /// Deploys the test contract inside the runner from the sending account, and optionally runs /// the `setUp` function on the test contract. pub fn deploy(&mut self, setup: bool) -> Result<(Address, Vec, bool, Option)> { - // We set the nonce of the deployer account to 1 to get the same addresses as DappTools + // We set the nonce of the deployer accounts to 1 to get the same addresses as DappTools self.executor.set_nonce(self.sender, 1); + self.executor.set_nonce(Address::zero(), 1); // Deploy libraries self.predeploy_libs.iter().for_each(|code| { self.executor - .deploy(self.sender, code.0.clone(), 0u32.into()) + .deploy(Address::zero(), code.0.clone(), 0u32.into()) .expect("couldn't deploy library"); }); From c2ac639e3b384fc3a56dd6d4503e27a660215703 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Wed, 23 Feb 2022 14:01:45 +0100 Subject: [PATCH 12/17] fix: actually use env config --- forge/src/multi_runner.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/forge/src/multi_runner.rs b/forge/src/multi_runner.rs index b854432681d19..72f33ff1c068b 100644 --- a/forge/src/multi_runner.rs +++ b/forge/src/multi_runner.rs @@ -187,6 +187,7 @@ impl MultiContractRunner { // TODO: Fork mode and "vicinity" let executor = ExecutorBuilder::new() .with_cheatcodes(self.evm_opts.ffi) + .with_config(self.evm_opts.env.evm_env()) .with_spec(self.evm_spec) .build(); let result = From 6c0a2cb54752dc03d16a27fbe86777b9dd8b4f9e Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Wed, 23 Feb 2022 14:24:27 +0100 Subject: [PATCH 13/17] fix: correctly apply env --- forge/src/executor/mod.rs | 19 ++++++++++--------- forge/src/executor/opts.rs | 6 +++++- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/forge/src/executor/mod.rs b/forge/src/executor/mod.rs index 0f2409468041c..de5b083daec7b 100644 --- a/forge/src/executor/mod.rs +++ b/forge/src/executor/mod.rs @@ -194,13 +194,14 @@ where value: U256, ) -> Result { let mut evm = EVM::new(); - evm.env = self.env.clone(); + evm.env.cfg = self.env.cfg.clone(); + evm.env.block = self.env.block.clone(); evm.env.tx = TxEnv { caller: from, transact_to: TransactTo::Call(to), data: calldata, value, - ..Default::default() + ..self.env.tx.clone() }; evm.database(&mut self.db); @@ -256,13 +257,14 @@ where value: U256, ) -> Result { let mut evm = EVM::new(); - evm.env = self.env.clone(); + evm.env.cfg = self.env.cfg.clone(); + evm.env.block = self.env.block.clone(); evm.env.tx = TxEnv { caller: from, transact_to: TransactTo::Call(to), data: calldata, value, - ..Default::default() + ..self.env.tx.clone() }; evm.database(&self.db); @@ -293,14 +295,14 @@ where value: U256, ) -> Result<(Address, Return, u64, Vec)> { let mut evm = EVM::new(); - - evm.env = self.env.clone(); + evm.env.cfg = self.env.cfg.clone(); + evm.env.block = self.env.block.clone(); evm.env.tx = TxEnv { caller: from, transact_to: TransactTo::Create(CreateScheme::Create), data: code, value, - ..Default::default() + ..self.env.tx.clone() }; evm.database(&mut self.db); @@ -310,8 +312,7 @@ where TransactOut::Create(_, Some(addr)) => addr, // TODO: We should have better error handling logic in the test runner // regarding deployments in general - TransactOut::Create(_, None) => eyre::bail!("deployment failed"), - _ => unreachable!(), + _ => eyre::bail!("deployment failed: {:?}", status), }; let state = Rc::try_unwrap(state).expect("no inspector should be alive").into_inner(); diff --git a/forge/src/executor/opts.rs b/forge/src/executor/opts.rs index d62d7e3d7737e..cf27cd0ef4f2f 100644 --- a/forge/src/executor/opts.rs +++ b/forge/src/executor/opts.rs @@ -79,7 +79,11 @@ impl Env { spec_id: SpecId::LONDON, perf_all_precompiles_have_balance: false, }, - tx: TxEnv { gas_price: self.gas_price.into(), ..Default::default() }, + tx: TxEnv { + gas_price: self.gas_price.into(), + gas_limit: self.block_gas_limit.unwrap_or(self.gas_limit), + ..Default::default() + }, } } } From bc71264727aa6b59d8b2974b088eed1118e17d39 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Wed, 23 Feb 2022 14:24:41 +0100 Subject: [PATCH 14/17] revert: deploy from zero address We just reset the nonce to 1 instead after we deploy the libraries, otherwise we'd have to fund the zero address as well. --- forge/src/runner.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/forge/src/runner.rs b/forge/src/runner.rs index 20cc252404c7f..103c79686bb30 100644 --- a/forge/src/runner.rs +++ b/forge/src/runner.rs @@ -202,10 +202,6 @@ impl<'a, DB: DatabaseRef + Clone + Send + Sync> ContractRunner<'a, DB> { /// Deploys the test contract inside the runner from the sending account, and optionally runs /// the `setUp` function on the test contract. pub fn deploy(&mut self, setup: bool) -> Result<(Address, Vec, bool, Option)> { - // We set the nonce of the deployer accounts to 1 to get the same addresses as DappTools - self.executor.set_nonce(self.sender, 1); - self.executor.set_nonce(Address::zero(), 1); - // Deploy libraries self.predeploy_libs.iter().for_each(|code| { self.executor @@ -213,6 +209,9 @@ impl<'a, DB: DatabaseRef + Clone + Send + Sync> ContractRunner<'a, DB> { .expect("couldn't deploy library"); }); + // We set the nonce of the deployer accounts to 1 to get the same addresses as DappTools + self.executor.set_nonce(self.sender, 1); + // Deploy an instance of the contract let (addr, _, _, mut logs) = self .executor From e2d0de89b89c6a254a061193dd3e3dd5e167a839 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Wed, 23 Feb 2022 14:41:41 +0100 Subject: [PATCH 15/17] refactor: add `build_env` helper fn --- forge/src/executor/mod.rs | 38 +++++++++++--------------------------- forge/src/runner.rs | 6 +++--- 2 files changed, 14 insertions(+), 30 deletions(-) diff --git a/forge/src/executor/mod.rs b/forge/src/executor/mod.rs index de5b083daec7b..52ae5d8a2678a 100644 --- a/forge/src/executor/mod.rs +++ b/forge/src/executor/mod.rs @@ -194,15 +194,7 @@ where value: U256, ) -> Result { let mut evm = EVM::new(); - evm.env.cfg = self.env.cfg.clone(); - evm.env.block = self.env.block.clone(); - evm.env.tx = TxEnv { - caller: from, - transact_to: TransactTo::Call(to), - data: calldata, - value, - ..self.env.tx.clone() - }; + evm.env = self.build_env(from, TransactTo::Call(to), calldata, value); evm.database(&mut self.db); // Run the call @@ -257,15 +249,7 @@ where value: U256, ) -> Result { let mut evm = EVM::new(); - evm.env.cfg = self.env.cfg.clone(); - evm.env.block = self.env.block.clone(); - evm.env.tx = TxEnv { - caller: from, - transact_to: TransactTo::Call(to), - data: calldata, - value, - ..self.env.tx.clone() - }; + evm.env = self.build_env(from, TransactTo::Call(to), calldata, value); evm.database(&self.db); // Run the call @@ -295,15 +279,7 @@ where value: U256, ) -> Result<(Address, Return, u64, Vec)> { let mut evm = EVM::new(); - evm.env.cfg = self.env.cfg.clone(); - evm.env.block = self.env.block.clone(); - evm.env.tx = TxEnv { - caller: from, - transact_to: TransactTo::Create(CreateScheme::Create), - data: code, - value, - ..self.env.tx.clone() - }; + evm.env = self.build_env(from, TransactTo::Create(CreateScheme::Create), code, value); evm.database(&mut self.db); let state = Rc::new(RefCell::new(ExecutorState::new())); @@ -353,4 +329,12 @@ where should_fail ^ success } + + fn build_env(&self, caller: Address, transact_to: TransactTo, data: Bytes, value: U256) -> Env { + Env { + cfg: self.env.cfg.clone(), + block: self.env.block.clone(), + tx: TxEnv { caller, transact_to, data, value, ..self.env.tx.clone() }, + } + } } diff --git a/forge/src/runner.rs b/forge/src/runner.rs index 103c79686bb30..754f82bf3a30c 100644 --- a/forge/src/runner.rs +++ b/forge/src/runner.rs @@ -202,6 +202,9 @@ impl<'a, DB: DatabaseRef + Clone + Send + Sync> ContractRunner<'a, DB> { /// Deploys the test contract inside the runner from the sending account, and optionally runs /// the `setUp` function on the test contract. pub fn deploy(&mut self, setup: bool) -> Result<(Address, Vec, bool, Option)> { + // We set the nonce of the deployer accounts to 1 to get the same addresses as DappTools + self.executor.set_nonce(self.sender, 1); + // Deploy libraries self.predeploy_libs.iter().for_each(|code| { self.executor @@ -209,9 +212,6 @@ impl<'a, DB: DatabaseRef + Clone + Send + Sync> ContractRunner<'a, DB> { .expect("couldn't deploy library"); }); - // We set the nonce of the deployer accounts to 1 to get the same addresses as DappTools - self.executor.set_nonce(self.sender, 1); - // Deploy an instance of the contract let (addr, _, _, mut logs) = self .executor From a46c7d7004839956adb7dd337831f6fb3ae67ff8 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Wed, 23 Feb 2022 15:02:46 +0100 Subject: [PATCH 16/17] refactor: get rid of `ExecutorState` --- forge/src/executor/inspector/logs.rs | 17 ++++++++--------- forge/src/executor/inspector/mod.rs | 14 -------------- forge/src/executor/mod.rs | 25 ++++++++++--------------- 3 files changed, 18 insertions(+), 38 deletions(-) diff --git a/forge/src/executor/inspector/logs.rs b/forge/src/executor/inspector/logs.rs index d0feb523a2674..eacf42fcc3078 100644 --- a/forge/src/executor/inspector/logs.rs +++ b/forge/src/executor/inspector/logs.rs @@ -1,4 +1,3 @@ -use super::ExecutorState; use crate::executor::{ patch_hardhat_console_selector, HardhatConsoleCalls, HARDHAT_CONSOLE_ADDRESS, }; @@ -11,21 +10,21 @@ use revm::{ db::Database, opcode, CallContext, CreateScheme, EVMData, Gas, Inspector, Machine, Return, Transfer, }; -use std::{cell::RefCell, rc::Rc}; /// An inspector that collects logs during execution. /// /// The inspector collects logs from the LOG opcodes as well as Hardhat-style logs. +#[derive(Debug)] pub struct LogCollector { - state: Rc>, + pub logs: Vec, } impl LogCollector { - pub fn new(state: Rc>) -> Self { - Self { state } + pub fn new() -> Self { + Self { logs: Vec::new() } } - fn log(&self, machine: &Machine, n: u8) { + fn log(&mut self, machine: &Machine, n: u8) { let (offset, len) = (try_or_return!(machine.stack().peek(0)), try_or_return!(machine.stack().peek(1))); let data = if len.is_zero() { @@ -42,10 +41,10 @@ impl LogCollector { topics.push(topic); } - self.state.borrow_mut().logs.push(RawLog { topics, data }); + self.logs.push(RawLog { topics, data }); } - fn hardhat_log(&self, input: Vec) -> (Return, Bytes) { + fn hardhat_log(&mut self, input: Vec) -> (Return, Bytes) { // Patch the Hardhat-style selectors let input = patch_hardhat_console_selector(input.to_vec()); let decoded = match HardhatConsoleCalls::decode(&input) { @@ -59,7 +58,7 @@ impl LogCollector { }; // Convert it to a DS-style `emit log(string)` event - self.state.borrow_mut().logs.push(convert_hh_log_to_event(decoded)); + self.logs.push(convert_hh_log_to_event(decoded)); (Return::Continue, Bytes::new()) } diff --git a/forge/src/executor/inspector/mod.rs b/forge/src/executor/inspector/mod.rs index 73c7b73cf4197..aeb0fa9c98787 100644 --- a/forge/src/executor/inspector/mod.rs +++ b/forge/src/executor/inspector/mod.rs @@ -3,17 +3,3 @@ mod macros; mod logs; pub use logs::LogCollector; - -use ethers::abi::RawLog; - -// TODO: Move -#[derive(Default, Debug)] -pub struct ExecutorState { - pub logs: Vec, -} - -impl ExecutorState { - pub fn new() -> Self { - Self::default() - } -} diff --git a/forge/src/executor/mod.rs b/forge/src/executor/mod.rs index 52ae5d8a2678a..36f4102ae2fd3 100644 --- a/forge/src/executor/mod.rs +++ b/forge/src/executor/mod.rs @@ -29,12 +29,11 @@ use ethers::{ use eyre::Result; use foundry_utils::IntoFunction; use hashbrown::HashMap; -use inspector::{ExecutorState, LogCollector}; +use inspector::LogCollector; use revm::{ db::{CacheDB, DatabaseCommit, DatabaseRef, EmptyDB}, return_ok, Account, CreateScheme, Env, Return, TransactOut, TransactTo, TxEnv, EVM, }; -use std::{cell::RefCell, rc::Rc}; #[derive(thiserror::Error, Debug)] pub enum EvmError { @@ -198,15 +197,14 @@ where evm.database(&mut self.db); // Run the call - let state = Rc::new(RefCell::new(ExecutorState::new())); - let (status, out, gas, _) = evm.inspect_commit(LogCollector::new(state.clone())); + let mut inspector = LogCollector::new(); + let (status, out, gas, _) = evm.inspect_commit(&mut inspector); let result = match out { TransactOut::Call(data) => data, _ => Bytes::default(), }; - let state = Rc::try_unwrap(state).expect("no inspector should be alive").into_inner(); - Ok(RawCallResult { status, result, gas, logs: state.logs, state_changeset: None }) + Ok(RawCallResult { status, result, gas, logs: inspector.logs, state_changeset: None }) } /// Performs a call to an account on the current state of the VM. @@ -253,20 +251,18 @@ where evm.database(&self.db); // Run the call - let state = Rc::new(RefCell::new(ExecutorState::new())); - let (status, out, gas, state_changeset, _) = - evm.inspect_ref(LogCollector::new(state.clone())); + let mut inspector = LogCollector::new(); + let (status, out, gas, state_changeset, _) = evm.inspect_ref(&mut inspector); let result = match out { TransactOut::Call(data) => data, _ => Bytes::default(), }; - let state = Rc::try_unwrap(state).expect("no inspector should be alive").into_inner(); Ok(RawCallResult { status, result, gas, - logs: state.logs, + logs: inspector.logs, state_changeset: Some(state_changeset), }) } @@ -282,17 +278,16 @@ where evm.env = self.build_env(from, TransactTo::Create(CreateScheme::Create), code, value); evm.database(&mut self.db); - let state = Rc::new(RefCell::new(ExecutorState::new())); - let (status, out, gas, _) = evm.inspect_commit(LogCollector::new(state.clone())); + let mut inspector = LogCollector::new(); + let (status, out, gas, _) = evm.inspect_commit(&mut inspector); let addr = match out { TransactOut::Create(_, Some(addr)) => addr, // TODO: We should have better error handling logic in the test runner // regarding deployments in general _ => eyre::bail!("deployment failed: {:?}", status), }; - let state = Rc::try_unwrap(state).expect("no inspector should be alive").into_inner(); - Ok((addr, status, gas, state.logs)) + Ok((addr, status, gas, inspector.logs)) } /// Check if a call to a test contract was successful From 9ce0c08c955616f7b0402fa602d75483c87e1780 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Wed, 23 Feb 2022 16:29:39 +0100 Subject: [PATCH 17/17] chore: clippy --- forge/src/executor/inspector/logs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge/src/executor/inspector/logs.rs b/forge/src/executor/inspector/logs.rs index eacf42fcc3078..746bc824ed5e3 100644 --- a/forge/src/executor/inspector/logs.rs +++ b/forge/src/executor/inspector/logs.rs @@ -14,7 +14,7 @@ use revm::{ /// An inspector that collects logs during execution. /// /// The inspector collects logs from the LOG opcodes as well as Hardhat-style logs. -#[derive(Debug)] +#[derive(Debug, Default)] pub struct LogCollector { pub logs: Vec, }