Skip to content

Commit 33f1df0

Browse files
onbjerggakonstbrockelmorerefcellmattsse
authored
refactor: move to revm (#918)
* 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 <[email protected]> * 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 <[email protected]> Co-authored-by: brockelmore <[email protected]> * 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 <[email protected]> * 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 <[email protected]> * 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 <[email protected]> * 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 <[email protected]> * 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 <[email protected]> * 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 <[email protected]> Co-authored-by: brockelmore <[email protected]> * 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 <[email protected]> * 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 <[email protected]> * 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 <[email protected]> * 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 <[email protected]> * 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 <[email protected]> Co-authored-by: Georgios Konstantopoulos <[email protected]> Co-authored-by: Oliver Nordbjerg <[email protected]> Co-authored-by: brockelmore <[email protected]> Co-authored-by: brockelmore <[email protected]> * 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 <[email protected]> Co-authored-by: brockelmore <[email protected]> Co-authored-by: brockelmore <[email protected]> Co-authored-by: abigger87 <[email protected]> Co-authored-by: Matthias Seitz <[email protected]>
1 parent 22323e2 commit 33f1df0

File tree

168 files changed

+11044
-12431
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

168 files changed

+11044
-12431
lines changed

Cargo.lock

Lines changed: 128 additions & 291 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
[workspace]
22
members = [
3-
"evm-adapters",
43
"utils",
54
"cast",
65
"forge",
76
"cli",
87
"cli/test-utils",
98
"config",
109
"fmt",
10+
"ui",
11+
"evm"
1112
]
1213

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

30-
## Patch sputnik with more recent primitive types
31-
# https://github.com/rust-blockchain/evm/pulls
32-
[patch."https://github.com/rust-blockchain/evm"]
33-
evm = { git = "https://github.com/gakonst/evm", branch = "bump-primitive-types" }
34-
3531
## Patch ethers-rs with a local checkout then run `cargo update -p ethers`
3632
#[patch."https://github.com/gakonst/ethers-rs"]
3733
#ethers = { path = "../ethers-rs" }

cast/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "cast"
3-
version = "0.1.0"
3+
version = "0.2.0"
44
edition = "2021"
55
license = "MIT OR Apache-2.0"
66

cli/Cargo.toml

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "foundry-cli"
3-
version = "0.1.0"
3+
version = "0.2.0"
44
edition = "2021"
55
license = "MIT OR Apache-2.0"
66

@@ -26,7 +26,6 @@ foundry-utils = { path = "../utils" }
2626
forge = { path = "../forge" }
2727
foundry-config = { path = "../config" }
2828
cast = { path = "../cast" }
29-
evm-adapters = { path = "../evm-adapters" }
3029
ui = { path = "../ui" }
3130
dunce = "1.0.2"
3231
# ethers = "0.5"
@@ -39,16 +38,14 @@ tokio = { version = "1.11.0", features = ["macros"] }
3938
regex = { version = "1.5.4", default-features = false }
4039
ansi_term = "0.12.1"
4140
rpassword = "5.0.1"
42-
tracing-subscriber = "0.2.20"
41+
tracing-error = "0.2.0"
42+
tracing-subscriber = { version = "0.3", features = ["registry", "env-filter", "fmt"] }
4343
tracing = "0.1.26"
4444
hex = "0.4.3"
4545
rayon = "1.5.1"
4646
serde = "1.0.133"
4747
futures = "0.3.17"
4848

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

6966
[features]
70-
default = ["sputnik-evm", "rustls"]
67+
default = ["rustls"]
7168
solc-asm = ["ethers/solc-sha2-asm"]
7269
rustls = ["ethers/rustls"]
7370
openssl = ["ethers/openssl"]
7471

75-
sputnik-evm = [
76-
"sputnik",
77-
"evm-adapters/sputnik",
78-
"evm-adapters/sputnik-helpers",
79-
]
80-
8172
integration-tests = []
8273

8374
[[bin]]

cli/src/cast.rs

Lines changed: 3 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use ethers::{
1515
},
1616
providers::{Middleware, Provider},
1717
signers::{LocalWallet, Signer},
18-
types::{Address, Chain, NameOrAddress, Signature, U256, U64},
18+
types::{Address, Chain, NameOrAddress, Signature, U256},
1919
utils::get_contract_address,
2020
};
2121
use opts::{
@@ -36,9 +36,8 @@ use std::{
3636
use clap::{IntoApp, Parser};
3737
use clap_complete::generate;
3838

39-
use crate::utils::read_secret;
39+
use crate::{cmd::Cmd, utils::read_secret};
4040
use eyre::WrapErr;
41-
use futures::join;
4241

4342
#[tokio::main]
4443
async fn main() -> eyre::Result<()> {
@@ -542,64 +541,7 @@ async fn main() -> eyre::Result<()> {
542541
let selector = contract.abi().functions().last().unwrap().short_signature();
543542
println!("0x{}", hex::encode(selector));
544543
}
545-
Subcommands::FindBlock { timestamp, rpc_url } => {
546-
let ts_target = U256::from(timestamp);
547-
let provider = Provider::try_from(rpc_url)?;
548-
let last_block_num = provider.get_block_number().await?;
549-
let cast_provider = Cast::new(provider);
550-
551-
let res = join!(cast_provider.timestamp(last_block_num), cast_provider.timestamp(1));
552-
let ts_block_latest = res.0.unwrap();
553-
let ts_block_1 = res.1.unwrap();
554-
555-
let block_num = if ts_block_latest.lt(&ts_target) {
556-
// If the most recent block's timestamp is below the target, return it
557-
last_block_num
558-
} else if ts_block_1.gt(&ts_target) {
559-
// If the target timestamp is below block 1's timestamp, return that
560-
U64::from(1)
561-
} else {
562-
// Otherwise, find the block that is closest to the timestamp
563-
let mut low_block = U64::from(1); // block 0 has a timestamp of 0: https://github.com/ethereum/go-ethereum/issues/17042#issuecomment-559414137
564-
let mut high_block = last_block_num;
565-
let mut matching_block: Option<U64> = None;
566-
while high_block.gt(&low_block) && matching_block.is_none() {
567-
// Get timestamp of middle block (this approach approach to avoids overflow)
568-
let high_minus_low_over_2 = high_block
569-
.checked_sub(low_block)
570-
.ok_or_else(|| eyre::eyre!("unexpected underflow"))
571-
.unwrap()
572-
.checked_div(U64::from(2))
573-
.unwrap();
574-
let mid_block = high_block.checked_sub(high_minus_low_over_2).unwrap();
575-
let ts_mid_block = cast_provider.timestamp(mid_block).await?;
576-
577-
// Check if we've found a match or should keep searching
578-
if ts_mid_block.eq(&ts_target) {
579-
matching_block = Some(mid_block)
580-
} else if high_block.checked_sub(low_block).unwrap().eq(&U64::from(1)) {
581-
// The target timestamp is in between these blocks. This rounds to the
582-
// highest block if timestamp is equidistant between blocks
583-
let res = join!(
584-
cast_provider.timestamp(high_block),
585-
cast_provider.timestamp(low_block)
586-
);
587-
let ts_high = res.0.unwrap();
588-
let ts_low = res.1.unwrap();
589-
let high_diff = ts_high.checked_sub(ts_target).unwrap();
590-
let low_diff = ts_target.checked_sub(ts_low).unwrap();
591-
let is_low = low_diff.lt(&high_diff);
592-
matching_block = if is_low { Some(low_block) } else { Some(high_block) }
593-
} else if ts_mid_block.lt(&ts_target) {
594-
low_block = mid_block;
595-
} else {
596-
high_block = mid_block;
597-
}
598-
}
599-
matching_block.unwrap_or(low_block)
600-
};
601-
println!("{}", block_num);
602-
}
544+
Subcommands::FindBlock(cmd) => cmd.run()?,
603545
Subcommands::Wallet { command } => match command {
604546
WalletSubcommands::New { path, password, unsafe_password } => {
605547
let mut rng = thread_rng();

cli/src/cmd/cast/find_block.rs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
//! cast find-block subcommand
2+
3+
use crate::cmd::Cmd;
4+
use cast::Cast;
5+
use clap::Parser;
6+
use ethers::prelude::*;
7+
use eyre::Result;
8+
use futures::join;
9+
10+
#[derive(Debug, Clone, Parser)]
11+
pub struct FindBlockArgs {
12+
#[clap(help = "The UNIX timestamp to search for (in seconds)")]
13+
timestamp: u64,
14+
#[clap(long, env = "ETH_RPC_URL")]
15+
rpc_url: String,
16+
}
17+
18+
impl Cmd for FindBlockArgs {
19+
type Output = ();
20+
21+
fn run(self) -> Result<Self::Output> {
22+
let FindBlockArgs { timestamp, rpc_url } = self;
23+
let rt = tokio::runtime::Runtime::new().expect("could not start tokio rt");
24+
rt.block_on(Self::query_block(timestamp, rpc_url))?;
25+
Ok(())
26+
}
27+
}
28+
29+
impl FindBlockArgs {
30+
async fn query_block(timestamp: u64, rpc_url: String) -> Result<()> {
31+
let ts_target = U256::from(timestamp);
32+
let provider = Provider::try_from(rpc_url)?;
33+
let last_block_num = provider.get_block_number().await?;
34+
let cast_provider = Cast::new(provider);
35+
36+
let res = join!(cast_provider.timestamp(last_block_num), cast_provider.timestamp(1));
37+
let ts_block_latest = res.0.unwrap();
38+
let ts_block_1 = res.1.unwrap();
39+
40+
let block_num = if ts_block_latest.lt(&ts_target) {
41+
// If the most recent block's timestamp is below the target, return it
42+
last_block_num
43+
} else if ts_block_1.gt(&ts_target) {
44+
// If the target timestamp is below block 1's timestamp, return that
45+
U64::from(1_u64)
46+
} else {
47+
// Otherwise, find the block that is closest to the timestamp
48+
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
49+
let mut high_block = last_block_num;
50+
let mut matching_block: Option<U64> = None;
51+
while high_block.gt(&low_block) && matching_block.is_none() {
52+
// Get timestamp of middle block (this approach approach to avoids overflow)
53+
let high_minus_low_over_2 = high_block
54+
.checked_sub(low_block)
55+
.ok_or_else(|| eyre::eyre!("unexpected underflow"))
56+
.unwrap()
57+
.checked_div(U64::from(2_u64))
58+
.unwrap();
59+
let mid_block = high_block.checked_sub(high_minus_low_over_2).unwrap();
60+
let ts_mid_block = cast_provider.timestamp(mid_block).await?;
61+
62+
// Check if we've found a match or should keep searching
63+
if ts_mid_block.eq(&ts_target) {
64+
matching_block = Some(mid_block)
65+
} else if high_block.checked_sub(low_block).unwrap().eq(&U64::from(1_u64)) {
66+
// The target timestamp is in between these blocks. This rounds to the
67+
// highest block if timestamp is equidistant between blocks
68+
let res = join!(
69+
cast_provider.timestamp(high_block),
70+
cast_provider.timestamp(low_block)
71+
);
72+
let ts_high = res.0.unwrap();
73+
let ts_low = res.1.unwrap();
74+
let high_diff = ts_high.checked_sub(ts_target).unwrap();
75+
let low_diff = ts_target.checked_sub(ts_low).unwrap();
76+
let is_low = low_diff.lt(&high_diff);
77+
matching_block = if is_low { Some(low_block) } else { Some(high_block) }
78+
} else if ts_mid_block.lt(&ts_target) {
79+
low_block = mid_block;
80+
} else {
81+
high_block = mid_block;
82+
}
83+
}
84+
matching_block.unwrap_or(low_block)
85+
};
86+
println!("{}", block_num);
87+
88+
Ok(())
89+
}
90+
}

cli/src/cmd/cast/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
//! Subcommands for cast
2+
//!
3+
//! All subcommands should respect the `foundry_config::Config`.
4+
//! If a subcommand accepts values that are supported by the `Config`, then the subcommand should
5+
//! implement `figment::Provider` which allows the subcommand to override the config's defaults, see
6+
//! [`foundry_config::Config`].
7+
8+
pub mod find_block;

cli/src/cmd/bind.rs renamed to cli/src/cmd/forge/bind.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::cmd::Cmd;
1+
use crate::cmd::utils::Cmd;
22

33
use clap::{Parser, ValueHint};
44
use ethers::contract::MultiAbigen;

cli/src/cmd/build.rs renamed to cli/src/cmd/forge/build.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::path::PathBuf;
55

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

8-
use crate::cmd::watch::WatchArgs;
8+
use crate::cmd::forge::watch::WatchArgs;
99
use clap::{Parser, ValueHint};
1010
use ethers::solc::remappings::Remapping;
1111
use foundry_config::{
@@ -191,7 +191,7 @@ impl Cmd for BuildArgs {
191191
type Output = ProjectCompileOutput;
192192
fn run(self) -> eyre::Result<Self::Output> {
193193
let project = self.project()?;
194-
super::compile(&project, self.names, self.sizes)
194+
crate::cmd::utils::compile(&project, self.names, self.sizes)
195195
}
196196
}
197197

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

cli/src/cmd/config.rs renamed to cli/src/cmd/forge/config.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! config command
22
33
use crate::{
4-
cmd::{build::BuildArgs, Cmd},
4+
cmd::{forge::build::BuildArgs, utils::Cmd},
55
opts::evm::EvmArgs,
66
};
77
use clap::Parser;

cli/src/cmd/create.rs renamed to cli/src/cmd/forge/create.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Create command
22
33
use crate::{
4-
cmd::{build::BuildArgs, Cmd},
4+
cmd::{forge::build::BuildArgs, Cmd},
55
opts::{EthereumOpts, WalletType},
66
utils::parse_u256,
77
};
@@ -70,10 +70,11 @@ impl Cmd for CreateArgs {
7070
fn run(self) -> Result<Self::Output> {
7171
// Find Project & Compile
7272
let project = self.opts.project()?;
73-
let compiled = super::compile(&project, self.opts.names, self.opts.sizes)?;
73+
let compiled = crate::cmd::utils::compile(&project, self.opts.names, self.opts.sizes)?;
7474

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

7879
let bin = match bin.object {
7980
BytecodeObject::Bytecode(_) => bin.object,

cli/src/cmd/flatten.rs renamed to cli/src/cmd/forge/flatten.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::path::PathBuf;
22

33
use ethers::solc::remappings::Remapping;
44

5-
use crate::cmd::{build::BuildArgs, Cmd};
5+
use crate::cmd::{forge::build::BuildArgs, Cmd};
66
use clap::{Parser, ValueHint};
77
use foundry_config::Config;
88

File renamed without changes.

cli/src/cmd/init.rs renamed to cli/src/cmd/forge/init.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
//! init command
22
33
use crate::{
4-
cmd::{install::install, Cmd},
4+
cmd::{forge::install::install, Cmd},
55
opts::forge::Dependency,
66
utils::p_println,
77
};
88
use clap::{Parser, ValueHint};
99
use foundry_config::Config;
1010

11-
use crate::cmd::{install::DependencyInstallOpts, remappings};
11+
use crate::cmd::forge::{install::DependencyInstallOpts, remappings};
1212
use ansi_term::Colour;
1313
use ethers::solc::remappings::Remapping;
1414
use std::{
@@ -102,10 +102,13 @@ impl Cmd for InitArgs {
102102

103103
// write the contract file
104104
let contract_path = src.join("Contract.sol");
105-
std::fs::write(contract_path, include_str!("../../../assets/ContractTemplate.sol"))?;
105+
std::fs::write(contract_path, include_str!("../../../../assets/ContractTemplate.sol"))?;
106106
// write the tests
107107
let contract_path = test.join("Contract.t.sol");
108-
std::fs::write(contract_path, include_str!("../../../assets/ContractTemplate.t.sol"))?;
108+
std::fs::write(
109+
contract_path,
110+
include_str!("../../../../assets/ContractTemplate.t.sol"),
111+
)?;
109112

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

160163
if !is_git.success() {
161164
let gitignore_path = root.join(".gitignore");
162-
std::fs::write(gitignore_path, include_str!("../../../assets/.gitignoreTemplate"))?;
165+
std::fs::write(gitignore_path, include_str!("../../../../assets/.gitignoreTemplate"))?;
163166

164167
Command::new("git")
165168
.arg("init")

0 commit comments

Comments
 (0)