Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 11 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,14 @@ Foundry consists of:

![demo](./assets/demo.svg)

## Forge Quickstart

Forge is a development and testing framework for Ethereum applications.
## Forge

```
cargo install forge
forge init
forge build
forge test
cargo install --git https://github.com/gakonst/foundry --bin forge
```

More documentation can be found in the [forge package](./forge/README.md).
More documentation can be found in the [forge package](./forge/README.md) and in
the [CLI README](./cli/README.md).

### Features

Expand Down Expand Up @@ -73,14 +69,15 @@ It also works with "non-standard" directory structures (i.e. contracts not in
[`openzeppelin-contracts`](https://github.com/OpenZeppelin/openzeppelin-contracts),
Hardhat compilation took 15.244s, whereas Forge took 9.449 (~4s cached)

## Cast Quickstart
## Cast

Cast is a swiss army knife for interacting with Ethereum applications from the
command line.

```
cargo install cast
cast call 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 "totalSupply()" --rpc-url $ETH_RPC_URL
cargo install --git https://github.com/gakonst/foundry --bin cast
// Get USDC's total supply
cast call 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 "totalSupply()(uint256)" --rpc-url <..your node url>
```

More documentation can be found in the [cast package](./cast/README.md).
Expand All @@ -102,8 +99,6 @@ This repository contains several Rust crates:
- [`utils`](utils): Utilities for parsing ABI data, will eventually be
upstreamed to [ethers-rs](https://github.com/gakonst/ethers-rs/).

The minimum supported rust version is 1.51.

### Rust Toolchain

We use the stable Rust toolchain. Install by running:
Expand All @@ -124,9 +119,11 @@ cargo doc --open

### Formatting

We use the nightly toolchain for formatting and linting.

```
cargo +nightly fmt
cargo clippy
cargo +nightly clippy --all-features -- -D warnings
```

## Getting Help
Expand Down
19 changes: 10 additions & 9 deletions evm-adapters/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ using the [`proptest`](https://docs.rs/proptest) crate.
## Sputnik's Hooked Executor

In order to implement cheatcodes, we had to hook in EVM execution. This was done
by implementing a `Handler` and overriding the `call` function.
by implementing a `Handler` and overriding the `call` function, in the
[`CheatcodeHandler`](crate::sputnik::cheatcodes::CheatcodeHandler)

## Sputnik's Cached Forking backend

Expand All @@ -19,11 +20,11 @@ e.g. Ethereum mainnet instead of redeploying the contracts locally yourself.

To assist with that, we provide 2 forking providers:

1. ForkMemoryBackend: A simple provider which calls out to the remote node for
any data that it does not have locally, and caching the result to avoid
unnecessary extra requests
1. SharedBackend: A backend which can be cheaply cloned and used in different
tests, typically useful for test parallelization. Under the hood, it has a
background worker which deduplicates any outgoing requests from each
individual backend, while also sharing the return values and cache. This
backend not in-use yet.
1. [`ForkMemoryBackend`](crate::sputnik::ForkMemoryBackend): A simple provider
which calls out to the remote node for any data that it does not have
locally, and caching the result to avoid unnecessary extra requests
1. [`SharedBackend`](crate::sputnik::cache::SharedBackend): A backend which can
be cheaply cloned and used in different tests, typically useful for test
parallelization. Under the hood, it has a background worker which
deduplicates any outgoing requests from each individual backend, while also
sharing the return values and cache. This backend not in-use yet.
9 changes: 9 additions & 0 deletions evm-adapters/src/blocking_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,21 @@ impl<M: Middleware> BlockingProvider<M>
where
M::Error: 'static,
{
/// Constructs the provider. If no tokio runtime exists, it instantiates one as well.
pub fn new(provider: M) -> Self {
let runtime = Handle::try_current().is_err().then(|| Runtime::new().unwrap());
Self { provider, runtime }
}

/// Receives a future and runs it to completion.
fn block_on<F: std::future::Future>(&self, f: F) -> F::Output {
match &self.runtime {
Some(runtime) => runtime.block_on(f),
None => futures::executor::block_on(f),
}
}

/// Gets the specified block as well as the chain id concurrently.
pub fn block_and_chainid(
&self,
block_id: Option<impl Into<BlockId>>,
Expand All @@ -52,6 +55,7 @@ where
Ok((block.ok_or_else(|| eyre::eyre!("block {:?} not found", block_id))?, chain_id))
}

/// Gets the nonce, balance and code associated with an account.
pub fn get_account(
&self,
address: Address,
Expand All @@ -68,14 +72,17 @@ where
Ok((nonce, balance, code))
}

/// Gets the current block number.
pub fn get_block_number(&self) -> Result<U64, M::Error> {
self.block_on(self.provider.get_block_number())
}

/// Gets the account's balance at the specified block.
pub fn get_balance(&self, address: Address, block: Option<BlockId>) -> Result<U256, M::Error> {
self.block_on(self.provider.get_balance(address, block))
}

/// Gets the account's nonce at the specified block.
pub fn get_transaction_count(
&self,
address: Address,
Expand All @@ -84,10 +91,12 @@ where
self.block_on(self.provider.get_transaction_count(address, block))
}

/// Gets the account's code at the specified block.
pub fn get_code(&self, address: Address, block: Option<BlockId>) -> Result<Bytes, M::Error> {
self.block_on(self.provider.get_code(address, block))
}

/// Gets the value at the specified storage slot & block.
pub fn get_storage_at(
&self,
address: Address,
Expand Down
1 change: 1 addition & 0 deletions evm-adapters/src/evmodin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use eyre::Result;

// TODO: Check if we can implement this as the base layer of an ethers-provider
// Middleware stack instead of doing RPC calls.
/// Wrapper around EVModin which implements the [Evm](`crate::Evm`) trait
#[derive(Clone, Debug)]
pub struct EvmOdin<S, T> {
pub host: S,
Expand Down
11 changes: 11 additions & 0 deletions evm-adapters/src/fuzz.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//! Fuzzing support abstracted over the [`Evm`](crate::Evm) used
use crate::Evm;
use ethers::{
abi::{Function, ParamType, Token, Tokenizable},
Expand All @@ -15,6 +16,11 @@ use proptest::{

pub use proptest::test_runner::Config as FuzzConfig;

/// Wrapper around any [`Evm`](crate::Evm) implementor 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 overriden via [environment variables](https://docs.rs/proptest/1.0.0/proptest/test_runner/struct.Config.html)
#[derive(Debug)]
pub struct FuzzedExecutor<'a, E, S> {
evm: RefCell<&'a mut E>,
Expand All @@ -24,6 +30,7 @@ pub struct FuzzedExecutor<'a, E, S> {
}

impl<'a, S, E: Evm<S>> FuzzedExecutor<'a, E, S> {
/// Returns a mutable reference to the fuzzer's internal EVM instance
pub fn as_mut(&self) -> RefMut<'_, &'a mut E> {
self.evm.borrow_mut()
}
Expand Down Expand Up @@ -83,6 +90,8 @@ impl<'a, S, E: Evm<S>> FuzzedExecutor<'a, E, S> {
}
}

/// 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<Value = Bytes> + '_ {
// We need to compose all the strategies generated for each parameter in all
// possible combinations
Expand All @@ -97,6 +106,8 @@ pub fn fuzz_calldata(func: &Function) -> impl Strategy<Value = Bytes> + '_ {
/// 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<Value = Token> {
match param {
ParamType::Address => {
Expand Down
13 changes: 10 additions & 3 deletions evm-adapters/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#![doc = include_str!("../README.md")]
#[cfg(feature = "sputnik")]
/// Abstraction over [Sputnik EVM](https://github.com/rust-blockchain/evm)
pub mod sputnik;
Expand All @@ -22,19 +23,23 @@ use foundry_utils::IntoFunction;
use eyre::Result;
use once_cell::sync::Lazy;

// The account that we use to fund all the deployed contracts
/// The account that we use to fund all the deployed contracts
pub static FAUCET_ACCOUNT: Lazy<Address> =
Lazy::new(|| Address::from_slice(&ethers::utils::keccak256("turbodapp faucet")[12..]));

/// Errors related to the EVM call execution
#[derive(thiserror::Error, Debug)]
pub enum EvmError {
#[error(transparent)]
Eyre(#[from] eyre::Error),
#[error("Execution reverted: {reason}, (gas: {gas_used})")]
// TODO: Add proper log printing.
/// Error which occured during execution of an EVM transaction
Execution { reason: String, gas_used: u64, logs: Vec<String> },
#[error(transparent)]
/// Error which occured during ABI encoding / decoding of data
AbiError(#[from] ethers::contract::AbiError),
#[error(transparent)]
/// Any other generic error
Eyre(#[from] eyre::Error),
}

// TODO: Any reason this should be an async trait?
Expand Down Expand Up @@ -64,6 +69,8 @@ pub trait Evm<State> {
/// Resets the EVM's state to the provided value
fn reset(&mut self, state: State);

/// Performs a [`call_unchecked`](Self::call_unchecked), checks if execution reverted, and
/// proceeds to return the decoded response to the user.
fn call<D: Detokenize, T: Tokenize, F: IntoFunction>(
&mut self,
from: Address,
Expand Down
1 change: 1 addition & 0 deletions evm-adapters/src/sputnik/cheatcodes/backend.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//! Cheatcode-enabled backend implementation
use super::Cheatcodes;
use ethers::types::{H160, H256, U256};
use sputnik::backend::{Backend, Basic};
Expand Down
19 changes: 18 additions & 1 deletion evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//! Hooks to EVM execution
use super::{
backend::CheatcodeBackend, memory_stackstate_owned::MemoryStackStateOwned, HEVMCalls,
HevmConsoleEvents,
Expand Down Expand Up @@ -31,10 +32,20 @@ use once_cell::sync::Lazy;

// This is now getting us the right hash? Also tried [..20]
// Lazy::new(|| Address::from_slice(&keccak256("hevm cheat code")[12..]));
/// Address where the Vm cheatcodes contract lives
pub static CHEATCODE_ADDRESS: Lazy<Address> = Lazy::new(|| {
Address::from_slice(&hex::decode("7109709ECfa91a80626fF3989D68f67F5b1DD12D").unwrap())
});

/// Hooks on live EVM execution and forwards everything else to a Sputnik [`Handler`].
///
/// It allows:
/// 1. Logging of values for debugging
/// 2. Modifying chain state live with cheatcodes
///
/// The `call_inner` and `create_inner` functions are copy-pasted from upstream, so that
/// it can hook in the runtime. They may eventually be removed if Sputnik allows bringing in your
/// own runtime handler.
#[derive(Clone, Debug)]
// TODO: Should this be called `HookedHandler`? Maybe we could implement other hooks
// here, e.g. hardhat console.log-style, or dapptools logs, some ad-hoc method for tracing
Expand Down Expand Up @@ -186,14 +197,18 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> SputnikExecutor<CheatcodeStackState<'
}
}

/// A [`MemoryStackStateOwned`] state instantiated over a [`CheatcodeBackend`]
pub type CheatcodeStackState<'a, B> = MemoryStackStateOwned<'a, CheatcodeBackend<B>>;

/// A [`CheatcodeHandler`] which uses a [`CheatcodeStackState`] to store its state and a
/// [`StackExecutor`] for executing transactions.
pub type CheatcodeStackExecutor<'a, 'b, B, P> =
CheatcodeHandler<StackExecutor<'a, 'b, CheatcodeStackState<'a, B>, P>>;

impl<'a, 'b, B: Backend, P: PrecompileSet>
Executor<CheatcodeStackState<'a, B>, CheatcodeStackExecutor<'a, 'b, B, P>>
{
/// Instantiates a cheatcode-enabled [`Executor`]
pub fn new_with_cheatcodes(
backend: B,
gas_limit: u64,
Expand Down Expand Up @@ -223,6 +238,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet>
}
}

// helper for creating an exit type
fn evm_error(retdata: &str) -> Capture<(ExitReason, Vec<u8>), Infallible> {
Capture::Exit((
ExitReason::Revert(ExitRevert::Reverted),
Expand All @@ -231,7 +247,8 @@ fn evm_error(retdata: &str) -> Capture<(ExitReason, Vec<u8>), Infallible> {
}

impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> {
/// Decodes the provided calldata as a
/// Given a transaction's calldata, it tries to parse it as an [`HEVM cheatcode`](super::HEVM)
/// call and modify the state accordingly.
fn apply_cheatcode(
&mut self,
input: Vec<u8>,
Expand Down
8 changes: 8 additions & 0 deletions evm-adapters/src/sputnik/cheatcodes/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Hooks over Sputnik EVM execution which allow runtime logging and modification of chain state
//! from Solidity (cheatcodes).
pub mod memory_stackstate_owned;

pub mod cheatcode_handler;
Expand All @@ -14,12 +16,18 @@ use sputnik::backend::{Backend, MemoryAccount, MemoryBackend};
/// Cheatcodes can be used to control the EVM context during setup or runtime,
/// which can be useful for simulations or specialized unit tests
pub struct Cheatcodes {
/// The overriden block number
pub block_number: Option<U256>,
/// The overriden timestamp
pub block_timestamp: Option<U256>,
/// The overriden basefee
pub block_base_fee_per_gas: Option<U256>,
/// The overriden storage slots
pub accounts: HashMap<Address, MemoryAccount>,
}

/// Extension trait over [`Backend`] which provides additional methods for interacting with the
/// state
pub trait BackendExt: Backend {
fn set_storage(&mut self, address: Address, slot: H256, value: H256);
}
Expand Down
2 changes: 2 additions & 0 deletions evm-adapters/src/sputnik/evm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ pub type MemoryState = BTreeMap<Address, MemoryAccount>;

// TODO: Check if we can implement this as the base layer of an ethers-provider
// Middleware stack instead of doing RPC calls.
/// Wrapper around Sputnik Executors which implements the [`Evm`] trait.
pub struct Executor<S, E> {
pub executor: E,
pub gas_limit: u64,
marker: PhantomData<S>,
}

impl<S, E> Executor<S, E> {
/// Instantiates the executor given a Sputnik instance.
pub fn from_executor(executor: E, gas_limit: u64) -> Self {
Self { executor, gas_limit, marker: PhantomData }
}
Expand Down
1 change: 1 addition & 0 deletions evm-adapters/src/sputnik/forked_backend/cache.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//! Smart caching and deduplication of requests when using a forking provider
use sputnik::backend::{Backend, Basic, MemoryAccount, MemoryVicinity};

use ethers::{
Expand Down
1 change: 1 addition & 0 deletions evm-adapters/src/sputnik/forked_backend/rpc.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//! Simple in-memory cache backend for use with forking providers
use std::{cell::RefCell, collections::BTreeMap};

use ethers::{
Expand Down
5 changes: 5 additions & 0 deletions evm-adapters/src/sputnik/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ use sputnik::{
pub use sputnik as sputnik_evm;
use sputnik_evm::executor::stack::PrecompileSet;

/// Given an ethers provider and a block, it proceeds to construct a [`MemoryVicinity`] from
/// the live chain data returned by the provider.
pub async fn vicinity<M: Middleware>(
provider: &M,
pin_block: Option<u64>,
Expand Down Expand Up @@ -148,6 +150,7 @@ use std::borrow::Cow;
type PrecompileFn =
fn(&[u8], Option<u64>, &sputnik::Context, bool) -> Result<PrecompileOutput, PrecompileFailure>;

/// Precompiled contracts which should be provided when instantiating the EVM.
pub static PRECOMPILES: Lazy<revm_precompiles::Precompiles> = Lazy::new(|| {
// We use the const to immediately choose the latest revision of available
// precompiles. Is this wrong maybe?
Expand Down Expand Up @@ -176,6 +179,7 @@ macro_rules! precompile_entry {
use once_cell::sync::Lazy;
use sputnik::Context;
use std::collections::BTreeMap;
/// Map of Address => [Precompile](PRECOMPILES) contracts
pub static PRECOMPILES_MAP: Lazy<BTreeMap<Address, PrecompileFn>> = Lazy::new(|| {
let mut map = BTreeMap::new();
precompile_entry!(map, 1);
Expand All @@ -190,6 +194,7 @@ pub static PRECOMPILES_MAP: Lazy<BTreeMap<Address, PrecompileFn>> = Lazy::new(||
map
});

/// Runs the provided precompile against the input data.
pub fn exec(
builtin: &revm_precompiles::Precompile,
input: &[u8],
Expand Down