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 let resolved = resolve_to_block(parse_to_block(to_block)?, &rpc_url)?;
167 let chain = i64::try_from(chain_id)
168 .map_err(|_| CliError::InvalidArgument(format!("chain id {chain_id} is out of range")))?;
169 let interval = if verify_all {
170 Some(verify_all_interval)
171 } else {
172 None
173 };
174 match run_pool_update(
175 &database_path,
176 chain,
177 resolved,
178 chunk_size,
179 &rpc_url,
180 cancel.flag(),
181 Arc::new(NoProgress),
182 verify_chunk,
183 interval,
184 verify_all,
185 ) {
186 Ok(report) => Ok(PoolReport::Updated {
187 chain_id: report.chain_id,
188 from_block: report.from_block,
189 to_block: report.to_block,
190 chunks_committed: report.chunks_committed,
191 total_pools_written: report.total_pools_written,
192 total_liquidity_applies: report.total_liquidity_applies,
193 }),
194 Err(RunError::Cancelled) => Ok(PoolReport::UpdateCancelled { chain_id: chain }),
195 Err(err) => Err(CliError::PoolUpdate(err)),
196 }
197}
198
199fn verify(
201 ctx: &CliContext<'_>,
202 rpc_url: &str,
203 chain_id: i64,
204 block_number: u64,
205 pool: &str,
206 family: PoolFamily,
207 pool_manager: Option<&str>,
208) -> Result<PoolReport, CliError> {
209 let database_path = ctx.database_path().value;
210 let (computed, target) = {
211 let (db, _state) = DegenbotDb::open(&database_path)?;
212 let conn = db.lock();
213 fetch_verify_state(&conn, chain_id, pool, family, pool_manager)?
214 };
215 let divergences = match target {
216 VerifyTarget::V3(address) => crate::block::block_on(async {
217 let provider = degenbot_rpc::provider::AlloyProvider::new(rpc_url, RPC_MAX_RETRIES)
218 .await
219 .map_err(|err| CliError::BlockResolution(err.to_string()))?;
220 verify_v3_liquidity_map_on_chain(&provider, address, &computed, block_number)
221 .await
222 .map_err(CliError::PoolUpdate)
223 })??,
224 VerifyTarget::V4 { manager, pool_id } => crate::block::block_on(async {
225 let provider = degenbot_rpc::provider::AlloyProvider::new(rpc_url, RPC_MAX_RETRIES)
226 .await
227 .map_err(|err| CliError::BlockResolution(err.to_string()))?;
228 verify_v4_liquidity_map_on_chain(&provider, manager, pool_id, &computed, block_number)
229 .await
230 .map_err(CliError::PoolUpdate)
231 })??,
232 };
233 Ok(PoolReport::Verified {
234 pool: pool.to_string(),
235 family,
236 block_number,
237 divergences,
238 })
239}
240
241fn fetch_verify_state(
243 conn: &rusqlite::Connection,
244 chain_id: i64,
245 pool: &str,
246 family: PoolFamily,
247 pool_manager: Option<&str>,
248) -> Result<(ComputedLiquidityUpdate, VerifyTarget), CliError> {
249 match family {
250 PoolFamily::V3 => {
251 let address: Address = pool
252 .parse()
253 .map_err(|_| CliError::InvalidAddress(pool.to_string()))?;
254 let key = address.to_checksum(None);
255 let state = DegenbotDb::fetch_v3_pool_update_state_on_conn(conn, chain_id, &key)?
256 .ok_or_else(|| {
257 CliError::InvalidArgument(format!(
258 "v3 pool {key} not found on chain {chain_id}"
259 ))
260 })?;
261 let (tick_bitmap, tick_data) =
262 DegenbotDb::fetch_v3_liquidity_map_on_conn(conn, state.pool_id)?;
263 Ok((
264 ComputedLiquidityUpdate {
265 pool_id: state.pool_id,
266 tick_spacing: state.tick_spacing,
267 tick_data,
268 tick_bitmap,
269 last_event: None,
270 },
271 VerifyTarget::V3(address),
272 ))
273 }
274 PoolFamily::V4 => {
275 let manager = pool_manager.ok_or_else(|| {
276 CliError::InvalidArgument(
277 "--pool-manager is required for --family v4 (the PoolManager singleton)."
278 .to_string(),
279 )
280 })?;
281 let manager: Address = manager
282 .parse()
283 .map_err(|_| CliError::InvalidAddress(manager.to_string()))?;
284 let pool_id = B256::from_str(pool.strip_prefix("0x").unwrap_or(pool))
285 .map_err(|_| CliError::InvalidAddress(pool.to_string()))?;
286 let state = DegenbotDb::fetch_v4_pool_update_state_on_conn(conn, pool, chain_id)?
287 .ok_or_else(|| {
288 CliError::InvalidArgument(format!(
289 "v4 pool {pool} not found on chain {chain_id}"
290 ))
291 })?;
292 let (tick_bitmap, tick_data) =
293 DegenbotDb::fetch_v4_liquidity_map_on_conn(conn, state.pool_id)?;
294 Ok((
295 ComputedLiquidityUpdate {
296 pool_id: state.pool_id,
297 tick_spacing: state.tick_spacing,
298 tick_data,
299 tick_bitmap,
300 last_event: None,
301 },
302 VerifyTarget::V4 { manager, pool_id },
303 ))
304 }
305 }
306}