1use crate::cmd::rpc_provider;
2use alloy_provider::Provider;
3use clap::Parser;
4use eyre::Result;
5use foundry_cli::{json::print_scalar, opts::RpcOpts};
6use futures::join;
7
8#[derive(Clone, Debug, Parser)]
10pub struct FindBlockArgs {
11 timestamp: u64,
13
14 #[command(flatten)]
15 rpc: RpcOpts,
16}
17
18fn interpolate_block(
19 low_block: u64,
20 low_timestamp: u64,
21 high_block: u64,
22 high_timestamp: u64,
23 target_timestamp: u64,
24) -> u64 {
25 let block_range = high_block - low_block;
26 let midpoint = high_block - block_range / 2;
27 if high_timestamp <= low_timestamp {
28 return midpoint;
29 }
30
31 let timestamp_offset = target_timestamp - low_timestamp;
32 let timestamp_range = high_timestamp - low_timestamp;
33 let block_offset =
34 u128::from(timestamp_offset) * u128::from(block_range) / u128::from(timestamp_range);
35 (low_block + block_offset as u64).clamp(low_block + 1, high_block - 1)
36}
37
38impl FindBlockArgs {
39 pub async fn run(self) -> Result<()> {
40 let Self { timestamp: ts_target, rpc } = self;
41 let provider = rpc_provider(&rpc)?;
42
43 let last_block_num = provider.get_block_number().await?;
44 let (ts_block_latest, ts_block_1) =
45 join!(timestamp(&provider, last_block_num), timestamp(&provider, 1));
46 let ts_block_latest = ts_block_latest?;
47 let ts_block_1 = ts_block_1?;
48
49 let block_num = if ts_block_latest < ts_target {
50 last_block_num
52 } else if ts_block_1 > ts_target {
53 1
55 } else {
56 let mut low_block = 1_u64; let mut low_timestamp = ts_block_1;
59 let mut high_block = last_block_num;
60 let mut high_timestamp = ts_block_latest;
61 let mut interpolation_budget =
64 u64::BITS - last_block_num.saturating_sub(1).leading_zeros();
65 loop {
66 let block_range = high_block - low_block;
67 if block_range == 0 {
68 break low_block;
69 }
70 if block_range == 1 {
71 let high_diff = high_timestamp - ts_target;
73 let low_diff = ts_target - low_timestamp;
74 break if low_diff < high_diff { low_block } else { high_block };
75 }
76
77 let midpoint = high_block - block_range / 2;
78 let next_block = if interpolation_budget == 0 {
79 midpoint
80 } else {
81 interpolation_budget -= 1;
82 interpolate_block(
83 low_block,
84 low_timestamp,
85 high_block,
86 high_timestamp,
87 ts_target,
88 )
89 };
90 let next_timestamp = timestamp(&provider, next_block).await?;
91
92 if next_timestamp == ts_target {
93 break next_block;
94 }
95 if next_timestamp < ts_target {
96 low_block = next_block;
97 low_timestamp = next_timestamp;
98 } else {
99 high_block = next_block;
100 high_timestamp = next_timestamp;
101 }
102 }
103 };
104 print_scalar(block_num)?;
105
106 Ok(())
107 }
108}
109
110async fn timestamp(
111 provider: &impl Provider<alloy_network::AnyNetwork>,
112 number: u64,
113) -> Result<u64> {
114 Ok(provider
115 .get_block_by_number(number.into())
116 .await?
117 .ok_or_else(|| eyre::eyre!("block {number} not found"))?
118 .header
119 .timestamp)
120}
121
122#[cfg(test)]
123mod tests {
124 use super::interpolate_block;
125
126 #[test]
127 fn interpolates_block_from_timestamps() {
128 assert_eq!(interpolate_block(1, 100, 11, 200, 150), 6);
129 assert_eq!(interpolate_block(1, 100, 11, 200, 100), 2);
131 assert_eq!(interpolate_block(1, 100, 11, 200, 200), 10);
132 assert_eq!(interpolate_block(0, 0, u64::MAX, u64::MAX, u64::MAX / 2), u64::MAX / 2);
134 assert_eq!(interpolate_block(1, 100, 10, 100, 100), 6);
136 }
137}