Skip to content

Commit

Permalink
refactor: move to revm (#918)
Browse files Browse the repository at this point in the history
* Simple REVM test runner (#788)

* refactor: nuke `evm-adapters`

* refactor: simple revm test runner

Current features:

- Can run unit tests
- Works with both revert-type tests and DSTest-type tests
- Collects logs, albeit not for reverting tests
- Integrated with config and CLI flags

Disabled features:

- Gas reports
- Tracing
- Cheatcodes
- Fuzzing
- Log decoding
- Forking mode
- Hardhat-style `console.log`, since those require
  us to decode calls to a specific address (HH does
  not emit logs)
- The debugger

In addition to this, I've disabled some tests that
could never pass under the current circumstances,
but that should be adjusted and re-enabled when their
respective features are implemented (such as fuzz tests)

* refactor: adjust CLI to new runner API

* feat: log collector inspector

* feat: hardhat logs

* chore: lint

* refactor: extract hh log converter to helper fn

* refactor: return single test result if setup fails

* build: use upstream revm

chore: renuke `evm-adapters`

* REVM fuzzer (#789)

* REVM cheatcodes (#841)

* feat: add `InspectorStack`

Adds `InspectorStack`, an inspector that calls a stack
of other inspectors sequentially.

Closes #752

* feat: port cheatcodes to revm

* feat: port `expectCall` cheatcode

* feat: extract labels from cheatcode inspector

* feat: port `expectEmit` cheatcode

* refactor: move log decoding into `forge` crate

* chore: remove unused evm patch

* test: re-enable debug logs test

* fix: record reads on `SSTORE` ops

* refactor: rename `record` to `start_record`

* docs: clarify why `DUMMY_CALL_OUTPUT` is 320 bytes

* fix: handle `expectRevert` with no return data

* build: bump revm

* chore: remove outdated todo

* refactor: use static dispatch in `InspectorStack`

* build: use k256

* fix: make gas usage not so crazy

* feat(revm): add forking mode (#835)

* feat: copy-paste old forking provider

* feat(fork): convert to REVM traits

* chore: remove unnecessary codehash handler

* feat: impl Database for shared backend

* chore: fix tests

* chore: fmt

* fix(fork): correctly convert H256 <> U256 for storage

* refactor: separate storage from accounts in cache

* feat(fork): fetch block hashes

* chore: remove unused DB parameter

* test: add test for block hashes

* feat: add forked backend to executor builder

* feat(cli): set fork url on the executor

* refactor: move shared backend to separate file

* feat(fork): add fn for instantiating forked env

* feat(cli): allow pinning block number

* fix(fork): install missing listeners

* feat(fork): instantiate environment with forked state

* fix: use a CALLER address with maxed out balance for calls

this is required because in forking mode otherwise the account wont have enough balance
to transact

* chore: fmt

Co-authored-by: Oliver Nordbjerg <hi@notbjerg.me>

* chore: fmt

* REVM tracing and gas reports (#867)

* feat: very simple traces

* feat: creation traces

* feat: setup and revert traces

* fix: fix lib addresses

* refactor: simplify tracer inspector

* fix: fill traces in correct order

* build: bump revm

* fix: get code for newly created contracts

* refactor: unify log extraction logic

* feat: trace logs

* refactor: unify labels and names

* refactor: return string from trace

Instead of passing in an empty string we then pass
around inside the trace display logic, we just return
strings where appropriate.

* refactor: remove identified contracts

* refactor: remove unused vars

* refactor: simplify `construct_func_call`

* refactor: name special characters in traces

* refactor: rework all display logic

* feat: first pass identify/decode for traces

* refactor: move tracing to own module

* refactor: simplify `test`

* feat: traces for fuzz tests

* fix: make fuzz revert reasons less verbose

* feat: port gas reports

* refactor: small readability nits

* feat: run fuzz *and* unit tests in parallel

Previously we would run each test contract in parallel,
but within each `ContractRunner` we would run unit tests
first (in parallel) and then fuzz tests (in parallel).

* refactor: move colouring logic to its own function

* fix: test contract identification

We now include three kinds of traces that are used for
identification of contracts:

- Deployment traces: these are the initial deployments
  of the test contract and libraries
- Setup traces: these are traces of calls to the `setUp`
  function
- Execution traces: these are the traces of calls to
  the test contract itself

* fix: mark setup trace as a setup trace

* fix: get correct nonce in tracer

* fix: log extraction outside of current memory

* chore: clean up complex types

* chore: remove outdated comment

* fix: make tests compile

* fix: add missing test filter function

* feat: display full address in traces

* fix: color "new" keyword in traces

* fix: filter out `console.log` calls from traces

* chore: remove unnecessary comment

* feat: add gas cost to creation traces

* fix: properly decode outputs

* refactor: destructure `TestSetup` in test funcs

* fix: ignore address for func output decoding

* fix: fix expect emit

Co-authored-by: Georgios Konstantopoulos <me@gakonst.com>
Co-authored-by: brockelmore <brockelmore@users.noreply.github.com>

* REVM debugger (#920)

* feat: port debugger data structures

* feat: initial port of `ui` crate

* chore: add `ui` crate as a workspace member

* refactor: adjust ui contract identification

* feat: grey out 0 values in debugger memory

Closes #902

* style: minor debugger ui beautification

* feat: better stack display in debugger ui

* feat: gray out zero bytes in stack view

* feat: debugger inspector

* refactor: minor code cleanup

* feat: port `forge run`

* fix: temp fix for failing `DsTest.sol` include

* chore: fix lints

* test: adjust `forge run` tests

* refactor: use simple bool for revert checks

* chore: remove unused display impl

* chore: remove unused comment

* fix: display number of stack items in ui

* docs: prettify cli help for some commands

* feat: `forge test --debug`

* refactor: `get_create_address` util

* refactor: `InspectorData`

* docs: more detailed err for `forge test --debug`

* feat: support hardhat artifacts in `vm.getCode` (#956)

Ports #903

* REVM: FFI cheatcode updates (#955)

* feat: only strip 0x in ffi output if present

Ports #904

* Update forge/src/executor/inspector/cheatcodes/ext.rs

Co-authored-by: Georgios Konstantopoulos <me@gakonst.com>

* REVM gas fixes (#950)

* feat: account for gas refunds

* refactor: merge `call_raw` and committing variant

* fix: actually use refund quotient

* feat: strip tx gas stipend

* fix: fix reported gas usage in debugger

* build: use upstream revm

* test: adjust `forge run` gas values in tests

* chore: remove unused copy

* chore: add note on push maths

* feat: make stipend reduction optional

* fix: remove tx stipend in `forge run`

* REVM: Pull EVM executor into own crate (#961)

* refactor: move evm executor to own crate

* refactor: `evm::executor::fuzz` -> `evm::fuzz`

* refactor: `evm::debugger` -> `evm::debug`

* test: fix multi runner test

* feat: better ux for expect revert without reason (#962)

* Cross-crate testdata (#965)

* feat: cross-crate shared testdata

* refactor: move `foundry-utils` to common tests

* fix: fix getcode test

* fix: compile once in tests

* fix: fix prank cheatcode (#973)

Correctly apply `msg.sender` prank to both transfers
and calls.

* fix: prank depth math

* test: fix lib linking test

* refactor: use revm `log` hook (#984)

* refactor: use revm `log` hook

* chore: bump revm

Co-authored-by: Georgios Konstantopoulos <me@gakonst.com>

* test: add lil-web3 to integration tests

* test: add maple labs loans to integration tests

Closes #959

* REVM fuzz dictionary (#985)

* feat: fuzz dictionary

Co-authored-by: brockelmore <31553173+brockelmore@users.noreply.github.com>

* fix: handle malformed bytecode

* fix: limit search for push bytes

* feat: collect fuzz state from logs

* feat: build initial fuzz state from db

* perf: use `Index` instead of `Selector`

Co-authored-by: brockelmore <31553173+brockelmore@users.noreply.github.com>

* feat(cli): Refactor cli/cmd over forge and cast (#1009)

* ⚙️ refactor cli

* 🧪 refactor casts

* REVM: Support cheatcodes in `setUp` (#997)

* fix: support cheatcodes in `setUp`

* fix: subtract stipend without panic

* chore: rename test

* fix: set tx gas price to block basefee

* fix: use `CALLER` for `is_success` check

* chore: remove duplicate clap attribute

* fix: set chain id correctly in fork mode

* fix: separate evm block env from execution env

* chore: clippy

* refactor: block override without `block_env` fn

* test: explain why git clone failed

* test: disable maple-labs/loan

* refactor: make addresses statics instead of lazies

* docs: fix console address comment

* refactor: make `DUMMY_CREATE_ADDRESS` a static

* chore: minor nits

* refactor: move inspector state collection

* fix: report correct fuzz failure case (#1017)

* fix: report correct fuzz failure case

* docs: improve some docs in fuzzer

* feat: add support for storage caching (#1006)

* Simple REVM test runner (#788)

* refactor: nuke `evm-adapters`

* refactor: simple revm test runner

Current features:

- Can run unit tests
- Works with both revert-type tests and DSTest-type tests
- Collects logs, albeit not for reverting tests
- Integrated with config and CLI flags

Disabled features:

- Gas reports
- Tracing
- Cheatcodes
- Fuzzing
- Log decoding
- Forking mode
- Hardhat-style `console.log`, since those require
  us to decode calls to a specific address (HH does
  not emit logs)
- The debugger

In addition to this, I've disabled some tests that
could never pass under the current circumstances,
but that should be adjusted and re-enabled when their
respective features are implemented (such as fuzz tests)

* refactor: adjust CLI to new runner API

* feat: log collector inspector

* feat: hardhat logs

* chore: lint

* refactor: extract hh log converter to helper fn

* refactor: return single test result if setup fails

* build: use upstream revm

chore: renuke `evm-adapters`

* REVM fuzzer (#789)

* REVM cheatcodes (#841)

* feat: add `InspectorStack`

Adds `InspectorStack`, an inspector that calls a stack
of other inspectors sequentially.

Closes #752

* feat: port cheatcodes to revm

* feat: port `expectCall` cheatcode

* feat: extract labels from cheatcode inspector

* feat: port `expectEmit` cheatcode

* refactor: move log decoding into `forge` crate

* chore: remove unused evm patch

* test: re-enable debug logs test

* fix: record reads on `SSTORE` ops

* refactor: rename `record` to `start_record`

* docs: clarify why `DUMMY_CALL_OUTPUT` is 320 bytes

* fix: handle `expectRevert` with no return data

* build: bump revm

* chore: remove outdated todo

* refactor: use static dispatch in `InspectorStack`

* build: use k256

* fix: make gas usage not so crazy

* feat(revm): add forking mode (#835)

* feat: copy-paste old forking provider

* feat(fork): convert to REVM traits

* chore: remove unnecessary codehash handler

* feat: impl Database for shared backend

* chore: fix tests

* chore: fmt

* fix(fork): correctly convert H256 <> U256 for storage

* refactor: separate storage from accounts in cache

* feat(fork): fetch block hashes

* chore: remove unused DB parameter

* test: add test for block hashes

* feat: add forked backend to executor builder

* feat(cli): set fork url on the executor

* refactor: move shared backend to separate file

* feat(fork): add fn for instantiating forked env

* feat(cli): allow pinning block number

* fix(fork): install missing listeners

* feat(fork): instantiate environment with forked state

* fix: use a CALLER address with maxed out balance for calls

this is required because in forking mode otherwise the account wont have enough balance
to transact

* chore: fmt

Co-authored-by: Oliver Nordbjerg <hi@notbjerg.me>

* chore: fmt

* REVM tracing and gas reports (#867)

* feat: very simple traces

* feat: creation traces

* feat: setup and revert traces

* fix: fix lib addresses

* refactor: simplify tracer inspector

* fix: fill traces in correct order

* build: bump revm

* fix: get code for newly created contracts

* refactor: unify log extraction logic

* feat: trace logs

* refactor: unify labels and names

* refactor: return string from trace

Instead of passing in an empty string we then pass
around inside the trace display logic, we just return
strings where appropriate.

* refactor: remove identified contracts

* refactor: remove unused vars

* refactor: simplify `construct_func_call`

* refactor: name special characters in traces

* refactor: rework all display logic

* feat: first pass identify/decode for traces

* refactor: move tracing to own module

* refactor: simplify `test`

* feat: traces for fuzz tests

* fix: make fuzz revert reasons less verbose

* feat: port gas reports

* refactor: small readability nits

* feat: run fuzz *and* unit tests in parallel

Previously we would run each test contract in parallel,
but within each `ContractRunner` we would run unit tests
first (in parallel) and then fuzz tests (in parallel).

* refactor: move colouring logic to its own function

* fix: test contract identification

We now include three kinds of traces that are used for
identification of contracts:

- Deployment traces: these are the initial deployments
  of the test contract and libraries
- Setup traces: these are traces of calls to the `setUp`
  function
- Execution traces: these are the traces of calls to
  the test contract itself

* fix: mark setup trace as a setup trace

* fix: get correct nonce in tracer

* fix: log extraction outside of current memory

* chore: clean up complex types

* chore: remove outdated comment

* fix: make tests compile

* fix: add missing test filter function

* feat: display full address in traces

* fix: color "new" keyword in traces

* fix: filter out `console.log` calls from traces

* chore: remove unnecessary comment

* feat: add gas cost to creation traces

* fix: properly decode outputs

* refactor: destructure `TestSetup` in test funcs

* fix: ignore address for func output decoding

* fix: fix expect emit

Co-authored-by: Georgios Konstantopoulos <me@gakonst.com>
Co-authored-by: brockelmore <brockelmore@users.noreply.github.com>

* REVM debugger (#920)

* feat: port debugger data structures

* feat: initial port of `ui` crate

* chore: add `ui` crate as a workspace member

* refactor: adjust ui contract identification

* feat: grey out 0 values in debugger memory

Closes #902

* style: minor debugger ui beautification

* feat: better stack display in debugger ui

* feat: gray out zero bytes in stack view

* feat: debugger inspector

* refactor: minor code cleanup

* feat: port `forge run`

* fix: temp fix for failing `DsTest.sol` include

* chore: fix lints

* test: adjust `forge run` tests

* refactor: use simple bool for revert checks

* chore: remove unused display impl

* chore: remove unused comment

* fix: display number of stack items in ui

* docs: prettify cli help for some commands

* feat: `forge test --debug`

* refactor: `get_create_address` util

* refactor: `InspectorData`

* docs: more detailed err for `forge test --debug`

* feat: support hardhat artifacts in `vm.getCode` (#956)

Ports #903

* REVM: FFI cheatcode updates (#955)

* feat: only strip 0x in ffi output if present

Ports #904

* Update forge/src/executor/inspector/cheatcodes/ext.rs

Co-authored-by: Georgios Konstantopoulos <me@gakonst.com>

* REVM gas fixes (#950)

* feat: account for gas refunds

* refactor: merge `call_raw` and committing variant

* fix: actually use refund quotient

* feat: strip tx gas stipend

* fix: fix reported gas usage in debugger

* build: use upstream revm

* test: adjust `forge run` gas values in tests

* chore: remove unused copy

* chore: add note on push maths

* feat: make stipend reduction optional

* fix: remove tx stipend in `forge run`

* REVM: Pull EVM executor into own crate (#961)

* refactor: move evm executor to own crate

* refactor: `evm::executor::fuzz` -> `evm::fuzz`

* refactor: `evm::debugger` -> `evm::debug`

* test: fix multi runner test

* feat: better ux for expect revert without reason (#962)

* Cross-crate testdata (#965)

* feat: cross-crate shared testdata

* refactor: move `foundry-utils` to common tests

* fix: fix getcode test

* fix: compile once in tests

* fix: fix prank cheatcode (#973)

Correctly apply `msg.sender` prank to both transfers
and calls.

* fix: prank depth math

* test: fix lib linking test

* refactor: use revm `log` hook (#984)

* refactor: use revm `log` hook

* chore: bump revm

Co-authored-by: Georgios Konstantopoulos <me@gakonst.com>

* test: add lil-web3 to integration tests

* test: add maple labs loans to integration tests

Closes #959

* REVM fuzz dictionary (#985)

* feat: fuzz dictionary

Co-authored-by: brockelmore <31553173+brockelmore@users.noreply.github.com>

* fix: handle malformed bytecode

* fix: limit search for push bytes

* feat: collect fuzz state from logs

* feat: build initial fuzz state from db

* perf: use `Index` instead of `Selector`

Co-authored-by: brockelmore <31553173+brockelmore@users.noreply.github.com>

* feat(config): add caching settings

* feat: add none option

* feat: add foundry data dir

* feat: add storage map support

* bump ethers

* chore(clippy): make clippy happy

* refactor: diskmap

* feat: add rpc caching support

* feat: add no storage cache option

* refactor: rename cnfig value

* docs: more storage caching docs

* fix: with config builder function

* refactor: address review

Co-authored-by: Bjerg <onbjerg@users.noreply.github.com>
Co-authored-by: Georgios Konstantopoulos <me@gakonst.com>
Co-authored-by: Oliver Nordbjerg <hi@notbjerg.me>
Co-authored-by: brockelmore <brockelmore@users.noreply.github.com>
Co-authored-by: brockelmore <31553173+brockelmore@users.noreply.github.com>

* fix: default to 80m gas

* fix(evm): gracefully shutdown backendhandler (#1021)

* feat(evm/cache): improve json file caching (#1025)

* feat(cache): proper json cache

* refactor: use new db types

* chore(clippy): make clippy happy

* bump revm

* docs: some docs

* refactor: extend Fork type

* remove diskmap types

* test: refactor tests

* remove sharedmemcache

* add tests

* more tracing

* chore(clippy): make clippy happy

* release: 0.2.0

Co-authored-by: Georgios Konstantopoulos <me@gakonst.com>
Co-authored-by: brockelmore <brockelmore@users.noreply.github.com>
Co-authored-by: brockelmore <31553173+brockelmore@users.noreply.github.com>
Co-authored-by: abigger87 <abigger87@gmail.com>
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
  • Loading branch information
6 people authored Mar 23, 2022
1 parent 22323e2 commit 33f1df0
Show file tree
Hide file tree
Showing 168 changed files with 11,044 additions and 12,431 deletions.
419 changes: 128 additions & 291 deletions Cargo.lock

Large diffs are not rendered by default.

8 changes: 2 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
[workspace]
members = [
"evm-adapters",
"utils",
"cast",
"forge",
"cli",
"cli/test-utils",
"config",
"fmt",
"ui",
"evm"
]

[profile.test]
Expand All @@ -27,11 +28,6 @@ panic = "abort"
# We end up stripping away these symbols anyway
debug = 0

## Patch sputnik with more recent primitive types
# https://github.com/rust-blockchain/evm/pulls
[patch."https://github.com/rust-blockchain/evm"]
evm = { git = "https://github.com/gakonst/evm", branch = "bump-primitive-types" }

## Patch ethers-rs with a local checkout then run `cargo update -p ethers`
#[patch."https://github.com/gakonst/ethers-rs"]
#ethers = { path = "../ethers-rs" }
Expand Down
2 changes: 1 addition & 1 deletion cast/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cast"
version = "0.1.0"
version = "0.2.0"
edition = "2021"
license = "MIT OR Apache-2.0"

Expand Down
17 changes: 4 additions & 13 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "foundry-cli"
version = "0.1.0"
version = "0.2.0"
edition = "2021"
license = "MIT OR Apache-2.0"

Expand All @@ -26,7 +26,6 @@ foundry-utils = { path = "../utils" }
forge = { path = "../forge" }
foundry-config = { path = "../config" }
cast = { path = "../cast" }
evm-adapters = { path = "../evm-adapters" }
ui = { path = "../ui" }
dunce = "1.0.2"
# ethers = "0.5"
Expand All @@ -39,16 +38,14 @@ tokio = { version = "1.11.0", features = ["macros"] }
regex = { version = "1.5.4", default-features = false }
ansi_term = "0.12.1"
rpassword = "5.0.1"
tracing-subscriber = "0.2.20"
tracing-error = "0.2.0"
tracing-subscriber = { version = "0.3", features = ["registry", "env-filter", "fmt"] }
tracing = "0.1.26"
hex = "0.4.3"
rayon = "1.5.1"
serde = "1.0.133"
futures = "0.3.17"

## EVM Implementations
# evm = { version = "0.30.1" }
sputnik = { package = "evm", git = "https://github.com/rust-blockchain/evm", default-features = false, features = ["std"], optional = true }
proptest = "1.0.0"
glob = "0.3.0"
semver = "1.0.5"
Expand All @@ -67,17 +64,11 @@ pretty_assertions = "1.0.0"
toml = "0.5"

[features]
default = ["sputnik-evm", "rustls"]
default = ["rustls"]
solc-asm = ["ethers/solc-sha2-asm"]
rustls = ["ethers/rustls"]
openssl = ["ethers/openssl"]

sputnik-evm = [
"sputnik",
"evm-adapters/sputnik",
"evm-adapters/sputnik-helpers",
]

integration-tests = []

[[bin]]
Expand Down
64 changes: 3 additions & 61 deletions cli/src/cast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use ethers::{
},
providers::{Middleware, Provider},
signers::{LocalWallet, Signer},
types::{Address, Chain, NameOrAddress, Signature, U256, U64},
types::{Address, Chain, NameOrAddress, Signature, U256},
utils::get_contract_address,
};
use opts::{
Expand All @@ -36,9 +36,8 @@ use std::{
use clap::{IntoApp, Parser};
use clap_complete::generate;

use crate::utils::read_secret;
use crate::{cmd::Cmd, utils::read_secret};
use eyre::WrapErr;
use futures::join;

#[tokio::main]
async fn main() -> eyre::Result<()> {
Expand Down Expand Up @@ -542,64 +541,7 @@ async fn main() -> eyre::Result<()> {
let selector = contract.abi().functions().last().unwrap().short_signature();
println!("0x{}", hex::encode(selector));
}
Subcommands::FindBlock { timestamp, rpc_url } => {
let ts_target = U256::from(timestamp);
let provider = Provider::try_from(rpc_url)?;
let last_block_num = provider.get_block_number().await?;
let cast_provider = Cast::new(provider);

let res = join!(cast_provider.timestamp(last_block_num), cast_provider.timestamp(1));
let ts_block_latest = res.0.unwrap();
let ts_block_1 = res.1.unwrap();

let block_num = if ts_block_latest.lt(&ts_target) {
// If the most recent block's timestamp is below the target, return it
last_block_num
} else if ts_block_1.gt(&ts_target) {
// If the target timestamp is below block 1's timestamp, return that
U64::from(1)
} else {
// Otherwise, find the block that is closest to the timestamp
let mut low_block = U64::from(1); // block 0 has a timestamp of 0: https://github.com/ethereum/go-ethereum/issues/17042#issuecomment-559414137
let mut high_block = last_block_num;
let mut matching_block: Option<U64> = None;
while high_block.gt(&low_block) && matching_block.is_none() {
// Get timestamp of middle block (this approach approach to avoids overflow)
let high_minus_low_over_2 = high_block
.checked_sub(low_block)
.ok_or_else(|| eyre::eyre!("unexpected underflow"))
.unwrap()
.checked_div(U64::from(2))
.unwrap();
let mid_block = high_block.checked_sub(high_minus_low_over_2).unwrap();
let ts_mid_block = cast_provider.timestamp(mid_block).await?;

// Check if we've found a match or should keep searching
if ts_mid_block.eq(&ts_target) {
matching_block = Some(mid_block)
} else if high_block.checked_sub(low_block).unwrap().eq(&U64::from(1)) {
// The target timestamp is in between these blocks. This rounds to the
// highest block if timestamp is equidistant between blocks
let res = join!(
cast_provider.timestamp(high_block),
cast_provider.timestamp(low_block)
);
let ts_high = res.0.unwrap();
let ts_low = res.1.unwrap();
let high_diff = ts_high.checked_sub(ts_target).unwrap();
let low_diff = ts_target.checked_sub(ts_low).unwrap();
let is_low = low_diff.lt(&high_diff);
matching_block = if is_low { Some(low_block) } else { Some(high_block) }
} else if ts_mid_block.lt(&ts_target) {
low_block = mid_block;
} else {
high_block = mid_block;
}
}
matching_block.unwrap_or(low_block)
};
println!("{}", block_num);
}
Subcommands::FindBlock(cmd) => cmd.run()?,
Subcommands::Wallet { command } => match command {
WalletSubcommands::New { path, password, unsafe_password } => {
let mut rng = thread_rng();
Expand Down
90 changes: 90 additions & 0 deletions cli/src/cmd/cast/find_block.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//! cast find-block subcommand

use crate::cmd::Cmd;
use cast::Cast;
use clap::Parser;
use ethers::prelude::*;
use eyre::Result;
use futures::join;

#[derive(Debug, Clone, Parser)]
pub struct FindBlockArgs {
#[clap(help = "The UNIX timestamp to search for (in seconds)")]
timestamp: u64,
#[clap(long, env = "ETH_RPC_URL")]
rpc_url: String,
}

impl Cmd for FindBlockArgs {
type Output = ();

fn run(self) -> Result<Self::Output> {
let FindBlockArgs { timestamp, rpc_url } = self;
let rt = tokio::runtime::Runtime::new().expect("could not start tokio rt");
rt.block_on(Self::query_block(timestamp, rpc_url))?;
Ok(())
}
}

impl FindBlockArgs {
async fn query_block(timestamp: u64, rpc_url: String) -> Result<()> {
let ts_target = U256::from(timestamp);
let provider = Provider::try_from(rpc_url)?;
let last_block_num = provider.get_block_number().await?;
let cast_provider = Cast::new(provider);

let res = join!(cast_provider.timestamp(last_block_num), cast_provider.timestamp(1));
let ts_block_latest = res.0.unwrap();
let ts_block_1 = res.1.unwrap();

let block_num = if ts_block_latest.lt(&ts_target) {
// If the most recent block's timestamp is below the target, return it
last_block_num
} else if ts_block_1.gt(&ts_target) {
// If the target timestamp is below block 1's timestamp, return that
U64::from(1_u64)
} else {
// Otherwise, find the block that is closest to the timestamp
let mut low_block = U64::from(1_u64); // block 0 has a timestamp of 0: https://github.com/ethereum/go-ethereum/issues/17042#issuecomment-559414137
let mut high_block = last_block_num;
let mut matching_block: Option<U64> = None;
while high_block.gt(&low_block) && matching_block.is_none() {
// Get timestamp of middle block (this approach approach to avoids overflow)
let high_minus_low_over_2 = high_block
.checked_sub(low_block)
.ok_or_else(|| eyre::eyre!("unexpected underflow"))
.unwrap()
.checked_div(U64::from(2_u64))
.unwrap();
let mid_block = high_block.checked_sub(high_minus_low_over_2).unwrap();
let ts_mid_block = cast_provider.timestamp(mid_block).await?;

// Check if we've found a match or should keep searching
if ts_mid_block.eq(&ts_target) {
matching_block = Some(mid_block)
} else if high_block.checked_sub(low_block).unwrap().eq(&U64::from(1_u64)) {
// The target timestamp is in between these blocks. This rounds to the
// highest block if timestamp is equidistant between blocks
let res = join!(
cast_provider.timestamp(high_block),
cast_provider.timestamp(low_block)
);
let ts_high = res.0.unwrap();
let ts_low = res.1.unwrap();
let high_diff = ts_high.checked_sub(ts_target).unwrap();
let low_diff = ts_target.checked_sub(ts_low).unwrap();
let is_low = low_diff.lt(&high_diff);
matching_block = if is_low { Some(low_block) } else { Some(high_block) }
} else if ts_mid_block.lt(&ts_target) {
low_block = mid_block;
} else {
high_block = mid_block;
}
}
matching_block.unwrap_or(low_block)
};
println!("{}", block_num);

Ok(())
}
}
8 changes: 8 additions & 0 deletions cli/src/cmd/cast/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//! Subcommands for cast
//!
//! All subcommands should respect the `foundry_config::Config`.
//! If a subcommand accepts values that are supported by the `Config`, then the subcommand should
//! implement `figment::Provider` which allows the subcommand to override the config's defaults, see
//! [`foundry_config::Config`].

pub mod find_block;
2 changes: 1 addition & 1 deletion cli/src/cmd/bind.rs → cli/src/cmd/forge/bind.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::cmd::Cmd;
use crate::cmd::utils::Cmd;

use clap::{Parser, ValueHint};
use ethers::contract::MultiAbigen;
Expand Down
6 changes: 3 additions & 3 deletions cli/src/cmd/build.rs → cli/src/cmd/forge/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::path::PathBuf;

use crate::{cmd::Cmd, opts::forge::CompilerArgs};

use crate::cmd::watch::WatchArgs;
use crate::cmd::forge::watch::WatchArgs;
use clap::{Parser, ValueHint};
use ethers::solc::remappings::Remapping;
use foundry_config::{
Expand Down Expand Up @@ -191,7 +191,7 @@ impl Cmd for BuildArgs {
type Output = ProjectCompileOutput;
fn run(self) -> eyre::Result<Self::Output> {
let project = self.project()?;
super::compile(&project, self.names, self.sizes)
crate::cmd::utils::compile(&project, self.names, self.sizes)
}
}

Expand All @@ -214,7 +214,7 @@ impl BuildArgs {
/// Returns the [`watchexec::InitConfig`] and [`watchexec::RuntimeConfig`] necessary to
/// bootstrap a new [`watchexe::Watchexec`] loop.
pub(crate) fn watchexec_config(&self) -> eyre::Result<(InitConfig, RuntimeConfig)> {
use crate::cmd::watch;
use crate::cmd::forge::watch;
let init = watch::init()?;
let mut runtime = watch::runtime(&self.watch)?;

Expand Down
2 changes: 1 addition & 1 deletion cli/src/cmd/config.rs → cli/src/cmd/forge/config.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! config command

use crate::{
cmd::{build::BuildArgs, Cmd},
cmd::{forge::build::BuildArgs, utils::Cmd},
opts::evm::EvmArgs,
};
use clap::Parser;
Expand Down
7 changes: 4 additions & 3 deletions cli/src/cmd/create.rs → cli/src/cmd/forge/create.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Create command

use crate::{
cmd::{build::BuildArgs, Cmd},
cmd::{forge::build::BuildArgs, Cmd},
opts::{EthereumOpts, WalletType},
utils::parse_u256,
};
Expand Down Expand Up @@ -70,10 +70,11 @@ impl Cmd for CreateArgs {
fn run(self) -> Result<Self::Output> {
// Find Project & Compile
let project = self.opts.project()?;
let compiled = super::compile(&project, self.opts.names, self.opts.sizes)?;
let compiled = crate::cmd::utils::compile(&project, self.opts.names, self.opts.sizes)?;

// Get ABI and BIN
let (abi, bin, _) = super::read_artifact(&project, compiled, self.contract.clone())?;
let (abi, bin, _) =
crate::cmd::utils::read_artifact(&project, compiled, self.contract.clone())?;

let bin = match bin.object {
BytecodeObject::Bytecode(_) => bin.object,
Expand Down
2 changes: 1 addition & 1 deletion cli/src/cmd/flatten.rs → cli/src/cmd/forge/flatten.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::path::PathBuf;

use ethers::solc::remappings::Remapping;

use crate::cmd::{build::BuildArgs, Cmd};
use crate::cmd::{forge::build::BuildArgs, Cmd};
use clap::{Parser, ValueHint};
use foundry_config::Config;

Expand Down
File renamed without changes.
13 changes: 8 additions & 5 deletions cli/src/cmd/init.rs → cli/src/cmd/forge/init.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
//! init command

use crate::{
cmd::{install::install, Cmd},
cmd::{forge::install::install, Cmd},
opts::forge::Dependency,
utils::p_println,
};
use clap::{Parser, ValueHint};
use foundry_config::Config;

use crate::cmd::{install::DependencyInstallOpts, remappings};
use crate::cmd::forge::{install::DependencyInstallOpts, remappings};
use ansi_term::Colour;
use ethers::solc::remappings::Remapping;
use std::{
Expand Down Expand Up @@ -102,10 +102,13 @@ impl Cmd for InitArgs {

// write the contract file
let contract_path = src.join("Contract.sol");
std::fs::write(contract_path, include_str!("../../../assets/ContractTemplate.sol"))?;
std::fs::write(contract_path, include_str!("../../../../assets/ContractTemplate.sol"))?;
// write the tests
let contract_path = test.join("Contract.t.sol");
std::fs::write(contract_path, include_str!("../../../assets/ContractTemplate.t.sol"))?;
std::fs::write(
contract_path,
include_str!("../../../../assets/ContractTemplate.t.sol"),
)?;

let dest = root.join(Config::FILE_NAME);
if !dest.exists() {
Expand Down Expand Up @@ -159,7 +162,7 @@ fn init_git_repo(root: &Path, no_commit: bool) -> eyre::Result<()> {

if !is_git.success() {
let gitignore_path = root.join(".gitignore");
std::fs::write(gitignore_path, include_str!("../../../assets/.gitignoreTemplate"))?;
std::fs::write(gitignore_path, include_str!("../../../../assets/.gitignoreTemplate"))?;

Command::new("git")
.arg("init")
Expand Down
Loading

0 comments on commit 33f1df0

Please sign in to comment.