use crate::contracts_rpc::RpcILBPair as ILBPair;
use crate::lb::LBPool;
use crate::TokenInfo;
use alloy::eips::BlockId;
use alloy::primitives::aliases::U24;
use alloy::primitives::{keccak256, Address, B256, U256};
use alloy::providers::{MulticallBuilder, Provider};
use anyhow::Result;
use log::info;
use std::collections::BTreeMap;
use std::sync::Arc;
const LB_TREE_BASE_SLOT_V22: u64 = 7;
const LB_TREE_BASE_SLOT_V21: u64 = 8;
fn mapping_storage_slot(key: B256, base_slot: U256) -> U256 {
let mut buf = [0u8; 64];
buf[..32].copy_from_slice(key.as_slice());
buf[32..].copy_from_slice(&base_slot.to_be_bytes::<32>());
U256::from_be_bytes(keccak256(buf).0)
}
fn set_bits(word: U256) -> Vec<u16> {
let mut bits = Vec::new();
for b in 0..=255u16 {
if word & (U256::from(1) << b) != U256::ZERO {
bits.push(b);
}
}
bits
}
async fn discover_bins_from_tree<P: Provider + Send + Sync>(
provider: &Arc<P>,
pool_address: Address,
block_number: BlockId,
base: u64,
) -> Result<Vec<u32>> {
let level0: U256 = provider
.get_storage_at(pool_address, U256::from(base))
.block_id(block_number)
.await?;
if level0.is_zero() {
return Ok(vec![]);
}
let level0_bits = set_bits(level0);
let level1_futures: Vec<_> = level0_bits
.iter()
.map(|&k| {
let slot = mapping_storage_slot(B256::from(U256::from(k)), U256::from(base + 1));
let provider = provider.clone();
async move {
provider
.get_storage_at(pool_address, slot)
.block_id(block_number)
.await
}
})
.collect();
let level1_values = futures_util::future::try_join_all(level1_futures).await?;
let mut level2_keys: Vec<u16> = Vec::new();
for (i, &k1) in level0_bits.iter().enumerate() {
for bit in set_bits(level1_values[i]) {
level2_keys.push((k1 << 8) | bit);
}
}
let level2_futures: Vec<_> = level2_keys
.iter()
.map(|&k| {
let slot = mapping_storage_slot(B256::from(U256::from(k)), U256::from(base + 2));
let provider = provider.clone();
async move {
provider
.get_storage_at(pool_address, slot)
.block_id(block_number)
.await
}
})
.collect();
let level2_values = futures_util::future::try_join_all(level2_futures).await?;
let mut bin_ids: Vec<u32> = Vec::new();
for (i, &k2) in level2_keys.iter().enumerate() {
for bit in set_bits(level2_values[i]) {
bin_ids.push((k2 as u32) << 8 | bit as u32);
}
}
Ok(bin_ids)
}
async fn discover_bins_by_walking<P: Provider + Send + Sync>(
provider: &Arc<P>,
pool_address: Address,
active_id: u32,
block_number: BlockId,
) -> Result<Vec<u32>> {
let lb = ILBPair::new(pool_address, provider);
let mut bin_ids: Vec<u32> = vec![active_id];
let mut id = active_id;
loop {
let next: u32 = lb
.getNextNonEmptyBin(true, U24::from(id))
.block(block_number)
.call()
.await?
.to();
if next == 0 || next >= id {
break;
}
bin_ids.push(next);
id = next;
}
let mut id = active_id;
loop {
let next: u32 = lb
.getNextNonEmptyBin(false, U24::from(id))
.block(block_number)
.call()
.await?
.to();
if next == 0x00FF_FFFF || next <= id {
break;
}
bin_ids.push(next);
id = next;
}
bin_ids.sort();
bin_ids.dedup();
Ok(bin_ids)
}
pub async fn fetch_lb_pool<P: Provider + Send + Sync, T: TokenInfo>(
provider: &Arc<P>,
pool_address: Address,
block_number: BlockId,
token_info: &T,
multicall_address: Address,
chain_id: u64,
) -> Result<LBPool> {
info!(
"[Chain {}] Fetching LB pool: {}",
chain_id, pool_address
);
let lb_instance = ILBPair::new(pool_address, provider);
let multicall_result = provider
.multicall()
.address(multicall_address)
.add(lb_instance.getTokenX()) .add(lb_instance.getTokenY()) .add(lb_instance.getBinStep()) .add(lb_instance.getActiveId()) .add(lb_instance.getStaticFeeParameters()) .add(lb_instance.getVariableFeeParameters()) .block(block_number)
.try_aggregate(false)
.await?;
let token_x_raw = multicall_result.0?;
let token_y_raw = multicall_result.1?;
let bin_step = multicall_result.2?;
let active_id: u32 = multicall_result.3?.to();
let static_fees = multicall_result.4?;
let var_fees = multicall_result.5?;
let (token_x, _) = token_info
.get_or_fetch_token(provider, token_x_raw, multicall_address)
.await?;
let (token_y, _) = token_info
.get_or_fetch_token(provider, token_y_raw, multicall_address)
.await?;
info!(
"[Chain {}] LB Pool: TokenX={}, TokenY={}, BinStep={}, ActiveId={}",
chain_id, token_x, token_y, bin_step, active_id
);
let bin_ids = 'discover: {
if let Ok(ids) =
discover_bins_from_tree(provider, pool_address, block_number, LB_TREE_BASE_SLOT_V22)
.await
{
if !ids.is_empty() {
info!(
"[Chain {}] LB tree bitmap (v2.2, slot 7): discovered {} non-empty bins",
chain_id,
ids.len()
);
break 'discover ids;
}
}
if let Ok(ids) =
discover_bins_from_tree(provider, pool_address, block_number, LB_TREE_BASE_SLOT_V21)
.await
{
if !ids.is_empty() {
info!(
"[Chain {}] LB tree bitmap (v2.1, slot 8): discovered {} non-empty bins",
chain_id,
ids.len()
);
break 'discover ids;
}
}
info!(
"[Chain {}] LB tree bitmap failed for both v2.2/v2.1, falling back to getNextNonEmptyBin walk",
chain_id
);
match discover_bins_by_walking(provider, pool_address, active_id, block_number).await {
Ok(ids) if !ids.is_empty() => {
info!(
"[Chain {}] LB walk: discovered {} non-empty bins",
chain_id,
ids.len()
);
ids
}
Ok(_) => {
info!(
"[Chain {}] LB walk returned empty, using active_id only",
chain_id
);
vec![active_id]
}
Err(e) => {
info!(
"[Chain {}] LB walk failed ({}), using active_id only",
chain_id, e
);
vec![active_id]
}
}
};
let mut bins = BTreeMap::new();
for chunk in bin_ids.chunks(250) {
let mut multicall = MulticallBuilder::new_dynamic(provider).address(multicall_address);
for &id in chunk {
multicall = multicall.add_dynamic(lb_instance.getBin(U24::from(id)));
}
let results = multicall.block(block_number).aggregate().await?;
for (i, &id) in chunk.iter().enumerate() {
let result = &results[i];
let rx: u128 = result.binReserveX;
let ry: u128 = result.binReserveY;
if rx > 0 || ry > 0 {
bins.insert(id, (rx, ry));
}
}
}
info!(
"[Chain {}] LB Pool: fetched {} non-empty bins (total discovered: {})",
chain_id,
bins.len(),
bin_ids.len()
);
let pool = LBPool::new(
pool_address,
token_x,
token_y,
bin_step,
active_id,
bins,
static_fees.baseFactor,
static_fees.filterPeriod,
static_fees.decayPeriod,
static_fees.reductionFactor,
static_fees.variableFeeControl.to(),
static_fees.protocolShare,
static_fees.maxVolatilityAccumulator.to(),
var_fees.volatilityAccumulator.to(),
var_fees.volatilityReference.to(),
var_fees.idReference.to(),
var_fees.timeOfLastUpdate.to(),
);
Ok(pool)
}