1use std::str::FromStr;
16use std::sync::Arc;
17
18use alloy::primitives::{Address, B256};
19use degenbot_db::{ComputedLiquidityUpdate, DegenbotDb};
20use degenbot_pool_updater::{
21 run_pool_update, verify_v3_liquidity_map_on_chain, verify_v4_liquidity_map_on_chain,
22 NoProgress, RunError,
23};
24
25use crate::block::{parse_to_block, resolve_to_block};
26use crate::cancel::CancelHandle;
27use crate::context::CliContext;
28use crate::error::CliError;
29use crate::prompt::{PromptPlan, Prompter};
30use crate::report::PoolReport;
31
32const RPC_MAX_RETRIES: u32 = 5;
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum PoolFamily {
38 V3,
40 V4,
42}
43
44impl PoolFamily {
45 #[must_use]
47 pub const fn as_str(self) -> &'static str {
48 match self {
49 Self::V3 => "v3",
50 Self::V4 => "v4",
51 }
52 }
53}
54
55#[derive(Debug, Clone, PartialEq, Eq)]
57pub enum PoolCommand {
58 Update {
60 chunk_size: u64,
62 to_block: String,
64 verify_chunk: bool,
66 verify_all: bool,
68 verify_all_interval: u64,
70 },
71 Verify {
73 rpc_url: String,
75 chain_id: i64,
77 block_number: u64,
79 pool: String,
81 family: PoolFamily,
83 pool_manager: Option<String>,
85 },
86}
87
88impl PoolCommand {
89 #[must_use]
91 pub const fn prompt_plan(&self, _ctx: &CliContext<'_>) -> PromptPlan {
92 PromptPlan::None
93 }
94}
95
96enum VerifyTarget {
98 V3(Address),
100 V4 { manager: Address, pool_id: B256 },
102}
103
104pub(crate) fn execute(
113 command: &PoolCommand,
114 ctx: &CliContext<'_>,
115 _prompter: &dyn Prompter,
116 cancel: &CancelHandle,
117) -> Result<PoolReport, CliError> {
118 match command {
119 PoolCommand::Update {
120 chunk_size,
121 to_block,
122 verify_chunk,
123 verify_all,
124 verify_all_interval,
125 } => update(
126 ctx,
127 cancel,
128 *chunk_size,
129 to_block,
130 *verify_chunk,
131 *verify_all,
132 *verify_all_interval,
133 ),
134 PoolCommand::Verify {
135 rpc_url,
136 chain_id,
137 block_number,
138 pool,
139 family,
140 pool_manager,
141 } => verify(
142 ctx,
143 rpc_url,
144 *chain_id,
145 *block_number,
146 pool,
147 *family,
148 pool_manager.as_deref(),
149 ),
150 }
151}
152
153fn update(
155 ctx: &CliContext<'_>,
156 cancel: &CancelHandle,
157 chunk_size: u64,
158 to_block: &str,
159 verify_chunk: bool,
160 verify_all: bool,
161 verify_all_interval: u64,
162) -> Result<PoolReport, CliError> {
163 let database_path = ctx.database_path().value;
164 let chain_id = ctx.chain_id()?.value;
165 let rpc_url = ctx.node_http_uri()?.value;
166 crate::registrations::ensure_supported_registrations(&database_path)?;
169 let resolved = resolve_to_block(parse_to_block(to_block)?, &rpc_url)?;
170 let chain = i64::try_from(chain_id)
171 .map_err(|_| CliError::InvalidArgument(format!("chain id {chain_id} is out of range")))?;
172 let interval = if verify_all {
173 Some(verify_all_interval)
174 } else {
175 None
176 };
177 match run_pool_update(
178 &database_path,
179 chain,
180 resolved,
181 chunk_size,
182 &rpc_url,
183 cancel.flag(),
184 Arc::new(NoProgress),
185 verify_chunk,
186 interval,
187 verify_all,
188 ) {
189 Ok(report) => Ok(PoolReport::Updated {
190 chain_id: report.chain_id,
191 from_block: report.from_block,
192 to_block: report.to_block,
193 chunks_committed: report.chunks_committed,
194 total_pools_written: report.total_pools_written,
195 total_liquidity_applies: report.total_liquidity_applies,
196 }),
197 Err(RunError::Cancelled) => Ok(PoolReport::UpdateCancelled { chain_id: chain }),
198 Err(err) => Err(CliError::PoolUpdate(err)),
199 }
200}
201
202fn verify(
204 ctx: &CliContext<'_>,
205 rpc_url: &str,
206 chain_id: i64,
207 block_number: u64,
208 pool: &str,
209 family: PoolFamily,
210 pool_manager: Option<&str>,
211) -> Result<PoolReport, CliError> {
212 let database_path = ctx.database_path().value;
213 let (computed, target) = {
214 let (db, _state) = DegenbotDb::open(&database_path)?;
215 let conn = db.lock();
216 fetch_verify_state(&conn, chain_id, pool, family, pool_manager)?
217 };
218 let divergences = match target {
219 VerifyTarget::V3(address) => crate::block::block_on(async {
220 let provider = degenbot_rpc::provider::AlloyProvider::new(rpc_url, RPC_MAX_RETRIES)
221 .await
222 .map_err(|err| CliError::BlockResolution(err.to_string()))?;
223 verify_v3_liquidity_map_on_chain(&provider, address, &computed, block_number)
224 .await
225 .map_err(CliError::PoolUpdate)
226 })??,
227 VerifyTarget::V4 { manager, pool_id } => crate::block::block_on(async {
228 let provider = degenbot_rpc::provider::AlloyProvider::new(rpc_url, RPC_MAX_RETRIES)
229 .await
230 .map_err(|err| CliError::BlockResolution(err.to_string()))?;
231 verify_v4_liquidity_map_on_chain(&provider, manager, pool_id, &computed, block_number)
232 .await
233 .map_err(CliError::PoolUpdate)
234 })??,
235 };
236 Ok(PoolReport::Verified {
237 pool: pool.to_string(),
238 family,
239 block_number,
240 divergences,
241 })
242}
243
244fn fetch_verify_state(
246 conn: &rusqlite::Connection,
247 chain_id: i64,
248 pool: &str,
249 family: PoolFamily,
250 pool_manager: Option<&str>,
251) -> Result<(ComputedLiquidityUpdate, VerifyTarget), CliError> {
252 match family {
253 PoolFamily::V3 => {
254 let address: Address = pool
255 .parse()
256 .map_err(|_| CliError::InvalidAddress(pool.to_string()))?;
257 let key = address.to_checksum(None);
258 let state = DegenbotDb::fetch_v3_pool_update_state_on_conn(conn, chain_id, &key)?
259 .ok_or_else(|| {
260 CliError::InvalidArgument(format!(
261 "v3 pool {key} not found on chain {chain_id}"
262 ))
263 })?;
264 let (tick_bitmap, tick_data) =
265 DegenbotDb::fetch_v3_liquidity_map_on_conn(conn, state.pool_id)?;
266 Ok((
267 ComputedLiquidityUpdate {
268 pool_id: state.pool_id,
269 tick_spacing: state.tick_spacing,
270 tick_data,
271 tick_bitmap,
272 last_event: None,
273 },
274 VerifyTarget::V3(address),
275 ))
276 }
277 PoolFamily::V4 => {
278 let manager = pool_manager.ok_or_else(|| {
279 CliError::InvalidArgument(
280 "--pool-manager is required for --family v4 (the PoolManager singleton)."
281 .to_string(),
282 )
283 })?;
284 let manager: Address = manager
285 .parse()
286 .map_err(|_| CliError::InvalidAddress(manager.to_string()))?;
287 let pool_id = B256::from_str(pool.strip_prefix("0x").unwrap_or(pool))
288 .map_err(|_| CliError::InvalidAddress(pool.to_string()))?;
289 let state = DegenbotDb::fetch_v4_pool_update_state_on_conn(conn, pool, chain_id)?
290 .ok_or_else(|| {
291 CliError::InvalidArgument(format!(
292 "v4 pool {pool} not found on chain {chain_id}"
293 ))
294 })?;
295 let (tick_bitmap, tick_data) =
296 DegenbotDb::fetch_v4_liquidity_map_on_conn(conn, state.pool_id)?;
297 Ok((
298 ComputedLiquidityUpdate {
299 pool_id: state.pool_id,
300 tick_spacing: state.tick_spacing,
301 tick_data,
302 tick_bitmap,
303 last_event: None,
304 },
305 VerifyTarget::V4 { manager, pool_id },
306 ))
307 }
308 }
309}