-_Rust port of DappTools_
-
-![Github Actions](https://github.com/gakonst/dapptools-rs/workflows/Tests/badge.svg)
-[![Telegram Chat](https://img.shields.io/endpoint?color=neon&style=flat-square&url=https%3A%2F%2Ftg.sumanjay.workers.dev%2Fturbodapptools)](https://t.me/turbodapptools)
+![Github Actions](https://github.com/gakonst/foundry/workflows/Tests/badge.svg)
+[![Telegram Chat](https://img.shields.io/endpoint?color=neon&style=flat-square&url=https%3A%2F%2Ftg.sumanjay.workers.dev%2Ffoundry_rs)](https://t.me/foundry_rs)
[![Crates.io][crates-badge]][crates-url]
-[crates-badge]: https://img.shields.io/crates/v/turbodapp.svg
-[crates-url]: https://crates.io/crates/turbodapp
+[crates-badge]: https://img.shields.io/crates/v/foundry.svg
+[crates-url]: https://crates.io/crates/foundry-rs
+
+**Foundry is a blazing fast, portable and modular toolkit for Ethereum
+application development written in Rust.**
+
+Foundry consists of:
+
+- [**Forge**](./forge): Ethereum testing framework (like Truffle, Hardhat and
+ Dapptools).
+- [**Cast**](./cast): Swiss army knife for interacting with EVM smart contracts,
+ sending transactions and getting chain data.
-## Installing
+![demo](./assets/demo.svg)
-We have not published a release yet. Until we do, please use the command below.
-Because our dependencies may not be stable, do not forget the `--locked`
-parameter, which will force the installer to use the checked in `Cargo.lock`
-file.
+## Forge Quickstart
+
+Forge is a development and testing framework for Ethereum applications.
```
-cargo install --git https://github.com/gakonst/dapptools-rs --locked
+cargo install forge
+forge init
+forge build
+forge test
```
-Alternatively, clone the repository and run: `cargo build --release`
-
-## Why?! DappTools is great!
-
-Developer experience is the #1 thing we should be optimizing for in development.
-Tests MUST be fast, non-trivial tests (e.g. proptests) MUST be easy to write,
-and compilation MUST be fast.
-
-Before getting into technical reasons, my simple answer is: rewriting software
-in Rust is fun. I enjoy it, and that could be the end of the "why" section.
-
-DappTools is REALLY great.
-[You should try it](https://github.com/dapphub/dapptools/), especially the
-symbolic execution and step debugger features.
-
-But it has some shortcomings:
-
-It's written in a mix of Bash, Javascript and Haskell. In my opinion, this makes
-it hard to contribute, you don't have a "standard" way to test things, and it
-happens to be that there are not that many Haskell developers in the Ethereum
-community.
-
-It is also hard to distribute. It requires installing Nix, and that's a barrier
-to entry to many already because (for whatever reason) Nix doesn't always
-install properly the first time.
-
-The more technical reasons I decided to use it are:
-
-1. It is easier to write regression tests in Rust than in Bash.
-1. Rust binaries are cross-platform and easy to distribute.
-1. Compilation speed: We can use native bindings to the Solidity compiler
- (instead of calling out to solcjs or even to the compiled binary) for extra
- compilation speed.
-1. Testing speed: HEVM tests are really fast, but I believe we can go faster by
- leveraging Rust's high performance multithreading and resource allocation
- system.
-1. There seems to be an emerging community of Rust-Ethereum developers.
-
-Benchmarks TBD in the future, but:
-
-1. [Using a Rust EVM w/ forked RPC mode](https://github.com/brockelmore/rust-cevm/#compevm-rust-ethereum-virtual-machine-implementation-designed-for-smart-contract-composability-testing)
- was claimed to be as high as 10x faster than HEVM's forking mode.
-1. Native bindings to the Solidity compiler have been shown to be
- [10x](https://forum.openzeppelin.com/t/a-faster-solidity-compiler-cli-in-rust/2546)
- faster than the JS bindings or even just calling out to the native binary.
-1. `seth` and `dapp` are less than 7mb when built with `cargo build --release`.
-
-## Features
-
-- seth
- - [ ] `--abi-decode`
- - [ ] `--calldata-decode`
- - [x] `--from-ascii` (with `--from-utf8` alias)
- - [ ] `--from-bin`
- - [ ] `--from-fix`
- - [ ] `--from-wei`
- - [ ] `--max-int`
- - [ ] `--max-uint`
- - [ ] `--min-int`
- - [x] `--to-checksum-address` (`--to-address` in dapptools)
- - [x] `--to-ascii`
- - [x] `--to-bytes32`
- - [x] `--to-dec`
- - [x] `--to-fix`
- - [x] `--to-hex`
- - [x] `--to-hexdata`
- - [ ] `--to-int256`
- - [x] `--to-uint256`
- - [x] `--to-wei`
- - [ ] `4byte`
- - [ ] `4byte-decode`
- - [ ] `4byte-event`
- - [ ] `abi-encode`
- - [x] `age`
- - [x] `balance`
- - [x] `basefee`
- - [x] `block`
- - [x] `block-number`
- - [ ] `bundle-source`
- - [x] `call` (partial)
- - [x] `calldata`
- - [x] `chain`
- - [x] `chain-id`
- - [ ] `code`
- - [ ] `debug`
- - [ ] `estimate`
- - [ ] `etherscan-source`
- - [ ] `events`
- - [x] `gas-price`
- - [ ] `index`
- - [x] `keccak`
- - [ ] `logs`
- - [x] `lookup-address`
- - [ ] `ls`
- - [ ] `mktx`
- - [x] `namehash`
- - [ ] `nonce`
- - [ ] `publish`
- - [ ] `receipt`
- - [x] `resolve-name`
- - [ ] `run-tx`
- - [x] `send` (partial)
- - [ ] `sign`
- - [x] `storage`
- - [ ] `tx`
-- dapp
- - [ ] test
- - [x] Simple unit tests
- - [x] Gas costs
- - [x] DappTools style test output
- - [x] JSON test output
- - [x] Matching on regex
- - [x] DSTest-style assertions support
- - [x] Fuzzing
- - [ ] Symbolic execution
- - [ ] Coverage
- - [ ] HEVM-style Solidity cheatcodes
- - [x] roll: Sets block.number
- - [x] warp: Sets block.timestamp
- - [x] ffi: Perform foreign function call to terminal
- - [x] store: Sets address storage slot
- - [x] load: Loads address storage slot
- - [x] deal: Sets account balance
- - [x] prank: Performs a call as another address (changes msg.sender for a call)
- - [x] sign: Signs data
- - [x] addr: Gets address for a private key
- - [ ] makeEOA
- - ...?
- - [ ] Structured tracing with abi decoding
- - [ ] Per-line gas profiling
- - [x] Forking mode
- - [x] Automatic solc selection
- - [x] build
- - [x] Can read DappTools-style .sol.json artifacts
- - [x] Manual remappings
- - [x] Automatic remappings
- - [x] Multiple compiler versions
- - [ ] Incremental compilation
- - [ ] Can read Hardhat-style artifacts
- - [ ] Can read Truffle-style artifacts
- - [x] install
- - [x] update
- - [ ] debug
- - [x] CLI Tracing with `RUST_LOG=dapp=trace`
-
-## Tested Against
-
-This repository has been tested against a few repositories which you can monitor
-[here](https://github.com/gakonst/dapptools-benchmarks)
-
-## Development
+More documentation can be found in the [forge package](./forge/README.md).
+
+### Features
+
+1. Fast & flexible compilation pipeline:
+ 1. Automatic Solidity compiler version detection & installation (under
+ `~/.svm`)
+ 1. Incremental compilation & caching: Only changed files are re-compiled
+ 1. Parallel compilation
+ 1. Non-standard directory structures support (e.g. can build
+ [Hardhat repos](https://twitter.com/gakonst/status/1461289225337421829))
+1. Tests are written in Solidity (like in DappTools)
+1. Fast fuzz Tests with shrinking of inputs & printing of counter-examples
+1. Fast remote RPC forking mode leveraging Rust's async infrastructure like
+ tokio
+1. Flexible debug logging:
+ 1. Dapptools-style, using `DsTest`'s emitted logs
+ 1. Hardhat-style, using the popular `console.sol` contract
+1. Portable (5-10MB) & easy to install statically linked binary without
+ requiring Nix or any other package manager
+1. Abstracted over EVM implementations (currently supported: Sputnik, EvmOdin)
+
+### How Fast?
+
+Forge is quite fast at both compiling (leveraging the
+[ethers-solc](https://github.com/gakonst/ethers-rs/tree/master/ethers-solc/)
+package) and testing.
+
+Some benchmarks below:
+
+| Project | Forge | DappTools | Speedup |
+| --------------------------------------------------- | ----- | --------- | ------- |
+| [guni-lev](https://github.com/hexonaut/guni-lev/) | 28.6s | 2m36s | 5.45x |
+| [solmate](https://github.com/Rari-Capital/solmate/) | 6s | 46s | 7.66x |
+| [geb](https://github.com/reflexer-labs/geb) | 11s | 40s | 3.63x |
+| [vaults](https://github.com/rari-capital/vaults) | 1.4s | 5.5s | 3.9x |
+
+It also works with "non-standard" directory structures (i.e. contracts not in
+`src/`, libraries not in `lib/`). When
+[tested](https://twitter.com/gakonst/status/1461289225337421829) with
+[`openzeppelin-contracts`](https://github.com/OpenZeppelin/openzeppelin-contracts),
+Hardhat compilation took 15.244s, whereas Forge took 9.449 (~4s cached)
+
+## Cast Quickstart
+
+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
+```
+
+More documentation can be found in the [cast package](./cast/README.md).
+
+## Contributing
+
+### Directory structure
+
+This repository contains several Rust crates:
+
+- [`forge`](forge): Library for building and testing a Solidity repository.
+- [`cast`](cast): Library for interacting with a live Ethereum JSON-RPC
+ compatible node, or for parsing data.
+- [`cli`](cli): Command line interfaces to `cast` and `forge`.
+- [`evm-adapters`](evm-adapters): Unified layer of abstraction over multiple EVM
+ types. Currently supported EVMs:
+ [Sputnik](https://github.com/rust-blockchain/evm/),
+ [Evmodin](https://github.com/vorot93/evmodin).
+- [`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
@@ -201,7 +133,23 @@ cargo clippy
First, see if the answer to your question can be found in the API documentation.
If the answer is not there, try opening an
-[issue](https://github.com/gakonst/dapptools-rs/issues/new) with the question.
-
-Join the [turbodapptools telegram](https://t.me/turbodapptools) to chat with the
-community!
+[issue](https://github.com/gakonst/foundry/issues/new) with the question.
+
+Join the [foundry telegram](https://t.me/foundry_rs) to chat with the community!
+
+## Acknowledgements
+
+- Foundry is a clean-room rewrite of the testing framework
+ [dapptools](https://github.com/dapphub/dapptools). None of this would have
+ been possible without the DappHub team's work over the years.
+- [Matthias Seitz](https://twitter.com/mattsse_): Created
+ [ethers-solc](https://github.com/gakonst/ethers-rs/tree/master/ethers-solc/)
+ which is the backbone of our compilation pipeline, as well as countless
+ contributions to ethers, in particular the `abigen` macros.
+- [Rohit Narunkar](https://twitter.com/rohitnarurkar): Created the Rust Solidity
+ version manager [svm-rs](https://github.com/roynalnaruto/svm-rs) which we use
+ to auto-detect and manage multiple Solidity versions.
+- All the other
+ [contributors](https://github.com/gakonst/foundry/graphs/contributors) to the
+ [ethers-rs](https://github.com/gakonst/ethers-rs) &
+ [foundry](https://github.com/gakonst/foundry) repositories and chatrooms.
diff --git a/assets/demo.svg b/assets/demo.svg
new file mode 100644
index 000000000000..08fe56a6a1d1
--- /dev/null
+++ b/assets/demo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/seth/Cargo.toml b/cast/Cargo.toml
similarity index 89%
rename from seth/Cargo.toml
rename to cast/Cargo.toml
index cca0aa8cdf50..a4a37482d5e3 100644
--- a/seth/Cargo.toml
+++ b/cast/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "seth"
+name = "cast"
version = "0.1.0"
edition = "2018"
license = "MIT OR Apache-2.0"
@@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
-dapp-utils = { path = "../utils" }
+foundry-utils = { path = "../utils" }
# ethers = "0.5"
ethers-core = { git = "https://github.com/gakonst/ethers-rs", default-features = false }
ethers-providers = { git = "https://github.com/gakonst/ethers-rs", default-features = false }
diff --git a/cast/README.md b/cast/README.md
new file mode 100644
index 000000000000..8e8397c0b5f4
--- /dev/null
+++ b/cast/README.md
@@ -0,0 +1,59 @@
+# `cast`
+
+## Features
+
+- [ ] `--abi-decode`
+- [ ] `--calldata-decode`
+- [x] `--from-ascii` (with `--from-utf8` alias)
+- [ ] `--from-bin`
+- [ ] `--from-fix`
+- [ ] `--from-wei`
+- [ ] `--max-int`
+- [ ] `--max-uint`
+- [ ] `--min-int`
+- [x] `--to-checksum-address` (`--to-address` in dapptools)
+- [x] `--to-ascii`
+- [x] `--to-bytes32`
+- [x] `--to-dec`
+- [x] `--to-fix`
+- [x] `--to-hex`
+- [x] `--to-hexdata`
+- [ ] `--to-int256`
+- [x] `--to-uint256`
+- [x] `--to-wei`
+- [ ] `4byte`
+- [ ] `4byte-decode`
+- [ ] `4byte-event`
+- [ ] `abi-encode`
+- [x] `age`
+- [x] `balance`
+- [x] `basefee`
+- [x] `block`
+- [x] `block-number`
+- [ ] `bundle-source`
+- [x] `call` (partial)
+- [x] `calldata`
+- [x] `chain`
+- [x] `chain-id`
+- [ ] `code`
+- [ ] `debug`
+- [ ] `estimate`
+- [ ] `etherscan-source`
+- [ ] `events`
+- [x] `gas-price`
+- [ ] `index`
+- [x] `keccak`
+- [ ] `logs`
+- [x] `lookup-address`
+- [ ] `ls`
+- [ ] `mktx`
+- [x] `namehash`
+- [ ] `nonce`
+- [ ] `publish`
+- [ ] `receipt`
+- [x] `resolve-name`
+- [ ] `run-tx`
+- [x] `send` (partial)
+- [ ] `sign`
+- [x] `storage`
+- [ ] `tx`
diff --git a/seth/src/lib.rs b/cast/src/lib.rs
similarity index 82%
rename from seth/src/lib.rs
rename to cast/src/lib.rs
index 8c5b69b5172f..43e9c423987f 100644
--- a/seth/src/lib.rs
+++ b/cast/src/lib.rs
@@ -1,4 +1,4 @@
-//! Seth
+//! Cast
//!
//! TODO
use chrono::NaiveDateTime;
@@ -12,28 +12,28 @@ use eyre::Result;
use rustc_hex::{FromHexIter, ToHex};
use std::str::FromStr;
-use dapp_utils::{encode_args, get_func, to_table};
+use foundry_utils::{encode_args, get_func, to_table};
-// TODO: SethContract with common contract initializers? Same for SethProviders?
+// TODO: CastContract with common contract initializers? Same for CastProviders?
-pub struct Seth {
+pub struct Cast {
provider: M,
}
-impl Seth
+impl Cast
where
M::Error: 'static,
{
/// Converts ASCII text input to hex
///
/// ```
- /// use seth::Seth;
+ /// use cast::Cast;
/// use ethers_providers::{Provider, Http};
/// use std::convert::TryFrom;
///
/// # async fn foo() -> eyre::Result<()> {
/// let provider = Provider::::try_from("http://localhost:8545")?;
- /// let seth = Seth::new(provider);
+ /// let cast = Cast::new(provider);
/// # Ok(())
/// # }
/// ```
@@ -45,18 +45,18 @@ where
///
/// ```no_run
///
- /// use seth::Seth;
+ /// use cast::Cast;
/// use ethers_core::types::Address;
/// use ethers_providers::{Provider, Http};
/// use std::{str::FromStr, convert::TryFrom};
///
/// # async fn foo() -> eyre::Result<()> {
/// let provider = Provider::::try_from("http://localhost:8545")?;
- /// let seth = Seth::new(provider);
+ /// let cast = Cast::new(provider);
/// let to = Address::from_str("0xB3C95ff08316fb2F2e3E52Ee82F8e7b605Aa1304")?;
/// let sig = "function greeting(uint256 i) public returns (string)";
/// let args = vec!["5".to_owned()];
- /// let data = seth.call(to, sig, args).await?;
+ /// let data = cast.call(to, sig, args).await?;
/// println!("{}", data);
/// # Ok(())
/// # }
@@ -98,18 +98,18 @@ where
/// Sends a transaction to the specified address
///
/// ```no_run
- /// use seth::Seth;
+ /// use cast::Cast;
/// use ethers_core::types::Address;
/// use ethers_providers::{Provider, Http};
/// use std::{str::FromStr, convert::TryFrom};
///
/// # async fn foo() -> eyre::Result<()> {
/// let provider = Provider::::try_from("http://localhost:8545")?;
- /// let seth = Seth::new(provider);
+ /// let cast = Cast::new(provider);
/// let to = Address::from_str("0xB3C95ff08316fb2F2e3E52Ee82F8e7b605Aa1304")?;
/// let sig = "function greet(string memory) public returns (string)";
/// let args = vec!["5".to_owned()];
- /// let data = seth.call(to, sig, args).await?;
+ /// let data = cast.call(to, sig, args).await?;
/// println!("{}", data);
/// # Ok(())
/// # }
@@ -140,14 +140,14 @@ where
}
/// ```no_run
- /// use seth::Seth;
+ /// use cast::Cast;
/// use ethers_providers::{Provider, Http};
/// use std::convert::TryFrom;
///
/// # async fn foo() -> eyre::Result<()> {
/// let provider = Provider::::try_from("http://localhost:8545")?;
- /// let seth = Seth::new(provider);
- /// let block = seth.block(5, true, None, false).await?;
+ /// let cast = Cast::new(provider);
+ /// let block = cast.block(5, true, None, false).await?;
/// println!("{}", block);
/// # Ok(())
/// # }
@@ -199,7 +199,7 @@ where
async fn block_field_as_num>(&self, block: T, field: String) -> Result {
let block = block.into();
- let block_field = Seth::block(
+ let block_field = Cast::block(
self,
block,
false,
@@ -213,18 +213,18 @@ where
}
pub async fn base_fee>(&self, block: T) -> Result {
- Ok(Seth::block_field_as_num(self, block, String::from("baseFeePerGas")).await?)
+ Ok(Cast::block_field_as_num(self, block, String::from("baseFeePerGas")).await?)
}
pub async fn age>(&self, block: T) -> Result {
let timestamp_str =
- Seth::block_field_as_num(self, block, String::from("timestamp")).await?.to_string();
+ Cast::block_field_as_num(self, block, String::from("timestamp")).await?.to_string();
let datetime = NaiveDateTime::from_timestamp(timestamp_str.parse::().unwrap(), 0);
Ok(datetime.format("%a %b %e %H:%M:%S %Y").to_string())
}
pub async fn chain(&self) -> Result<&str> {
- let genesis_hash = Seth::block(
+ let genesis_hash = Cast::block(
self,
0,
false,
@@ -236,7 +236,7 @@ where
Ok(match &genesis_hash[..] {
"0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" => {
- match &(Seth::block(self, 1920000, false, Some(String::from("hash")), false)
+ match &(Cast::block(self, 1920000, false, Some(String::from("hash")), false)
.await?)[..]
{
"0x94365e3a8c0b35089c1d1195081fe7489b528a84b22199c916180db8b28ade7f" => {
@@ -279,14 +279,14 @@ where
}
}
-pub struct SimpleSeth;
-impl SimpleSeth {
+pub struct SimpleCast;
+impl SimpleCast {
/// Converts UTF-8 text input to hex
///
/// ```
- /// use seth::SimpleSeth as Seth;
+ /// use cast::SimpleCast as Cast;
///
- /// let bin = Seth::from_utf8("yo");
+ /// let bin = Cast::from_utf8("yo");
/// assert_eq!(bin, "0x796f")
/// ```
pub fn from_utf8(s: &str) -> String {
@@ -297,11 +297,11 @@ impl SimpleSeth {
/// Converts hex data into text data
///
/// ```
- /// use seth::SimpleSeth as Seth;
+ /// use cast::SimpleCast as Cast;
///
/// fn main() -> eyre::Result<()> {
- /// assert_eq!("Hello, World!", Seth::ascii("48656c6c6f2c20576f726c6421")?);
- /// assert_eq!("TurboDappTools", Seth::ascii("0x547572626f44617070546f6f6c73")?);
+ /// assert_eq!("Hello, World!", Cast::ascii("48656c6c6f2c20576f726c6421")?);
+ /// assert_eq!("TurboDappTools", Cast::ascii("0x547572626f44617070546f6f6c73")?);
///
/// Ok(())
/// }
@@ -319,12 +319,12 @@ impl SimpleSeth {
/// Converts hex input to decimal
///
/// ```
- /// use seth::SimpleSeth as Seth;
+ /// use cast::SimpleCast as Cast;
/// use ethers_core::types::U256;
///
/// fn main() -> eyre::Result<()> {
- /// assert_eq!(U256::from_dec_str("424242")?, Seth::to_dec("0x67932")?);
- /// assert_eq!(U256::from_dec_str("1234")?, Seth::to_dec("0x4d2")?);
+ /// assert_eq!(U256::from_dec_str("424242")?, Cast::to_dec("0x67932")?);
+ /// assert_eq!(U256::from_dec_str("1234")?, Cast::to_dec("0x4d2")?);
///
/// Ok(())
/// }
@@ -335,14 +335,14 @@ impl SimpleSeth {
/// Converts integers with specified decimals into fixed point numbers
///
/// ```
- /// use seth::SimpleSeth as Seth;
+ /// use cast::SimpleCast as Cast;
/// use ethers_core::types::U256;
///
/// fn main() -> eyre::Result<()> {
- /// assert_eq!(Seth::to_fix(0, 10.into())?, "10.");
- /// assert_eq!(Seth::to_fix(1, 10.into())?, "1.0");
- /// assert_eq!(Seth::to_fix(2, 10.into())?, "0.10");
- /// assert_eq!(Seth::to_fix(3, 10.into())?, "0.010");
+ /// assert_eq!(Cast::to_fix(0, 10.into())?, "10.");
+ /// assert_eq!(Cast::to_fix(1, 10.into())?, "1.0");
+ /// assert_eq!(Cast::to_fix(2, 10.into())?, "0.10");
+ /// assert_eq!(Cast::to_fix(3, 10.into())?, "0.010");
///
/// Ok(())
/// }
@@ -364,13 +364,13 @@ impl SimpleSeth {
/// Converts decimal input to hex
///
/// ```
- /// use seth::SimpleSeth as Seth;
+ /// use cast::SimpleCast as Cast;
/// use ethers_core::types::U256;
///
/// fn main() -> eyre::Result<()> {
- /// assert_eq!(Seth::hex(U256::from_dec_str("424242")?), "0x67932");
- /// assert_eq!(Seth::hex(U256::from_dec_str("1234")?), "0x4d2");
- /// assert_eq!(Seth::hex(U256::from_dec_str("115792089237316195423570985008687907853269984665640564039457584007913129639935")?), "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
+ /// assert_eq!(Cast::hex(U256::from_dec_str("424242")?), "0x67932");
+ /// assert_eq!(Cast::hex(U256::from_dec_str("1234")?), "0x4d2");
+ /// assert_eq!(Cast::hex(U256::from_dec_str("115792089237316195423570985008687907853269984665640564039457584007913129639935")?), "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
///
/// Ok(())
/// }
@@ -382,13 +382,13 @@ impl SimpleSeth {
/// Converts a number into uint256 hex string with 0x prefix
///
/// ```
- /// use seth::SimpleSeth as Seth;
+ /// use cast::SimpleCast as Cast;
///
/// fn main() -> eyre::Result<()> {
- /// assert_eq!(Seth::to_uint256("100")?, "0x0000000000000000000000000000000000000000000000000000000000000064");
- /// assert_eq!(Seth::to_uint256("192038293923")?, "0x0000000000000000000000000000000000000000000000000000002cb65fd1a3");
+ /// assert_eq!(Cast::to_uint256("100")?, "0x0000000000000000000000000000000000000000000000000000000000000064");
+ /// assert_eq!(Cast::to_uint256("192038293923")?, "0x0000000000000000000000000000000000000000000000000000002cb65fd1a3");
/// assert_eq!(
- /// Seth::to_uint256("115792089237316195423570985008687907853269984665640564039457584007913129639935")?,
+ /// Cast::to_uint256("115792089237316195423570985008687907853269984665640564039457584007913129639935")?,
/// "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
/// );
///
@@ -404,13 +404,13 @@ impl SimpleSeth {
/// Converts an eth amount into wei
///
/// ```
- /// use seth::SimpleSeth as Seth;
+ /// use cast::SimpleCast as Cast;
///
/// fn main() -> eyre::Result<()> {
- /// assert_eq!(Seth::to_wei(1.into(), "".to_string())?, "1");
- /// assert_eq!(Seth::to_wei(100.into(), "gwei".to_string())?, "100000000000");
- /// assert_eq!(Seth::to_wei(100.into(), "eth".to_string())?, "100000000000000000000");
- /// assert_eq!(Seth::to_wei(1000.into(), "ether".to_string())?, "1000000000000000000000");
+ /// assert_eq!(Cast::to_wei(1.into(), "".to_string())?, "1");
+ /// assert_eq!(Cast::to_wei(100.into(), "gwei".to_string())?, "100000000000");
+ /// assert_eq!(Cast::to_wei(100.into(), "eth".to_string())?, "100000000000000000000");
+ /// assert_eq!(Cast::to_wei(1000.into(), "ether".to_string())?, "1000000000000000000000");
///
/// Ok(())
/// }
@@ -428,13 +428,13 @@ impl SimpleSeth {
/// according to [EIP-55](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md)
///
/// ```
- /// use seth::SimpleSeth as Seth;
+ /// use cast::SimpleCast as Cast;
/// use ethers_core::types::Address;
/// use std::str::FromStr;
///
/// # fn main() -> eyre::Result<()> {
/// let addr = Address::from_str("0xb7e390864a90b7b923c9f9310c6f98aafe43f707")?;
- /// let addr = Seth::checksum_address(&addr)?;
+ /// let addr = Cast::checksum_address(&addr)?;
/// assert_eq!(addr, "0xB7e390864a90b7b923C9f9310C6F98aafE43F707");
///
/// # Ok(())
@@ -446,16 +446,16 @@ impl SimpleSeth {
/// Converts hexdata into bytes32 value
/// ```
- /// use seth::SimpleSeth as Seth;
+ /// use cast::SimpleCast as Cast;
///
/// # fn main() -> eyre::Result<()> {
- /// let bytes = Seth::bytes32("1234")?;
+ /// let bytes = Cast::bytes32("1234")?;
/// assert_eq!(bytes, "0x1234000000000000000000000000000000000000000000000000000000000000");
///
- /// let bytes = Seth::bytes32("0x1234")?;
+ /// let bytes = Cast::bytes32("0x1234")?;
/// assert_eq!(bytes, "0x1234000000000000000000000000000000000000000000000000000000000000");
///
- /// let err = Seth::bytes32("0x123400000000000000000000000000000000000000000000000000000000000011").unwrap_err();
+ /// let err = Cast::bytes32("0x123400000000000000000000000000000000000000000000000000000000000011").unwrap_err();
/// assert_eq!(err.to_string(), "string >32 bytes");
///
/// # Ok(())
@@ -474,11 +474,11 @@ impl SimpleSeth {
/// Keccak-256 hashes arbitrary data
///
/// ```
- /// use seth::SimpleSeth as Seth;
+ /// use cast::SimpleCast as Cast;
///
/// fn main() -> eyre::Result<()> {
- /// assert_eq!(Seth::keccak("foo")?, "0x41b1a0649752af1b28b3dc29a1556eee781e4a4c3a1f7f53f90fa834de098c4d");
- /// assert_eq!(Seth::keccak("123abc")?, "0xb1f1c74a1ba56f07a892ea1110a39349d40f66ca01d245e704621033cb7046a4");
+ /// assert_eq!(Cast::keccak("foo")?, "0x41b1a0649752af1b28b3dc29a1556eee781e4a4c3a1f7f53f90fa834de098c4d");
+ /// assert_eq!(Cast::keccak("123abc")?, "0xb1f1c74a1ba56f07a892ea1110a39349d40f66ca01d245e704621033cb7046a4");
///
/// Ok(())
/// }
@@ -493,13 +493,13 @@ impl SimpleSeth {
/// [namehash-rust reference](https://github.com/InstateDev/namehash-rust/blob/master/src/lib.rs)
///
/// ```
- /// use seth::SimpleSeth as Seth;
+ /// use cast::SimpleCast as Cast;
///
/// fn main() -> eyre::Result<()> {
- /// assert_eq!(Seth::namehash("")?, "0x0000000000000000000000000000000000000000000000000000000000000000");
- /// assert_eq!(Seth::namehash("eth")?, "0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae");
- /// assert_eq!(Seth::namehash("foo.eth")?, "0xde9b09fd7c5f901e23a3f19fecc54828e9c848539801e86591bd9801b019f84f");
- /// assert_eq!(Seth::namehash("sub.foo.eth")?, "0x500d86f9e663479e5aaa6e99276e55fc139c597211ee47d17e1e92da16a83402");
+ /// assert_eq!(Cast::namehash("")?, "0x0000000000000000000000000000000000000000000000000000000000000000");
+ /// assert_eq!(Cast::namehash("eth")?, "0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae");
+ /// assert_eq!(Cast::namehash("foo.eth")?, "0xde9b09fd7c5f901e23a3f19fecc54828e9c848539801e86591bd9801b019f84f");
+ /// assert_eq!(Cast::namehash("sub.foo.eth")?, "0x500d86f9e663479e5aaa6e99276e55fc139c597211ee47d17e1e92da16a83402");
///
/// Ok(())
/// }
@@ -528,19 +528,19 @@ impl SimpleSeth {
/// Performs ABI encoding to produce the hexadecimal calldata with the given arguments.
///
/// ```
- /// # use seth::SimpleSeth as Seth;
+ /// # use cast::SimpleCast as Cast;
///
/// # fn main() -> eyre::Result<()> {
/// assert_eq!(
/// "0xb3de648b0000000000000000000000000000000000000000000000000000000000000001",
- /// Seth::calldata("f(uint a)", &["1"]).unwrap().as_str()
+ /// Cast::calldata("f(uint a)", &["1"]).unwrap().as_str()
/// );
/// # Ok(())
/// # }
/// ```
pub fn calldata(sig: impl AsRef, args: &[impl AsRef]) -> Result {
let func = AbiParser::default().parse_function(sig.as_ref())?;
- let calldata = dapp_utils::encode_args(&func, args)?;
+ let calldata = encode_args(&func, args)?;
Ok(format!("0x{}", calldata.to_hex::()))
}
}
@@ -551,13 +551,13 @@ fn strip_0x(s: &str) -> &str {
#[cfg(test)]
mod tests {
- use super::SimpleSeth as Seth;
+ use super::SimpleCast as Cast;
#[test]
fn calldata_uint() {
assert_eq!(
"0xb3de648b0000000000000000000000000000000000000000000000000000000000000001",
- Seth::calldata("f(uint a)", &["1"]).unwrap().as_str()
+ Cast::calldata("f(uint a)", &["1"]).unwrap().as_str()
);
}
@@ -565,7 +565,7 @@ mod tests {
fn calldata_bool() {
assert_eq!(
"0x6fae94120000000000000000000000000000000000000000000000000000000000000000",
- Seth::calldata("bar(bool)", &["false"]).unwrap().as_str()
+ Cast::calldata("bar(bool)", &["false"]).unwrap().as_str()
);
}
}
diff --git a/dapptools/Cargo.toml b/cli/Cargo.toml
similarity index 87%
rename from dapptools/Cargo.toml
rename to cli/Cargo.toml
index 0c8cb5715926..55c0223df30a 100644
--- a/dapptools/Cargo.toml
+++ b/cli/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "dapptools"
+name = "foundry-cli"
version = "0.1.0"
edition = "2018"
license = "MIT OR Apache-2.0"
@@ -8,10 +8,11 @@ license = "MIT OR Apache-2.0"
[dependencies]
structopt = "0.3.23"
-dapp-utils = { path = "../utils" }
-dapp = { path = "../dapp" }
-seth = { path = "../seth" }
+foundry-utils = { path = "../utils" }
+forge = { path = "../forge" }
+cast = { path = "../cast" }
evm-adapters = { path = "../evm-adapters" }
+
# ethers = "0.5"
ethers = { git = "https://github.com/gakonst/ethers-rs" }
ethers-etherscan = { git = "https://github.com/gakonst/ethers-rs" }
@@ -55,11 +56,11 @@ evmodin-evm = [
]
[[bin]]
-name = "seth"
-path = "src/seth.rs"
+name = "cast"
+path = "src/cast.rs"
doc = false
[[bin]]
-name = "dapp"
-path = "src/dapp.rs"
+name = "forge"
+path = "src/forge.rs"
doc = false
diff --git a/cli/README.md b/cli/README.md
new file mode 100644
index 000000000000..bff49e8cdab5
--- /dev/null
+++ b/cli/README.md
@@ -0,0 +1,213 @@
+# Foundry CLIs
+
+The CLIs are written using [structopt](https://docs.rs/structopt).
+
+Debug logs are printed with
+[`tracing`](https://docs.rs/tracing/0.1.29/tracing/). You can configure the
+verbosity level via the
+[`RUST_LOG`](https://docs.rs/tracing-subscriber/0.3.2/tracing_subscriber/fmt/index.html#filtering-events-with-environment-variables)
+environment variable, on a per package level,
+e.g.:`RUST_LOG=forge=trace,evm_adapters=trace forge test`
+
+## Forge
+
+```
+foundry-cli 0.1.0
+Build, test, fuzz, formally verify, debug & deploy solidity contracts.
+
+USAGE:
+ forge
+
+FLAGS:
+ -h, --help Prints help information
+ -V, --version Prints version information
+
+SUBCOMMANDS:
+ build build your smart contracts
+ create deploy a compiled contract
+ help Prints this message or the help of the given subcommand(s)
+ install installs one or more dependencies as git submodules
+ remappings prints the automatically inferred remappings for this repository
+ test test your smart contracts
+ update fetches all upstream lib changes
+ verify-contract build your smart contracts. Requires `ETHERSCAN_API_KEY` to be set.
+```
+
+The subcommands are also aliased to their first letter, e.g. you can do
+`forge t` instead of `forge test` or `forge b` instead of `forge build`.
+
+### Build
+
+The `build` subcommand proceeds to compile your smart contracts.
+
+```
+forge-build 0.1.0
+build your smart contracts
+
+USAGE:
+ forge build [FLAGS] [OPTIONS]
+
+FLAGS:
+ -h, --help Prints help information
+ --no-auto-detect if set to true, skips auto-detecting solc and uses what is in the user's $PATH
+ -V, --version Prints version information
+
+OPTIONS:
+ -c, --contracts the directory relative to the root under which the smart contrats are [env:
+ DAPP_SRC=]
+ --evm-version choose the evm version [default: london]
+ --lib-paths ... the paths where your libraries are installed
+ -o, --out path to where the contract artifacts are stored
+ -r, --remappings ... the remappings
+ --remappings-env [env: DAPP_REMAPPINGS=]
+ --root the project's root path, default being the current directory [default:
+ std::env::current_dir().unwrap()]
+```
+
+By default, it will auto-detect the solc pragma version requirement per-file and
+will use the [latest version](https://github.com/ethereum/solidity/releases)
+that satisfies the requirement, (e.g. `pragma solidity >=0.7.0 <0.8.0` will use
+`solc` 0.7.6). If you want to disable this feature, you can call
+`forge build --no-auto-detect`, and it'll use whichever `solc` version is in
+your `$PATH`.
+
+The project's root directory defaults to the current directory, assuming
+contracts are under `src/` and `lib/`, but can also be configured via the
+`--root`, `--lib-paths` and `--contracts` arguments. The contracts and libraries
+directories are assumed to be relative to the project root, for example
+`forge build --root ../my-project --contracts my-contracts-dir` will try to find
+the contracts under `../my-project/my-contracts-dir`. You can also configure the
+output directory where the contract artifacts will be written to with the
+`--out` variable.
+
+Compiler remappings are automatically detected, but if you want to override them
+you can do it with the `--remappings` flag like below:
+
+```bash
+$ forge build --remappings @openzeppelin/=node_modules/@openzeppelin/
+```
+
+Most of the arguments can also be provided via environment variables, which you
+can find by looking for the `env` tooltip in the command's help menu
+(`forge build --help`).
+
+### Test
+
+Proceeds to build (if needed) and test your smart contracts. It will look for
+any contract with a function name that starts with `test`, deploy it, and run
+that test function. If that test function takes any arguments, it will proceed
+to "fuzz" it (i.e. call it with a lot of different arguments, default: 256
+tries).
+
+The command re-uses all the options of `forge build`, and also allows you to
+configure any blockchain context related variables such as the block coinbase,
+difficulty etc.
+
+```
+forge-test 0.1.0
+test your smart contracts
+
+USAGE:
+ forge test [FLAGS] [OPTIONS]
+
+FLAGS:
+ --ffi enables the FFI cheatcode
+ -h, --help Prints help information
+ -j, --json print the test results in json format
+ --no-auto-detect if set to true, skips auto-detecting solc and uses what is in the user's $PATH
+ -V, --version Prints version information
+
+OPTIONS:
+ --block-base-fee-per-gas the base fee in a block [default: 0]
+ --block-coinbase
+ the block.coinbase value during EVM execution [default: 0x0000000000000000000000000000000000000000]
+
+ --block-difficulty
+ the block.difficulty value during EVM execution [default: 0]
+
+ --block-gas-limit the block.gaslimit value during EVM execution
+ --block-number
+ the block.number value during EVM execution [env: DAPP_TEST_NUMBER=] [default: 0]
+
+ --block-timestamp
+ the block.timestamp value during EVM execution [env: DAPP_TEST_TIMESTAMP=] [default: 0]
+
+ --chain-id the chainid opcode value [default: 1]
+ -c, --contracts
+ the directory relative to the root under which the smart contrats are [env: DAPP_SRC=]
+
+ -e, --evm-type
+ the EVM type you want to use (e.g. sputnik, evmodin) [default: sputnik]
+
+ --evm-version choose the evm version [default: london]
+ --fork-block-number
+ pins the block number for the state fork [env: DAPP_FORK_BLOCK=]
+
+ -f, --fork-url
+ fetch state over a remote instead of starting from empty state [env: ETH_RPC_URL=]
+
+ --gas-limit the block gas limit [default: 18446744073709551615]
+ --gas-price the tx.gasprice value during EVM execution [default: 0]
+ --initial-balance
+ the initial balance of each deployed test contract [default: 0xffffffffffffffffffffffff]
+
+ --lib-paths ... the paths where your libraries are installed
+ -o, --out path to where the contract artifacts are stored
+ -m, --match only run test methods matching regex [default: .*]
+ -r, --remappings ... the remappings
+ --remappings-env [env: DAPP_REMAPPINGS=]
+ --root
+ the project's root path, default being the current directory [default: std::env::current_dir().unwrap()]
+
+ --sender
+ the address which will be executing all tests [env: DAPP_TEST_ADDRESS=] [default:
+ 0x0000000000000000000000000000000000000000]
+ --tx-origin
+ the tx.origin value during EVM execution [default: 0x0000000000000000000000000000000000000000]
+
+ --verbosity verbosity of 'forge test' output (0-3) [default: 0]
+```
+
+Here's how the CLI output looks like when used with
+[`dapptools-template`](https://github.com/gakonst/dapptools-template)
+
+```bash
+$ forge test
+success.
+Running 3 tests for "Greet.json":Greet
+[PASS] testCanSetGreeting (gas: 31070)
+[PASS] testWorksForAllGreetings (gas: [fuzztest])
+[PASS] testCannotGm (gas: 6819)
+
+Running 3 tests for "Gm.json":Gm
+[PASS] testOwnerCannotGmOnBadBlocks (gas: 7771)
+[PASS] testNonOwnerCannotGm (gas: 3782)
+[PASS] testOwnerCanGmOnGoodBlocks (gas: 31696)
+```
+
+You can optionally specify a regular expression, to only run matching functions:
+
+```bash
+$ forge test -m Cannot
+$HOME/oss/foundry/target/release/forge test -m Cannot
+no files changed, compilation skippped.
+Running 1 test for "Greet.json":Greet
+[PASS] testCannotGm (gas: 6819)
+
+Running 2 tests for "Gm.json":Gm
+[PASS] testNonOwnerCannotGm (gas: 3782)
+[PASS] testOwnerCannotGmOnBadBlocks (gas: 7771)
+```
+
+In order to compose with other commands, you may print the results as JSON via
+the `--json` flag
+
+```bash
+$ forge test --json
+no files changed, compilation skippped.
+{"\"Gm.json\":Gm":{"testNonOwnerCannotGm":{"success":true,"reason":null,"gas_used":3782,"counterexample":null,"logs":[]},"testOwnerCannotGmOnBadBlocks":{"success":true,"reason":null,"gas_used":7771,"counterexample":null,"logs":[]},"testOwnerCanGmOnGoodBlocks":{"success":true,"reason":null,"gas_used":31696,"counterexample":null,"logs":[]}},"\"Greet.json\":Greet":{"testWorksForAllGreetings":{"success":true,"reason":null,"gas_used":null,"counterexample":null,"logs":[]},"testCannotGm":{"success":true,"reason":null,"gas_used":6819,"counterexample":null,"logs":[]},"testCanSetGreeting":{"success":true,"reason":null,"gas_used":31070,"counterexample":null,"logs":[]}}}
+```
+
+```
+
+```
diff --git a/dapptools/src/seth.rs b/cli/src/cast.rs
similarity index 79%
rename from dapptools/src/seth.rs
rename to cli/src/cast.rs
index 9205df927496..7f44b52b830a 100644
--- a/dapptools/src/seth.rs
+++ b/cli/src/cast.rs
@@ -1,5 +1,7 @@
-mod seth_opts;
-use seth_opts::{Opts, Subcommands};
+use cast::{Cast, SimpleCast};
+
+mod cast_opts;
+use cast_opts::{Opts, Subcommands};
use ethers::{
core::types::{BlockId, BlockNumber::Latest},
@@ -9,7 +11,6 @@ use ethers::{
types::{NameOrAddress, U256},
};
use rustc_hex::ToHex;
-use seth::{Seth, SimpleSeth};
use std::{convert::TryFrom, str::FromStr};
use structopt::StructOpt;
@@ -19,11 +20,11 @@ async fn main() -> eyre::Result<()> {
match opts.sub {
Subcommands::FromUtf8 { text } => {
let val = unwrap_or_stdin(text)?;
- println!("{}", SimpleSeth::from_utf8(&val));
+ println!("{}", SimpleCast::from_utf8(&val));
}
Subcommands::ToHex { decimal } => {
let val = unwrap_or_stdin(decimal)?;
- println!("{}", SimpleSeth::hex(U256::from_dec_str(&val)?));
+ println!("{}", SimpleCast::hex(U256::from_dec_str(&val)?));
}
Subcommands::ToHexdata { input } => {
let val = unwrap_or_stdin(input)?;
@@ -48,36 +49,36 @@ async fn main() -> eyre::Result<()> {
}
Subcommands::ToCheckSumAddress { address } => {
let val = unwrap_or_stdin(address)?;
- println!("{}", SimpleSeth::checksum_address(&val)?);
+ println!("{}", SimpleCast::checksum_address(&val)?);
}
Subcommands::ToAscii { hexdata } => {
let val = unwrap_or_stdin(hexdata)?;
- println!("{}", SimpleSeth::ascii(&val)?);
+ println!("{}", SimpleCast::ascii(&val)?);
}
Subcommands::ToBytes32 { bytes } => {
let val = unwrap_or_stdin(bytes)?;
- println!("{}", SimpleSeth::bytes32(&val)?);
+ println!("{}", SimpleCast::bytes32(&val)?);
}
Subcommands::ToDec { hexvalue } => {
let val = unwrap_or_stdin(hexvalue)?;
- println!("{}", SimpleSeth::to_dec(&val)?);
+ println!("{}", SimpleCast::to_dec(&val)?);
}
Subcommands::ToFix { decimals, value } => {
let val = unwrap_or_stdin(value)?;
println!(
"{}",
- SimpleSeth::to_fix(unwrap_or_stdin(decimals)?, U256::from_dec_str(&val)?)?
+ SimpleCast::to_fix(unwrap_or_stdin(decimals)?, U256::from_dec_str(&val)?)?
);
}
Subcommands::ToUint256 { value } => {
let val = unwrap_or_stdin(value)?;
- println!("{}", SimpleSeth::to_uint256(&val)?);
+ println!("{}", SimpleCast::to_uint256(&val)?);
}
Subcommands::ToWei { value, unit } => {
let val = unwrap_or_stdin(value)?;
println!(
"{}",
- SimpleSeth::to_wei(
+ SimpleCast::to_wei(
U256::from_dec_str(&val)?,
unit.unwrap_or_else(|| String::from("wei"))
)?
@@ -85,65 +86,65 @@ async fn main() -> eyre::Result<()> {
}
Subcommands::Block { rpc_url, block, full, field, to_json } => {
let provider = Provider::try_from(rpc_url)?;
- println!("{}", Seth::new(provider).block(block, full, field, to_json).await?);
+ println!("{}", Cast::new(provider).block(block, full, field, to_json).await?);
}
Subcommands::BlockNumber { rpc_url } => {
let provider = Provider::try_from(rpc_url)?;
- println!("{}", Seth::new(provider).block_number().await?);
+ println!("{}", Cast::new(provider).block_number().await?);
}
Subcommands::Call { rpc_url, address, sig, args } => {
let provider = Provider::try_from(rpc_url)?;
- println!("{}", Seth::new(provider).call(address, &sig, args).await?);
+ println!("{}", Cast::new(provider).call(address, &sig, args).await?);
}
Subcommands::Calldata { sig, args } => {
- println!("{}", SimpleSeth::calldata(sig, &args)?);
+ println!("{}", SimpleCast::calldata(sig, &args)?);
}
Subcommands::Chain { rpc_url } => {
let provider = Provider::try_from(rpc_url)?;
- println!("{}", Seth::new(provider).chain().await?);
+ println!("{}", Cast::new(provider).chain().await?);
}
Subcommands::ChainId { rpc_url } => {
let provider = Provider::try_from(rpc_url)?;
- println!("{}", Seth::new(provider).chain_id().await?);
+ println!("{}", Cast::new(provider).chain_id().await?);
}
Subcommands::Namehash { name } => {
- println!("{}", SimpleSeth::namehash(&name)?);
+ println!("{}", SimpleCast::namehash(&name)?);
}
Subcommands::SendTx { eth, to, sig, args } => {
let provider = Provider::try_from(eth.rpc_url.as_str())?;
if let Some(signer) = eth.signer()? {
let from = eth.from.unwrap_or_else(|| signer.address());
let provider = SignerMiddleware::new(provider, signer);
- seth_send(provider, from, to, sig, args, eth.seth_async).await?;
+ cast_send(provider, from, to, sig, args, eth.cast_async).await?;
} else {
let from = eth.from.expect("No ETH_FROM or signer specified");
- seth_send(provider, from, to, sig, args, eth.seth_async).await?;
+ cast_send(provider, from, to, sig, args, eth.cast_async).await?;
}
}
Subcommands::Age { block, rpc_url } => {
let provider = Provider::try_from(rpc_url)?;
println!(
"{}",
- Seth::new(provider).age(block.unwrap_or(BlockId::Number(Latest))).await?
+ Cast::new(provider).age(block.unwrap_or(BlockId::Number(Latest))).await?
);
}
Subcommands::Balance { block, who, rpc_url } => {
let provider = Provider::try_from(rpc_url)?;
- println!("{}", Seth::new(provider).balance(who, block).await?);
+ println!("{}", Cast::new(provider).balance(who, block).await?);
}
Subcommands::BaseFee { block, rpc_url } => {
let provider = Provider::try_from(rpc_url)?;
println!(
"{}",
- Seth::new(provider).base_fee(block.unwrap_or(BlockId::Number(Latest))).await?
+ Cast::new(provider).base_fee(block.unwrap_or(BlockId::Number(Latest))).await?
);
}
Subcommands::GasPrice { rpc_url } => {
let provider = Provider::try_from(rpc_url)?;
- println!("{}", Seth::new(provider).gas_price().await?);
+ println!("{}", Cast::new(provider).gas_price().await?);
}
Subcommands::Keccak { data } => {
- println!("{}", SimpleSeth::keccak(&data)?);
+ println!("{}", SimpleCast::keccak(&data)?);
}
Subcommands::ResolveName { who, rpc_url, verify } => {
let provider = Provider::try_from(rpc_url)?;
@@ -199,23 +200,23 @@ where
})
}
-async fn seth_send, T: Into>(
+async fn cast_send, T: Into>(
provider: M,
from: F,
to: T,
sig: String,
args: Vec,
- seth_async: bool,
+ cast_async: bool,
) -> eyre::Result<()>
where
M::Error: 'static,
{
- let seth = Seth::new(provider);
+ let cast = Cast::new(provider);
let pending_tx =
- seth.send(from, to, if !sig.is_empty() { Some((&sig, args)) } else { None }).await?;
+ cast.send(from, to, if !sig.is_empty() { Some((&sig, args)) } else { None }).await?;
let tx_hash = *pending_tx;
- if seth_async {
+ if cast_async {
println!("{}", tx_hash);
} else {
let receipt = pending_tx.await?.ok_or_else(|| eyre::eyre!("tx {} not found", tx_hash))?;
diff --git a/dapptools/src/seth_opts.rs b/cli/src/cast_opts.rs
similarity index 97%
rename from dapptools/src/seth_opts.rs
rename to cli/src/cast_opts.rs
index 9787a3f20feb..1141189a261a 100644
--- a/dapptools/src/seth_opts.rs
+++ b/cli/src/cast_opts.rs
@@ -3,7 +3,7 @@ use std::{convert::TryFrom, str::FromStr, sync::Arc};
use ethers::{
providers::{Http, Provider},
signers::{coins_bip39::English, LocalWallet, MnemonicBuilder},
- types::{Address, BlockId, BlockNumber, NameOrAddress, H256, U64},
+ types::{Address, BlockId, BlockNumber, NameOrAddress, H256},
};
use eyre::Result;
use structopt::StructOpt;
@@ -28,7 +28,7 @@ pub enum Subcommands {
- @tag, where $TAG is defined in environment variables
"#)]
ToHexdata { input: Option },
- #[structopt(aliases = &["--to-checksum"])] // Compatibility with dapptools' seth
+ #[structopt(aliases = &["--to-checksum"])] // Compatibility with dapptools' cast
#[structopt(name = "--to-checksum-address")]
#[structopt(about = "convert an address to a checksummed format (EIP-55)")]
ToCheckSumAddress { address: Option },
@@ -57,7 +57,7 @@ pub enum Subcommands {
Block {
#[structopt(help = "the block you want to query, can also be earliest/latest/pending", parse(try_from_str = parse_block_id))]
block: BlockId,
- #[structopt(long, env = "SETH_FULL_BLOCK")]
+ #[structopt(long, env = "CAST_FULL_BLOCK")]
full: bool,
field: Option,
#[structopt(long = "--json", short = "-j")]
@@ -213,7 +213,7 @@ fn parse_block_id(s: &str) -> eyre::Result {
"earliest" => BlockId::Number(BlockNumber::Earliest),
"latest" => BlockId::Number(BlockNumber::Latest),
s if s.starts_with("0x") => BlockId::Hash(H256::from_str(s)?),
- s => BlockId::Number(BlockNumber::Number(U64::from_str(s)?)),
+ s => BlockId::Number(BlockNumber::Number(u64::from_str(s)?.into())),
})
}
@@ -245,8 +245,8 @@ pub struct EthereumOpts {
#[structopt(env = "ETH_FROM", short, long = "from", help = "The sender account")]
pub from: Option,
- #[structopt(long, env = "SETH_ASYNC")]
- pub seth_async: bool,
+ #[structopt(long, env = "CAST_ASYNC")]
+ pub cast_async: bool,
#[structopt(flatten)]
pub wallet: Wallet,
diff --git a/dapptools/src/cmd/mod.rs b/cli/src/cmd/mod.rs
similarity index 100%
rename from dapptools/src/cmd/mod.rs
rename to cli/src/cmd/mod.rs
diff --git a/dapptools/src/cmd/verify.rs b/cli/src/cmd/verify.rs
similarity index 97%
rename from dapptools/src/cmd/verify.rs
rename to cli/src/cmd/verify.rs
index bc0a56b511e4..ada213b7587c 100644
--- a/dapptools/src/cmd/verify.rs
+++ b/cli/src/cmd/verify.rs
@@ -2,6 +2,7 @@
use crate::utils;
+use cast::SimpleCast;
use ethers::{
abi::{Address, Function, FunctionExt},
core::types::Chain,
@@ -10,7 +11,6 @@ use ethers::{
};
use ethers_etherscan::{contract::VerifyContract, Client};
use eyre::ContextCompat;
-use seth::SimpleSeth;
use std::convert::TryFrom;
/// Run the verify command to submit the contract's source code for verification on etherscan
@@ -56,7 +56,7 @@ pub async fn run(
state_mutability: Default::default(),
};
- constructor_args = Some(SimpleSeth::calldata(fun.abi_signature(), &args)?);
+ constructor_args = Some(SimpleCast::calldata(fun.abi_signature(), &args)?);
} else if !args.is_empty() {
eyre::bail!("No constructor found but contract arguments provided")
}
diff --git a/dapptools/src/dapp.rs b/cli/src/forge.rs
similarity index 97%
rename from dapptools/src/dapp.rs
rename to cli/src/forge.rs
index 510195c37621..9b4dca185b34 100644
--- a/dapptools/src/dapp.rs
+++ b/cli/src/forge.rs
@@ -10,15 +10,15 @@ use regex::Regex;
use sputnik::backend::Backend;
use structopt::StructOpt;
-use dapp::MultiContractRunnerBuilder;
+use forge::MultiContractRunnerBuilder;
use ansi_term::Colour;
use ethers::types::U256;
-mod dapp_opts;
-use dapp_opts::{EvmType, Opts, Subcommands};
+mod forge_opts;
+use forge_opts::{EvmType, Opts, Subcommands};
-use crate::dapp_opts::FullContractInfo;
+use crate::forge_opts::FullContractInfo;
use std::{collections::HashMap, convert::TryFrom, path::Path, sync::Arc};
mod cmd;
@@ -215,9 +215,9 @@ fn main() -> eyre::Result<()> {
let tree = repo.find_tree(id).unwrap();
let message = if let Some(ref tag) = dep.tag {
- format!("turbodapp install: {}\n\n{}", dep.name, tag)
+ format!("forge install: {}\n\n{}", dep.name, tag)
} else {
- format!("turbodapp install: {}", dep.name)
+ format!("forge install: {}", dep.name)
};
// committing to the parent may make running the installation step
@@ -257,7 +257,7 @@ fn test>(
pattern: Regex,
json: bool,
verbosity: u8,
-) -> eyre::Result>> {
+) -> eyre::Result>> {
let mut runner = builder.build(project, evm)?;
let mut exit_code = 0;
diff --git a/dapptools/src/dapp_opts.rs b/cli/src/forge_opts.rs
similarity index 97%
rename from dapptools/src/dapp_opts.rs
rename to cli/src/forge_opts.rs
index b6b94d7ba366..aa500252966a 100644
--- a/dapptools/src/dapp_opts.rs
+++ b/cli/src/forge_opts.rs
@@ -13,10 +13,12 @@ pub struct Opts {
}
#[derive(Debug, StructOpt)]
+#[structopt(name = "forge")]
#[structopt(about = "Build, test, fuzz, formally verify, debug & deploy solidity contracts.")]
#[allow(clippy::large_enum_variant)]
pub enum Subcommands {
#[structopt(about = "test your smart contracts")]
+ #[structopt(alias = "t")]
Test {
#[structopt(help = "print the test results in json format", long, short)]
json: bool,
@@ -74,10 +76,11 @@ pub enum Subcommands {
#[structopt(help = "enables the FFI cheatcode", long)]
ffi: bool,
- #[structopt(help = "verbosity of 'dapp test' output (0-3)", long, default_value = "0")]
+ #[structopt(help = "verbosity of 'forge test' output (0-3)", long, default_value = "0")]
verbosity: u8,
},
#[structopt(about = "build your smart contracts")]
+ #[structopt(alias = "b")]
Build {
#[structopt(flatten)]
opts: BuildOpts,
@@ -168,8 +171,7 @@ impl std::convert::TryFrom<&BuildOpts> for Project {
/// Defaults to converting to DAppTools-style repo layout, but can be customized.
fn try_from(opts: &BuildOpts) -> eyre::Result {
// 1. Set the root dir
- let root = opts.root.clone().unwrap_or_else(|| std::env::current_dir().unwrap());
- let root = std::fs::canonicalize(root)?;
+ let root = std::fs::canonicalize(&opts.root)?;
// 2. Set the contracts dir
let contracts = if let Some(ref contracts) = opts.contracts {
@@ -233,8 +235,12 @@ impl std::convert::TryFrom<&BuildOpts> for Project {
#[derive(Debug, StructOpt)]
pub struct BuildOpts {
- #[structopt(help = "the project's root path, default being the current directory", long)]
- pub root: Option,
+ #[structopt(
+ help = "the project's root path, default being the current directory",
+ long,
+ default_value = "std::env::current_dir().unwrap()"
+ )]
+ pub root: PathBuf,
#[structopt(
help = "the directory relative to the root under which the smart contrats are",
@@ -246,8 +252,7 @@ pub struct BuildOpts {
#[structopt(help = "the remappings", long, short)]
pub remappings: Vec,
-
- #[structopt(env = "DAPP_REMAPPINGS")]
+ #[structopt(long = "remappings-env", env = "DAPP_REMAPPINGS")]
pub remappings_env: Option,
#[structopt(help = "the paths where your libraries are installed", long)]
diff --git a/dapptools/src/utils.rs b/cli/src/utils.rs
similarity index 100%
rename from dapptools/src/utils.rs
rename to cli/src/utils.rs
diff --git a/dapp/README.md b/dapp/README.md
deleted file mode 100644
index ad0980717a3e..000000000000
--- a/dapp/README.md
+++ /dev/null
@@ -1,167 +0,0 @@
-# `dapp`
-
-## Run Solidity tests
-
-Any contract that contains a function starting with `test` is being tested. The glob
-passed to `--contracts` must be wrapped with quotes so that it gets passed to the internal
-command without being expanded by your shell.
-
-```bash
-$ cargo r --bin dapp test --contracts './**/*.sol'
- Finished dev [unoptimized + debuginfo] target(s) in 0.21s
- Running `target/debug/dapp test --contracts './**/*.sol'`
-Running 1 test for Foo
-[PASS] testX (gas: 267)
-
-Running 1 test for GmTest
-[PASS] testGm (gas: 25786)
-
-Running 1 test for FooBar
-[PASS] testX (gas: 267)
-
-Running 3 tests for GreeterTest
-[PASS] testIsolation (gas: 3702)
-[PASS] testFailGreeting (gas: 26299)
-[PASS] testGreeting (gas: 26223)
-```
-
-You can optionally specify a regular expression, to only run matching functions:
-
-```bash
-$ cargo r --bin dapp test --contracts './**/*.sol' -m testG
- Finished dev [unoptimized + debuginfo] target(s) in 0.26s
- Running `target/debug/dapp test --contracts './**/*.sol' -m testG`
-Running 1 test for GreeterTest
-[PASS] testGreeting (gas: 26223)
-
-Running 1 test for GmTest
-[PASS] testGm (gas: 25786)
-```
-
-### Test output as JSON
-
-In order to compose with other commands, you may print the results as JSON via the `--json` flag
-
-```bash
-$ ./target/release/dapp test -c "./**/*.sol" --json
-{"GreeterTest":{"testIsolation":{"success":true,"gas_used":3702},"testFailGreeting":{"success":true,"gas_used":26299},"testGreeting":{"success":true,"gas_used":26223}},"FooBar":{"testX":{"success":true,"gas_used":267}},"Foo":{"testX":{"success":true,"gas_used":267}},"GmTest":{"testGm":{"success":true,"gas_used":25786}}}
-```
-
-### Build the contracts
-
-You can build the contracts by running, which will by default output the compilation artifacts
-of all contracts under `src/` at `out/dapp.sol.json`:
-
-```bash
-$ ./target/release/dapp build
-```
-
-You can specify an alternative path for your contracts and libraries with `--remappings`, `--lib-path`
-and `--contracts`. We default to importing libraries from `./lib`, but you still need to manually
-set your remappings.
-
-In the example below, we see that this also works for importing libraries from different paths
-(e.g. having a DappTools-style import under `lib/` and an NPM-style import under `node_modules`)
-
-Notably, we need 1 remapping and 1 lib path for each import. Given that this can be tedious,
-you can do set remappings via the env var `DAPP_REMAPPINGS`, by setting your remapping 1 in each line
-
-```bash
-$ dapp build --out out.json \
- --remappings ds-test/=lib/ds-test/src/ \
- --lib-paths ./lib/
- --remappings @openzeppelin/=node_modules/@openzeppelin/ \
- --lib-path ./node_modules/@openzeppelin
-```
-
-
-```bash
-$ echo $DAPP_REMAPPINGS
-@openzeppelin/=lib/openzeppelin-contracts/
-ds-test/=lib/ds-test/src/
-$ dapp build --out out.json \
- --lib-paths ./lib/ \
- --lib-paths ./node_modules/@openzeppelin
-```
-
-### CLI Help
-
-The CLI options can be seen below. You can fully customize the initial blockchain
-context. As an example, if you pass the flag `--block-number`, then the EVM's `NUMBER`
-opcode will always return the supplied value. This can be useful for testing.
-
-
-#### Build
-
-```bash
-$ cargo r --bin dapp build --help
- Compiling dapptools v0.1.0
- Finished dev [unoptimized + debuginfo] target(s) in 3.45s
- Running `target/debug/dapp build --help`
-dapp-build 0.1.0
-build your smart contracts
-
-USAGE:
- dapp build [FLAGS] [OPTIONS] [--] [remappings-env]
-
-FLAGS:
- -h, --help Prints help information
- -n, --no-compile skip re-compilation
- -V, --version Prints version information
-
-OPTIONS:
- -c, --contracts glob path to your smart contracts [default: ./src/**/*.sol]
- --evm-version choose the evm version [default: berlin]
- --lib-path the path where your libraries are installed
- -o, --out path to where the contract artifacts are stored [default: ./out/dapp.sol.json]
- -r, --remappings ... the remappings
-
-ARGS:
- [env: DAPP_REMAPPINGS=]
-```
-
-#### Test
-
-```bash
-$ cargo r --bin dapp test --help
- Finished dev [unoptimized + debuginfo] target(s) in 0.31s
- Running `target/debug/dapp test --help`
-dapp-test 0.1.0
-build your smart contracts
-
-USAGE:
- dapp test [FLAGS] [OPTIONS] [--] [remappings-env]
-
-FLAGS:
- -h, --help Prints help information
- -j, --json print the test results in json format
- -n, --no-compile skip re-compilation
- -V, --version Prints version information
-
-OPTIONS:
- --block-coinbase
- the block.coinbase value during EVM execution [default: 0x0000000000000000000000000000000000000000]
-
- --block-difficulty the block.difficulty value during EVM execution [default: 0]
- --block-gas-limit the block.gaslimit value during EVM execution
- --block-number the block.number value during EVM execution [default: 0]
- --block-timestamp the block.timestamp value during EVM execution [default: 0]
- --chain-id the chainid opcode value [default: 1]
- -c, --contracts glob path to your smart contracts [default: ./src/**/*.sol]
- --evm-version choose the evm version [default: berlin]
- --gas-limit the block gas limit [default: 25000000]
- --gas-price the tx.gasprice value during EVM execution [default: 0]
- --lib-path the path where your libraries are installed
- -o, --out
- path to where the contract artifacts are stored [default: ./out/dapp.sol.json]
-
- -m, --match only run test methods matching regex [default: .*]
- -r, --remappings ... the remappings
- --tx-origin
- the tx.origin value during EVM execution [default: 0x0000000000000000000000000000000000000000]
-
-
-ARGS:
- [env: DAPP_REMAPPINGS=]
-
-```
diff --git a/evm-adapters/Cargo.toml b/evm-adapters/Cargo.toml
index af8da3dd5318..503a4901165d 100644
--- a/evm-adapters/Cargo.toml
+++ b/evm-adapters/Cargo.toml
@@ -7,7 +7,7 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
-dapp-utils = { path = "./../utils" }
+foundry-utils = { path = "./../utils" }
sputnik = { package = "evm", git = "https://github.com/rust-blockchain/evm", optional = true, features = ["tracing"] }
diff --git a/evm-adapters/README.md b/evm-adapters/README.md
new file mode 100644
index 000000000000..6f41fa93653d
--- /dev/null
+++ b/evm-adapters/README.md
@@ -0,0 +1,29 @@
+# evm-adapters
+
+Abstraction over various EVM implementations via the `Evm` trait. Currently
+supported: [Sputnik EVM](https://github.com/rust-blockchain/evm/) and
+[Evmodin](https://github.com/vorot93/evmodin).
+
+Any implementation of the EVM trait receives [fuzzing support](./src/fuzz.rs)
+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.
+
+## Sputnik's Cached Forking backend
+
+When testing, it is frequently a requirement to be able to fetch live state from
+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.
diff --git a/evm-adapters/src/fuzz.rs b/evm-adapters/src/fuzz.rs
index 15a3c3db00c8..498872855f29 100644
--- a/evm-adapters/src/fuzz.rs
+++ b/evm-adapters/src/fuzz.rs
@@ -75,7 +75,7 @@ impl<'a, S, E: Evm> FuzzedExecutor<'a, E, S> {
"{}, expected failure: {}, reason: '{}'",
func.name,
should_fail,
- dapp_utils::decode_revert(returndata.as_ref())?
+ foundry_utils::decode_revert(returndata.as_ref())?
);
Ok(())
diff --git a/evm-adapters/src/lib.rs b/evm-adapters/src/lib.rs
index 5fc38dcfc600..2fab1f8deabd 100644
--- a/evm-adapters/src/lib.rs
+++ b/evm-adapters/src/lib.rs
@@ -17,7 +17,7 @@ use ethers::{
core::types::{Address, Bytes, U256},
};
-use dapp_utils::IntoFunction;
+use foundry_utils::IntoFunction;
use eyre::Result;
use once_cell::sync::Lazy;
@@ -75,7 +75,7 @@ pub trait Evm {
let func = func.into();
let (retdata, status, gas, logs) = self.call_unchecked(from, to, &func, args, value)?;
if Self::is_fail(&status) {
- let reason = dapp_utils::decode_revert(retdata.as_ref()).unwrap_or_default();
+ let reason = foundry_utils::decode_revert(retdata.as_ref()).unwrap_or_default();
Err(EvmError::Execution { reason, gas_used: gas, logs })
} else {
let retdata = decode_function_data(&func, retdata, false)?;
diff --git a/dapp/Cargo.toml b/forge/Cargo.toml
similarity index 94%
rename from dapp/Cargo.toml
rename to forge/Cargo.toml
index cb8e087a771f..d7f09c0b2e28 100644
--- a/dapp/Cargo.toml
+++ b/forge/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "dapp"
+name = "forge"
version = "0.1.0"
edition = "2018"
license = "MIT OR Apache-2.0"
@@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
-dapp-utils = { path = "./../utils" }
+foundry-utils = { path = "./../utils" }
evm-adapters = { path = "./../evm-adapters", default-features = false }
# ethers = { version = "0.5.2" }
diff --git a/forge/README.md b/forge/README.md
new file mode 100644
index 000000000000..f038357f535d
--- /dev/null
+++ b/forge/README.md
@@ -0,0 +1,193 @@
+# `forge`
+
+Forge is a fast and flexible Ethereum testing framework, inspired by
+[Dapp](https://github.com/dapphub/dapptools/tree/master/src/dapp)
+
+If you are looking into how to consume the software as an end user, check the
+[CLI README](../cli/README.md).
+
+For more context on how the package works under the hood, look in the
+[code docs](./src/lib.rs).
+
+## Why?
+
+### Write your tests in Solidity to minimize context switching
+
+Writing tests in Javascript/Typescript while writing your smart contracts in
+Solidity can be confusing. Forge lets you write your tests in Solidity, so you
+can focus on what matters.
+
+```solidity
+contract Foo {
+ uint256 public x = 1;
+ function set(uint256 _x) external {
+ x = _x;
+ }
+
+ function double() external {
+ x = 2 * x;
+ }
+}
+
+contract FooTest {
+ Foo foo;
+
+ // The state of the contract gets reset before each
+ // test is run, with the `setUp()` function being called
+ // each time after deployment.
+ function setUp() public {
+ foo = new Foo();
+ }
+
+ // A simple unit test
+ function testDouble() public {
+ require(foo.x() == 1);
+ foo.double();
+ require(foo.x() == 2);
+ }
+}
+```
+
+### Fuzzing: Go beyond unit testing
+
+When testing smart contracts, fuzzing can uncover edge cases which would be hard
+to manually detect with manual unit testing. We support fuzzing natively, where
+any test function that takes >0 arguments will be fuzzed, using the
+[proptest](https://docs.rs/proptest/1.0.0/proptest/) crate.
+
+An example of how a fuzzed test would look like can be seen below:
+
+```solidity
+function testDoubleWithFuzzing(uint256 x) public {
+ foo.set(x);
+ require(foo.x() == x);
+ foo.double();
+ require(foo.x() == 2 * x);
+}
+```
+
+## Features
+
+- [ ] test
+ - [x] Simple unit tests
+ - [x] Gas costs
+ - [x] DappTools style test output
+ - [x] JSON test output
+ - [x] Matching on regex
+ - [x] DSTest-style assertions support
+ - [x] Fuzzing
+ - [ ] Symbolic execution
+ - [ ] Coverage
+ - [x] HEVM-style Solidity cheatcodes
+ - [ ] Structured tracing with abi decoding
+ - [ ] Per-line gas profiling
+ - [x] Forking mode
+ - [x] Automatic solc selection
+- [x] build
+ - [x] Can read DappTools-style .sol.json artifacts
+ - [x] Manual remappings
+ - [x] Automatic remappings
+ - [x] Multiple compiler versions
+ - [x] Incremental compilation
+ - [ ] Can read Hardhat-style artifacts
+ - [ ] Can read Truffle-style artifacts
+- [x] install
+- [x] update
+- [ ] debug
+- [x] CLI Tracing with `RUST_LOG=forge=trace`
+
+### Cheat codes
+
+_The below is modified from
+[Dapp's README](https://github.com/dapphub/dapptools/blob/master/src/hevm/README.md#cheat-codes)_
+
+We allow modifying blockchain state with "cheat codes". These can be accessed by
+calling into a contract at address `0x7109709ECfa91a80626fF3989D68f67F5b1DD12D`,
+which implements the following methods:
+
+- `function warp(uint x) public` Sets the block timestamp to `x`.
+
+- `function roll(uint x) public` Sets the block number to `x`.
+
+- `function store(address c, bytes32 loc, bytes32 val) public` Sets the slot
+ `loc` of contract `c` to `val`.
+
+- `function load(address c, bytes32 loc) public returns (bytes32 val)` Reads the
+ slot `loc` of contract `c`.
+
+- `function sign(uint sk, bytes32 digest) public returns (uint8 v, bytes32 r, bytes32 s)`
+ Signs the `digest` using the private key `sk`. Note that signatures produced
+ via `hevm.sign` will leak the private key.
+
+- `function addr(uint sk) public returns (address addr)` Derives an ethereum
+ address from the private key `sk`. Note that `hevm.addr(0)` will fail with
+ `BadCheatCode` as `0` is an invalid ECDSA private key.
+
+- `function ffi(string[] calldata) external returns (bytes memory)` Executes the
+ arguments as a command in the system shell and returns stdout. Note that this
+ cheatcode means test authors can execute arbitrary code on user machines as
+ part of a call to `dapp test`, for this reason all calls to `ffi` will fail
+ unless the `--ffi` flag is passed.
+
+- `function deal(address who, uint256 amount)`: Sets an account's balance
+
+- `function etch(address where, bytes memory what)`:` Sets the contract code at
+ some address contract code
+
+- `function prank(address from, address to, bytes calldata) (bool success,bytes retdata)`:
+ Performs a smart contract call as another address
+
+The below example uses the `warp` cheatcode to override the timestamp:
+
+```solidity
+interface Vm {
+ function warp(uint256 x) external;
+}
+
+contract MyTest {
+ Vm vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
+
+ function testWarp() public {
+ vm.warp(100);
+ require(block.timestamp == 100);
+ }
+```
+
+## Future Features
+
+### Dapptools feature parity
+
+Over the next months, we intend to add the following features which are
+available in upstream dapptools:
+
+1. Stack Traces: Currently we do not provide any debug information when a call
+ fails. We intend to add a structured printer (something like
+ [this](https://twitter.com/gakonst/status/1434337110111182848) which will
+ show all the calls, logs and arguments passed across intermediate smart
+ contract calls, which should help with debugging.
+1. [Invariant Tests](https://github.com/dapphub/dapptools/blob/master/src/dapp/README.md#invariant-testing)
+1. [Interactive Debugger](https://github.com/dapphub/dapptools/blob/master/src/hevm/README.md#interactive-debugger-key-bindings)
+1. [Code coverage](https://twitter.com/dapptools/status/1435973810545729536)
+1. [Gas snapshots](https://github.com/dapphub/dapptools/pull/850/files)
+1. [Symbolic EVM](https://fv.ethereum.org/2020/07/28/symbolic-hevm-release/)
+
+### Unique features?
+
+We also intend to add features which are not available in dapptools:
+
+1. Even faster tests with parallel EVM execution that produces state diffs
+ instead of modifying the state
+1. Improved UX for assertions:
+ 1. Check revert error or reason on a Solidity call
+ 1. Check that an event was emitted with expected arguments
+1. Support more EVM backends ([revm](https://github.com/bluealloy/revm/), geth's
+ evm, hevm etc.) & benchmark performance across them
+1. Declarative deployment system based on a config file
+1. Formatting & Linting (maybe powered by
+ [Solang](https://github.com/hyperledger-labs/solang))
+ 1. `dapp fmt`, an automatic code formatter according to standard rules (like
+ [`prettier-plugin-solidity`](https://github.com/prettier-solidity/prettier-plugin-solidity))
+ 1. `dapp lint`, a linter + static analyzer, like a combination of
+ [`solhint`](https://github.com/protofire/solhint) and
+ [slither](https://github.com/crytic/slither/)
+1. Flamegraphs for gas profiling
diff --git a/dapp/src/lib.rs b/forge/src/lib.rs
similarity index 100%
rename from dapp/src/lib.rs
rename to forge/src/lib.rs
diff --git a/dapp/src/multi_runner.rs b/forge/src/multi_runner.rs
similarity index 100%
rename from dapp/src/multi_runner.rs
rename to forge/src/multi_runner.rs
diff --git a/dapp/src/runner.rs b/forge/src/runner.rs
similarity index 99%
rename from dapp/src/runner.rs
rename to forge/src/runner.rs
index 635cfe4522a0..8b8396d3a39a 100644
--- a/dapp/src/runner.rs
+++ b/forge/src/runner.rs
@@ -244,11 +244,11 @@ mod tests {
mod sputnik {
use std::str::FromStr;
- use dapp_utils::get_func;
use evm_adapters::sputnik::{
helpers::{new_backend, new_vicinity},
Executor,
};
+ use foundry_utils::get_func;
use proptest::test_runner::Config as FuzzConfig;
use super::*;
diff --git a/dapp/testdata/DebugLogsTest.sol b/forge/testdata/DebugLogsTest.sol
similarity index 100%
rename from dapp/testdata/DebugLogsTest.sol
rename to forge/testdata/DebugLogsTest.sol
diff --git a/dapp/testdata/FooTest.sol b/forge/testdata/FooTest.sol
similarity index 100%
rename from dapp/testdata/FooTest.sol
rename to forge/testdata/FooTest.sol
diff --git a/dapp/testdata/FooTest2.sol b/forge/testdata/FooTest2.sol
similarity index 100%
rename from dapp/testdata/FooTest2.sol
rename to forge/testdata/FooTest2.sol
diff --git a/dapp/testdata/GreetTest.sol b/forge/testdata/GreetTest.sol
similarity index 100%
rename from dapp/testdata/GreetTest.sol
rename to forge/testdata/GreetTest.sol
diff --git a/dapp/testdata/dapp-artifact.json b/forge/testdata/dapp-artifact.json
similarity index 100%
rename from dapp/testdata/dapp-artifact.json
rename to forge/testdata/dapp-artifact.json
diff --git a/utils/Cargo.toml b/utils/Cargo.toml
index 4c2eee5fd8e9..66cdc4f1c690 100644
--- a/utils/Cargo.toml
+++ b/utils/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "dapp-utils"
+name = "foundry-utils"
version = "0.1.0"
edition = "2018"
license = "MIT OR Apache-2.0"
diff --git a/utils/README.md b/utils/README.md
new file mode 100644
index 000000000000..45334f7718af
--- /dev/null
+++ b/utils/README.md
@@ -0,0 +1,3 @@
+# foundry-utils
+
+Helper methods for interacting with EVM ABIs
diff --git a/utils/src/lib.rs b/utils/src/lib.rs
index 4a4c1c624e97..9d7effffe553 100644
--- a/utils/src/lib.rs
+++ b/utils/src/lib.rs
@@ -1,3 +1,4 @@
+#![doc = include_str!("../README.md")]
use ethers_core::{
abi::{
self, parse_abi,
@@ -42,6 +43,8 @@ impl<'a> IntoFunction for &'a str {
}
}
+/// Given a gas value and a calldata array, it subtracts the calldata cost from the
+/// gas value, as well as the 21k base gas cost for all transactions.
pub fn remove_extra_costs(gas: U256, calldata: &[u8]) -> U256 {
let mut calldata_cost = 0;
for i in calldata {
@@ -55,6 +58,8 @@ pub fn remove_extra_costs(gas: U256, calldata: &[u8]) -> U256 {
gas - calldata_cost - BASE_TX_COST
}
+/// Given an ABI encoded error string with the function signature `Error(string)`, it decodes
+/// it and returns the revert error message.
pub fn decode_revert(error: &[u8]) -> std::result::Result {
let error = error.strip_prefix(ðers_core::utils::id("Error(string)")).unwrap_or(error);
if !error.is_empty() {
@@ -64,6 +69,7 @@ pub fn decode_revert(error: &[u8]) -> std::result::Result String {
match value {
serde_json::Value::String(s) => s,
@@ -78,6 +84,7 @@ pub fn to_table(value: serde_json::Value) -> String {
}
}
+/// Given a function signature string, it tries to parse it as a `Function`
pub fn get_func(sig: &str) -> Result {
// TODO: Make human readable ABI better / more minimal
let abi = parse_abi(&[sig])?;
@@ -106,6 +113,8 @@ pub fn parse_tokens<'a, I: IntoIterator>(
.wrap_err("Failed to parse tokens")
}
+/// Given a function and a vector of string arguments, it proceeds to convert the args to ethabi
+/// Tokens and then ABI encode them.
pub fn encode_args(func: &Function, args: &[impl AsRef]) -> Result> {
let params = func
.inputs