Skip to main content

cast/cmd/
find_block.rs

1use crate::Cast;
2use alloy_provider::Provider;
3use clap::Parser;
4use eyre::Result;
5use foundry_cli::{
6    json::print_scalar,
7    opts::RpcOpts,
8    utils::{self, LoadConfig},
9};
10use futures::join;
11
12/// CLI arguments for `cast find-block`.
13#[derive(Clone, Debug, Parser)]
14pub struct FindBlockArgs {
15    /// The UNIX timestamp to search for, in seconds.
16    timestamp: u64,
17
18    #[command(flatten)]
19    rpc: RpcOpts,
20}
21
22impl FindBlockArgs {
23    pub async fn run(self) -> Result<()> {
24        let Self { timestamp, rpc } = self;
25
26        let ts_target = timestamp;
27        let config = rpc.load_config()?;
28        let provider = utils::get_provider(&config)?;
29
30        let last_block_num = provider.get_block_number().await?;
31        let cast_provider = Cast::new(provider);
32
33        let res = join!(cast_provider.timestamp(last_block_num), cast_provider.timestamp(1));
34        let ts_block_latest: u64 = res.0?.to();
35        let ts_block_1: u64 = res.1?.to();
36
37        let block_num = if ts_block_latest < ts_target {
38            // If the most recent block's timestamp is below the target, return it
39            last_block_num
40        } else if ts_block_1 > ts_target {
41            // If the target timestamp is below block 1's timestamp, return that
42            1
43        } else {
44            // Otherwise, find the block that is closest to the timestamp
45            let mut low_block = 1_u64; // block 0 has a timestamp of 0: https://github.com/ethereum/go-ethereum/issues/17042#issuecomment-559414137
46            let mut high_block = last_block_num;
47            let mut matching_block = None;
48            while high_block > low_block && matching_block.is_none() {
49                // Get timestamp of middle block (this approach to avoids overflow)
50                let high_minus_low_over_2 = high_block
51                    .checked_sub(low_block)
52                    .ok_or_else(|| eyre::eyre!("unexpected underflow"))
53                    .unwrap()
54                    .checked_div(2_u64)
55                    .unwrap();
56                let mid_block = high_block.checked_sub(high_minus_low_over_2).unwrap();
57                let ts_mid_block = cast_provider.timestamp(mid_block).await?.to::<u64>();
58
59                // Check if we've found a match or should keep searching
60                if ts_mid_block == ts_target {
61                    matching_block = Some(mid_block)
62                } else if high_block.checked_sub(low_block).unwrap() == 1_u64 {
63                    // The target timestamp is in between these blocks. This rounds to the
64                    // highest block if timestamp is equidistant between blocks
65                    let res = join!(
66                        cast_provider.timestamp(high_block),
67                        cast_provider.timestamp(low_block)
68                    );
69                    let ts_high: u64 = res.0.unwrap().to();
70                    let ts_low: u64 = res.1.unwrap().to();
71                    let high_diff = ts_high.checked_sub(ts_target).unwrap();
72                    let low_diff = ts_target.checked_sub(ts_low).unwrap();
73                    let is_low = low_diff < high_diff;
74                    matching_block = if is_low { Some(low_block) } else { Some(high_block) }
75                } else if ts_mid_block < ts_target {
76                    low_block = mid_block;
77                } else {
78                    high_block = mid_block;
79                }
80            }
81            matching_block.unwrap_or(low_block)
82        };
83        print_scalar(block_num)?;
84
85        Ok(())
86    }
87}