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", 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/fuzz/mod.rs b/forge/src/executor/fuzz/mod.rs new file mode 100644 index 0000000000000..bc431286b53e9 --- /dev/null +++ b/forge/src/executor/fuzz/mod.rs @@ -0,0 +1,241 @@ +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}, + types::{Address, Bytes}, +}; +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 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())); + }*/ + + 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 + } + + /// 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() + } +} + +/// 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, +} + +#[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/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/uint.rs b/forge/src/executor/fuzz/strategies/uint.rs new file mode 100644 index 0000000000000..eb8db24482622 --- /dev/null +++ b/forge/src/executor/fuzz/strategies/uint.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/inspector/logs.rs b/forge/src/executor/inspector/logs.rs index d0feb523a2674..746bc824ed5e3 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, Default)] 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 f12eede50c5f7..36f4102ae2fd3 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; @@ -19,6 +15,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; @@ -30,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::{DatabaseCommit, DatabaseRef, EmptyDB}, + 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 { @@ -125,6 +123,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, @@ -187,26 +193,18 @@ where value: U256, ) -> Result { let mut evm = EVM::new(); - evm.env = self.env.clone(); - evm.env.tx = TxEnv { - caller: from, - transact_to: TransactTo::Call(to), - data: calldata, - value, - ..Default::default() - }; + evm.env = self.build_env(from, TransactTo::Call(to), calldata, value); 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. @@ -249,31 +247,22 @@ where value: U256, ) -> Result { let mut evm = EVM::new(); - evm.env = self.env.clone(); - evm.env.tx = TxEnv { - caller: from, - transact_to: TransactTo::Call(to), - data: calldata, - value, - ..Default::default() - }; + evm.env = self.build_env(from, TransactTo::Call(to), calldata, value); 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), }) } @@ -286,29 +275,19 @@ where value: U256, ) -> Result<(Address, Return, u64, Vec)> { let mut evm = EVM::new(); - - evm.env = self.env.clone(); - evm.env.tx = TxEnv { - caller: from, - transact_to: TransactTo::Create(CreateScheme::Create), - data: code, - value, - ..Default::default() - }; + 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 - 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(); - Ok((addr, status, gas, state.logs)) + Ok((addr, status, gas, inspector.logs)) } /// Check if a call to a test contract was successful @@ -345,4 +324,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/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() + }, } } } 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/multi_runner.rs b/forge/src/multi_runner.rs index e4f8aa8bb9202..72f33ff1c068b 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()?; @@ -183,10 +182,12 @@ 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() .with_cheatcodes(self.evm_opts.ffi) + .with_config(self.evm_opts.env.evm_env()) .with_spec(self.evm_spec) .build(); let result = @@ -234,45 +235,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(|(_, result)| { result.success })) + } } } diff --git a/forge/src/runner.rs b/forge/src/runner.rs index 9c71bb55e0383..754f82bf3a30c 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(), + }, } } } @@ -197,10 +202,13 @@ 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 - .deploy(self.sender, code.0.clone(), 0u32.into()) + .deploy(Address::zero(), code.0.clone(), 0u32.into()) .expect("couldn't deploy library"); }); @@ -311,10 +319,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 +357,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 +387,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(), }) @@ -400,10 +457,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; @@ -423,8 +479,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"); @@ -465,15 +520,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] @@ -481,13 +537,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 @@ -503,7 +562,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 @@ -512,15 +575,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)); } } 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); + } +}