Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: fuzzing #44

Merged
merged 16 commits into from
Sep 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
89 changes: 84 additions & 5 deletions Cargo.lock

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

6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,9 @@ opt-level = "z"
lto = true
codegen-units = 1
panic = "abort"

[patch."https://github.com/rust-blockchain/evm"]
evm = { git = "https://github.com/gakonst/evm", branch = "feat/clone-debug" }

[patch."https://github.com/vorot93/evmodin"]
evmodin = { git = "https://github.com/gakonst/evmodin", branch = "feat/clone-debug" }
3 changes: 2 additions & 1 deletion dapp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ glob = "0.3.0"
tokio = { version = "1.10.1" }
tracing = "0.1.26"
tracing-subscriber = "0.2.20"
proptest = "1.0.0"

[dev-dependencies]
evm-adapters = { path = "./../evm-adapters", features = ["sputnik", "sputnik-helpers", "evmodin", "evmodin-helpers"] }
evmodin = { git = "https://github.com/vorot93/evmodin", features = ["util"] }
# evm = { version = "0.30.1" }
evm = { git = "https://github.com/gakonst/evm" }
evm = { git = "https://github.com/rust-blockchain/evm" }
9 changes: 9 additions & 0 deletions dapp/GreetTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ contract GreeterTest is GreeterTestSetup {
greeter.greet(greeting);
}

function testFuzzing(string memory myGreeting) public {
greeter.greet(myGreeting);
require(keccak256(abi.encodePacked(greeter.greeting())) == keccak256(abi.encodePacked(myGreeting)), "not equal");
}

function testFuzzShrinking(uint256 x, uint256 y) public {
require(x * y <= 100, "product greater than 100");
}

// check the positive case
function testGreeting() public {
greeter.greet("yo");
Expand Down
37 changes: 37 additions & 0 deletions dapp/src/fuzz.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use ethers::{
abi::{Function, ParamType, Token, Tokenizable},
types::{Address, Bytes, U256},
};

use proptest::prelude::*;

pub fn fuzz_calldata(func: &Function) -> impl Strategy<Value = Bytes> + '_ {
// 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::<Vec<_>>();

strats.prop_map(move |tokens| func.encode_input(&tokens).unwrap().into())
}

fn fuzz_param(param: &ParamType) -> impl Strategy<Value = Token> {
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::Uint(n) => match n / 8 {
1 => any::<u8>().prop_map(|x| x.into_token()).boxed(),
2 => any::<u16>().prop_map(|x| x.into_token()).boxed(),
3..=4 => any::<u32>().prop_map(|x| x.into_token()).boxed(),
5..=8 => any::<u64>().prop_map(|x| x.into_token()).boxed(),
9..=16 => any::<u128>().prop_map(|x| x.into_token()).boxed(),
17..=32 => any::<[u8; 32]>().prop_map(|x| U256::from(&x).into_token()).boxed(),
_ => panic!("unsupported solidity type uint{}", n),
},
ParamType::String => any::<String>().prop_map(|x| x.into_token()).boxed(),
ParamType::Bytes => any::<Vec<u8>>().prop_map(|x| Bytes::from(x).into_token()).boxed(),
// TODO: Implement the rest of the strategies
_ => unimplemented!(),
}
}
2 changes: 2 additions & 0 deletions dapp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ pub use runner::{ContractRunner, TestResult};
mod multi_runner;
pub use multi_runner::{MultiContractRunner, MultiContractRunnerBuilder};

mod fuzz;

use ethers::abi;
use eyre::Result;

Expand Down
27 changes: 21 additions & 6 deletions dapp/src/multi_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use ethers::{
utils::{keccak256, CompiledContract},
};

use proptest::test_runner::TestRunner;
use regex::Regex;

use eyre::Result;
Expand All @@ -24,12 +25,14 @@ pub struct MultiContractRunnerBuilder<'a> {
/// The path for the output file
pub out_path: PathBuf,
pub no_compile: bool,
/// The fuzzer to be used for running fuzz tests
pub fuzzer: Option<TestRunner>,
}

impl<'a> MultiContractRunnerBuilder<'a> {
/// Given an EVM, proceeds to return a runner which is able to execute all tests
/// against that evm
pub fn build<E, S>(&self, mut evm: E) -> Result<MultiContractRunner<E, S>>
pub fn build<E, S>(self, mut evm: E) -> Result<MultiContractRunner<E, S>>
where
E: Evm<S>,
{
Expand All @@ -52,14 +55,25 @@ impl<'a> MultiContractRunnerBuilder<'a> {
});
evm.initialize_contracts(init_state);

Ok(MultiContractRunner { contracts, addresses, evm, state: PhantomData })
Ok(MultiContractRunner {
contracts,
addresses,
evm,
state: PhantomData,
fuzzer: self.fuzzer,
})
}

pub fn contracts(mut self, contracts: &'a str) -> Self {
self.contracts = contracts;
self
}

pub fn fuzzer(mut self, fuzzer: TestRunner) -> Self {
self.fuzzer = Some(fuzzer);
self
}

pub fn remappings(mut self, remappings: &'a [String]) -> Self {
self.remappings = remappings;
self
Expand Down Expand Up @@ -88,12 +102,13 @@ pub struct MultiContractRunner<E, S> {
addresses: HashMap<String, Address>,
/// The EVM instance used in the test runner
evm: E,
fuzzer: Option<TestRunner>,
state: PhantomData<S>,
}

impl<E, S> MultiContractRunner<E, S>
where
E: Evm<S>,
E: Evm<S> + Clone,
{
pub fn test(&mut self, pattern: Regex) -> Result<HashMap<String, HashMap<String, TestResult>>> {
// NB: We also have access to the contract's abi. When running the test.
Expand Down Expand Up @@ -143,15 +158,15 @@ where
pattern: &Regex,
) -> Result<HashMap<String, TestResult>> {
let mut runner = ContractRunner::new(&mut self.evm, contract, address);
runner.run_tests(pattern)
runner.run_tests(pattern, self.fuzzer.as_mut())
}
}

#[cfg(test)]
mod tests {
use super::*;

fn test_multi_runner<S, E: Evm<S>>(evm: E) {
fn test_multi_runner<S, E: Clone + Evm<S>>(evm: E) {
let mut runner =
MultiContractRunnerBuilder::default().contracts("./GreetTest.sol").build(evm).unwrap();

Expand All @@ -172,7 +187,7 @@ mod tests {
assert_eq!(only_gm["GmTest"].len(), 1);
}

fn test_ds_test_fail<S, E: Evm<S>>(evm: E) {
fn test_ds_test_fail<S, E: Clone + Evm<S>>(evm: E) {
let mut runner =
MultiContractRunnerBuilder::default().contracts("./../FooTest.sol").build(evm).unwrap();
let results = runner.test(Regex::new(".*").unwrap()).unwrap();
Expand Down