use std::collections::HashMap;
use std::fmt;
#[cfg(any(feature = "uniswap-v2", feature = "uniswap-v3", feature = "solidly-v2"))]
use super::ProtocolMetadata;
#[cfg(feature = "solidly-v2")]
use super::SolidlyV2Metadata;
#[cfg(feature = "uniswap-v2")]
use super::UniswapV2Metadata;
#[cfg(feature = "uniswap-v3")]
use super::V3Metadata;
use super::{AdapterCache, AdapterRegistry, EventSource, PoolKey, PoolRegistration, ProtocolId};
#[cfg(feature = "solidly-v2")]
use crate::adapters::storage::SolidlyStorageLayout;
#[cfg(feature = "uniswap-v3")]
use crate::adapters::storage::V3StorageLayout;
#[cfg(any(feature = "uniswap-v2", feature = "uniswap-v3", feature = "solidly-v2"))]
use alloy_primitives::B256;
use alloy_primitives::{Address, Log, U256};
#[cfg(any(feature = "uniswap-v2", feature = "uniswap-v3", feature = "solidly-v2"))]
use alloy_sol_types::SolEvent;
pub mod derive {
use alloy_primitives::Address;
#[cfg(any(feature = "uniswap-v2", feature = "uniswap-v3", feature = "solidly-v2"))]
use alloy_primitives::{B256, U256, keccak256};
pub fn sort_tokens(a: Address, b: Address) -> (Address, Address) {
if a.as_slice() <= b.as_slice() {
(a, b)
} else {
(b, a)
}
}
pub fn pairs_among(tokens: &[Address]) -> Vec<(Address, Address)> {
let mut pairs = Vec::new();
for (i, &a) in tokens.iter().enumerate() {
for &b in &tokens[i + 1..] {
if a != b {
pairs.push(sort_tokens(a, b));
}
}
}
pairs.sort_unstable();
pairs.dedup();
pairs
}
#[cfg(feature = "uniswap-v2")]
pub fn v2_get_pair_slot(base_slot: U256, token0: Address, token1: Address) -> U256 {
nested_address_mapping_slot(base_slot, token0, token1)
}
#[cfg(feature = "uniswap-v3")]
pub fn v3_get_pool_slot(base_slot: U256, token0: Address, token1: Address, fee: u32) -> U256 {
let first = mapping_slot_address(base_slot, token0);
let second = mapping_slot_address(first, token1);
mapping_slot_u256(second, U256::from(fee))
}
#[cfg(feature = "uniswap-v3")]
pub fn v3_get_pool_slot_by_spacing(
base_slot: U256,
token0: Address,
token1: Address,
spacing: i32,
) -> U256 {
let first = mapping_slot_address(base_slot, token0);
let second = mapping_slot_address(first, token1);
mapping_slot_u256(second, i24_to_word(spacing))
}
#[cfg(feature = "uniswap-v3")]
pub fn v3_fee_amount_tick_spacing_slot(base_slot: U256, fee: u32) -> U256 {
mapping_slot_u256(base_slot, U256::from(fee))
}
#[cfg(feature = "uniswap-v3")]
pub fn v3_tick_spacing_fee_slot(base_slot: U256, spacing: i32) -> U256 {
mapping_slot_u256(base_slot, i24_to_word(spacing))
}
#[cfg(feature = "uniswap-v2")]
pub fn v2_pair_address(
factory: Address,
init_code_hash: B256,
token0: Address,
token1: Address,
) -> Address {
let mut salt_preimage = [0u8; 40];
salt_preimage[..20].copy_from_slice(token0.as_slice());
salt_preimage[20..].copy_from_slice(token1.as_slice());
create2_address(factory, keccak256(salt_preimage), init_code_hash)
}
#[cfg(feature = "uniswap-v3")]
pub fn v3_pool_address(
deployer: Address,
init_code_hash: B256,
token0: Address,
token1: Address,
fee: u32,
) -> Address {
let mut salt_preimage = [0u8; 96];
salt_preimage[12..32].copy_from_slice(token0.as_slice());
salt_preimage[44..64].copy_from_slice(token1.as_slice());
salt_preimage[92..96].copy_from_slice(&fee.to_be_bytes());
create2_address(deployer, keccak256(salt_preimage), init_code_hash)
}
#[cfg(feature = "uniswap-v3")]
pub fn v3_pool_address_by_spacing(
deployer: Address,
init_code_hash: B256,
token0: Address,
token1: Address,
spacing: i32,
) -> Address {
let mut salt_preimage = [0u8; 96];
salt_preimage[12..32].copy_from_slice(token0.as_slice());
salt_preimage[44..64].copy_from_slice(token1.as_slice());
salt_preimage[64..96].copy_from_slice(&i24_to_word(spacing).to_be_bytes::<32>());
create2_address(deployer, keccak256(salt_preimage), init_code_hash)
}
#[cfg(feature = "solidly-v2")]
pub fn solidly_get_pool_slot(
base_slot: U256,
token0: Address,
token1: Address,
stable: bool,
) -> U256 {
let first = mapping_slot_address(base_slot, token0);
let second = mapping_slot_address(first, token1);
mapping_slot_u256(second, U256::from(stable as u8))
}
#[cfg(feature = "solidly-v2")]
pub fn solidly_pool_address(
deployer: Address,
init_code_hash: B256,
token0: Address,
token1: Address,
stable: bool,
) -> Address {
let mut salt_preimage = [0u8; 41];
salt_preimage[..20].copy_from_slice(token0.as_slice());
salt_preimage[20..40].copy_from_slice(token1.as_slice());
salt_preimage[40] = stable as u8;
create2_address(deployer, keccak256(salt_preimage), init_code_hash)
}
#[cfg(feature = "uniswap-v2")]
fn nested_address_mapping_slot(base_slot: U256, outer: Address, inner: Address) -> U256 {
let outer_slot = mapping_slot_address(base_slot, outer);
mapping_slot_address(outer_slot, inner)
}
#[cfg(any(feature = "uniswap-v2", feature = "uniswap-v3", feature = "solidly-v2"))]
fn mapping_slot_address(base_slot: U256, key: Address) -> U256 {
let mut preimage = [0u8; 64];
preimage[12..32].copy_from_slice(key.as_slice());
preimage[32..64].copy_from_slice(&base_slot.to_be_bytes::<32>());
keccak256(preimage).into()
}
#[cfg(any(feature = "uniswap-v3", feature = "solidly-v2"))]
fn mapping_slot_u256(base_slot: U256, key: U256) -> U256 {
let mut preimage = [0u8; 64];
preimage[..32].copy_from_slice(&key.to_be_bytes::<32>());
preimage[32..64].copy_from_slice(&base_slot.to_be_bytes::<32>());
keccak256(preimage).into()
}
#[cfg(feature = "uniswap-v3")]
fn i24_to_word(value: i32) -> U256 {
if value < 0 {
let low = U256::from(value as i64 as u64);
let high_ones = (U256::MAX >> 64) << 64;
low | high_ones
} else {
U256::from(value as u64)
}
}
#[cfg(any(feature = "uniswap-v2", feature = "uniswap-v3", feature = "solidly-v2"))]
fn create2_address(deployer: Address, salt: B256, init_code_hash: B256) -> Address {
let mut preimage = [0u8; 85];
preimage[0] = 0xff;
preimage[1..21].copy_from_slice(deployer.as_slice());
preimage[21..53].copy_from_slice(salt.as_slice());
preimage[53..85].copy_from_slice(init_code_hash.as_slice());
let hash = keccak256(preimage);
Address::from_slice(&hash.as_slice()[12..])
}
}
#[cfg(feature = "uniswap-v2")]
const UNISWAP_V2_GET_PAIR_BASE_SLOT: U256 = U256::from_limbs([2, 0, 0, 0]);
#[cfg(feature = "uniswap-v3")]
const UNISWAP_V3_FEE_AMOUNT_TICK_SPACING_BASE_SLOT: U256 = U256::from_limbs([4, 0, 0, 0]);
#[cfg(feature = "uniswap-v3")]
const UNISWAP_V3_GET_POOL_BASE_SLOT: U256 = U256::from_limbs([5, 0, 0, 0]);
#[cfg(feature = "uniswap-v3")]
const PANCAKE_V3_GET_POOL_BASE_SLOT: U256 = U256::from_limbs([2, 0, 0, 0]);
#[cfg(feature = "uniswap-v3")]
const PANCAKE_V3_FEE_AMOUNT_TICK_SPACING_BASE_SLOT: U256 = U256::from_limbs([1, 0, 0, 0]);
#[cfg(feature = "uniswap-v3")]
const SLIPSTREAM_GET_POOL_BASE_SLOT: U256 = U256::from_limbs([6, 0, 0, 0]);
#[cfg(feature = "uniswap-v3")]
const UNISWAP_V3_CANONICAL_FEE_TIERS: [u32; 4] = [100, 500, 3_000, 10_000];
#[cfg(feature = "uniswap-v3")]
const PANCAKE_V3_FEE_TIERS: [u32; 4] = [100, 500, 2_500, 10_000];
#[cfg(feature = "uniswap-v3")]
const SLIPSTREAM_TICK_SPACINGS: [i32; 5] = [1, 50, 100, 200, 2_000];
#[cfg(feature = "uniswap-v3")]
const PANCAKE_V3_POOL_DEPLOYER: Address =
alloy_primitives::address!("41ff9AA7e16B8B1a8a8dc4f0eFacd93D02d071c9");
#[cfg(feature = "uniswap-v3")]
const PANCAKE_V3_INIT_CODE_HASH: B256 =
alloy_primitives::b256!("6ce8eb472fa82df5469c6ab6d485f17c3ad13c8cd7af59b3d4a8026c5ce0f7e2");
#[cfg(feature = "uniswap-v3")]
const PANCAKE_V3_QUOTER_V2: Address =
alloy_primitives::address!("B048Bbc1Ee6b733FFfCFb9e9CeF7375518e25997");
#[cfg(feature = "solidly-v2")]
const SOLIDLY_GET_POOL_BASE_SLOT: U256 = U256::from_limbs([5, 0, 0, 0]);
#[cfg(feature = "uniswap-v2")]
mod v2_factory_events {
alloy_sol_types::sol! {
event PairCreated(address indexed token0, address indexed token1, address pair, uint256 allPairsLength);
}
}
#[cfg(feature = "uniswap-v2")]
use v2_factory_events::PairCreated;
#[cfg(feature = "uniswap-v3")]
mod cl_factory_events {
alloy_sol_types::sol! {
event PoolCreated(address indexed token0, address indexed token1, uint24 indexed fee, int24 tickSpacing, address pool);
event PoolCreatedTickSpacing(address indexed token0, address indexed token1, int24 indexed tickSpacing, address pool, uint24 fee);
}
}
#[cfg(feature = "uniswap-v3")]
use cl_factory_events::{PoolCreated, PoolCreatedTickSpacing};
#[cfg(feature = "solidly-v2")]
mod solidly_events {
alloy_sol_types::sol! {
event PoolCreated(address indexed token0, address indexed token1, bool indexed stable, address pool, uint256 allPoolsLength);
}
}
#[cfg(feature = "solidly-v2")]
use solidly_events::PoolCreated as SolidlyPoolCreated;
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PoolQuery {
pairs: Vec<(Address, Address)>,
protocol: Option<ProtocolId>,
}
impl PoolQuery {
pub fn pair(a: Address, b: Address) -> Self {
Self {
pairs: vec![derive::sort_tokens(a, b)],
protocol: None,
}
}
pub fn basket(tokens: impl IntoIterator<Item = Address>) -> Self {
let tokens: Vec<Address> = tokens.into_iter().collect();
Self {
pairs: derive::pairs_among(&tokens),
protocol: None,
}
}
pub fn pairs(pairs: impl IntoIterator<Item = (Address, Address)>) -> Self {
let mut pairs: Vec<(Address, Address)> = pairs
.into_iter()
.map(|(a, b)| derive::sort_tokens(a, b))
.collect();
pairs.sort_unstable();
pairs.dedup();
Self {
pairs,
protocol: None,
}
}
pub fn on(mut self, protocol: ProtocolId) -> Self {
self.protocol = Some(protocol);
self
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TokenEdgeDiscoveryRequest {
token: Address,
connectors: Vec<Address>,
protocol: Option<ProtocolId>,
}
impl TokenEdgeDiscoveryRequest {
pub fn new(token: Address, connectors: impl IntoIterator<Item = Address>) -> Self {
let mut connectors: Vec<_> = connectors
.into_iter()
.filter(|connector| *connector != token)
.collect();
connectors.sort_unstable();
connectors.dedup();
Self {
token,
connectors,
protocol: None,
}
}
pub const fn with_protocol(mut self, protocol: ProtocolId) -> Self {
self.protocol = Some(protocol);
self
}
pub const fn token(&self) -> Address {
self.token
}
pub fn connectors(&self) -> &[Address] {
&self.connectors
}
pub const fn protocol(&self) -> Option<ProtocolId> {
self.protocol
}
pub fn query(&self) -> PoolQuery {
let query = PoolQuery::pairs(
self.connectors
.iter()
.copied()
.map(|connector| (self.token, connector)),
);
match self.protocol {
Some(protocol) => query.on(protocol),
None => query,
}
}
}
#[derive(Clone, Debug)]
pub struct TokenEdgeDiscoveryReport {
request: TokenEdgeDiscoveryRequest,
discovered: Vec<DiscoveredPool>,
}
impl TokenEdgeDiscoveryReport {
pub fn new(request: TokenEdgeDiscoveryRequest, discovered: Vec<DiscoveredPool>) -> Self {
Self {
request,
discovered,
}
}
pub const fn request(&self) -> &TokenEdgeDiscoveryRequest {
&self.request
}
pub fn discovered(&self) -> &[DiscoveredPool] {
&self.discovered
}
pub fn into_parts(self) -> (TokenEdgeDiscoveryRequest, Vec<DiscoveredPool>) {
(self.request, self.discovered)
}
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FactoryConfig {
#[cfg(feature = "uniswap-v2")]
pub uniswap_v2: Vec<UniswapV2FactoryConfig>,
#[cfg(feature = "uniswap-v3")]
pub concentrated_liquidity: Vec<ClFactorySpec>,
#[cfg(feature = "solidly-v2")]
pub solidly: Vec<SolidlyFactoryConfig>,
pub verify_derivations: bool,
}
impl Default for FactoryConfig {
fn default() -> Self {
Self {
#[cfg(feature = "uniswap-v2")]
uniswap_v2: Vec::new(),
#[cfg(feature = "uniswap-v3")]
concentrated_liquidity: Vec::new(),
#[cfg(feature = "solidly-v2")]
solidly: Vec::new(),
verify_derivations: true,
}
}
}
impl FactoryConfig {
#[cfg(feature = "uniswap-v2")]
pub fn with_uniswap_v2(mut self, config: UniswapV2FactoryConfig) -> Self {
self.uniswap_v2.push(config);
self
}
#[cfg(feature = "uniswap-v2")]
pub fn with_uniswap_v2_factory(self, factory: Address) -> Self {
self.with_uniswap_v2(UniswapV2FactoryConfig::uniswap_v2(factory))
}
#[cfg(feature = "uniswap-v3")]
pub fn with_concentrated_liquidity(mut self, spec: ClFactorySpec) -> Self {
self.concentrated_liquidity.push(spec);
self
}
#[cfg(feature = "uniswap-v3")]
pub fn with_uniswap_v3(self, spec: ClFactorySpec) -> Self {
self.with_concentrated_liquidity(spec)
}
#[cfg(feature = "uniswap-v3")]
pub fn with_uniswap_v3_factory(self, factory: Address) -> Self {
self.with_concentrated_liquidity(ClFactorySpec::uniswap_v3(factory))
}
#[cfg(feature = "uniswap-v3")]
pub fn with_pancake_v3_factory(self, factory: Address) -> Self {
self.with_concentrated_liquidity(ClFactorySpec::pancake_v3(factory))
}
#[cfg(feature = "uniswap-v3")]
pub fn with_slipstream_factory(self, factory: Address) -> Self {
self.with_concentrated_liquidity(ClFactorySpec::slipstream(factory))
}
#[cfg(feature = "solidly-v2")]
pub fn with_solidly(mut self, config: SolidlyFactoryConfig) -> Self {
self.solidly.push(config);
self
}
#[cfg(feature = "solidly-v2")]
pub fn with_solidly_factory(self, factory: Address) -> Self {
self.with_solidly(SolidlyFactoryConfig::aerodrome(factory))
}
pub fn with_verify_derivations(mut self, verify_derivations: bool) -> Self {
self.verify_derivations = verify_derivations;
self
}
}
#[cfg(feature = "uniswap-v2")]
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct UniswapV2FactoryConfig {
pub factory: Address,
pub get_pair_base_slot: U256,
pub init_code_hash: Option<B256>,
pub fee_bps: Option<u32>,
}
#[cfg(feature = "uniswap-v2")]
impl UniswapV2FactoryConfig {
pub fn uniswap_v2(factory: Address) -> Self {
Self {
factory,
get_pair_base_slot: UNISWAP_V2_GET_PAIR_BASE_SLOT,
init_code_hash: None,
fee_bps: None,
}
}
pub fn with_get_pair_base_slot(mut self, slot: U256) -> Self {
self.get_pair_base_slot = slot;
self
}
pub fn with_init_code_hash(mut self, hash: B256) -> Self {
self.init_code_hash = Some(hash);
self
}
pub fn with_fee_bps(mut self, fee_bps: u32) -> Self {
self.fee_bps = Some(fee_bps);
self
}
}
#[cfg(feature = "uniswap-v3")]
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ClKeying {
Fee {
tiers: Vec<u32>,
fee_amount_tick_spacing_base_slot: U256,
},
TickSpacing {
spacings: Vec<i32>,
},
}
#[cfg(feature = "uniswap-v3")]
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FeeSource {
Key,
FactoryMapping {
base_slot: U256,
},
Fixed(u32),
}
#[cfg(feature = "uniswap-v3")]
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ClCreate2 {
pub deployer: Option<Address>,
pub init_code_hash: B256,
}
#[cfg(feature = "uniswap-v3")]
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ClFactorySpec {
pub protocol: ProtocolId,
pub factory: Address,
pub get_pool_base_slot: U256,
pub keying: ClKeying,
pub fee_source: FeeSource,
pub create2: Option<ClCreate2>,
pub quoter: Option<Address>,
pub creation_topic0: Option<B256>,
pub verify_derivations: bool,
}
#[cfg(feature = "uniswap-v3")]
impl ClFactorySpec {
pub fn fee_keyed(
protocol: ProtocolId,
factory: Address,
get_pool_base_slot: U256,
fee_amount_tick_spacing_base_slot: U256,
tiers: impl IntoIterator<Item = u32>,
) -> Self {
Self {
protocol,
factory,
get_pool_base_slot,
keying: ClKeying::Fee {
tiers: tiers.into_iter().collect(),
fee_amount_tick_spacing_base_slot,
},
fee_source: FeeSource::Key,
create2: None,
quoter: None,
creation_topic0: None,
verify_derivations: false,
}
}
pub fn tick_spacing_keyed(
protocol: ProtocolId,
factory: Address,
get_pool_base_slot: U256,
spacings: impl IntoIterator<Item = i32>,
) -> Self {
Self {
protocol,
factory,
get_pool_base_slot,
keying: ClKeying::TickSpacing {
spacings: spacings.into_iter().collect(),
},
fee_source: FeeSource::Fixed(0),
create2: None,
quoter: None,
creation_topic0: None,
verify_derivations: false,
}
}
pub fn with_quoter(mut self, quoter: Address) -> Self {
self.quoter = Some(quoter);
self
}
pub fn with_create2(mut self, deployer: Option<Address>, init_code_hash: B256) -> Self {
self.create2 = Some(ClCreate2 {
deployer,
init_code_hash,
});
self
}
pub fn with_verify_derivations(mut self, verify: bool) -> Self {
self.verify_derivations = verify;
self
}
pub fn with_creation_topic0(mut self, topic0: B256) -> Self {
self.creation_topic0 = Some(topic0);
self
}
pub fn with_fee_source(mut self, fee_source: FeeSource) -> Self {
self.fee_source = fee_source;
self
}
pub fn with_fee_tiers(mut self, fee_tiers: impl IntoIterator<Item = u32>) -> Self {
if let ClKeying::Fee { tiers, .. } = &mut self.keying {
*tiers = fee_tiers.into_iter().collect();
}
self
}
pub fn with_tick_spacings(mut self, spacings: impl IntoIterator<Item = i32>) -> Self {
if let ClKeying::TickSpacing { spacings: s } = &mut self.keying {
*s = spacings.into_iter().collect();
}
self
}
pub fn fee_amount_tick_spacing_base_slot(&self) -> Option<U256> {
match &self.keying {
ClKeying::Fee {
fee_amount_tick_spacing_base_slot,
..
} => Some(*fee_amount_tick_spacing_base_slot),
ClKeying::TickSpacing { .. } => None,
}
}
pub fn fee_tiers(&self) -> &[u32] {
match &self.keying {
ClKeying::Fee { tiers, .. } => tiers,
ClKeying::TickSpacing { .. } => &[],
}
}
pub fn tick_spacings(&self) -> &[i32] {
match &self.keying {
ClKeying::TickSpacing { spacings } => spacings,
ClKeying::Fee { .. } => &[],
}
}
pub fn uniswap_v3(factory: Address) -> Self {
Self::fee_keyed(
ProtocolId::UniswapV3,
factory,
UNISWAP_V3_GET_POOL_BASE_SLOT,
UNISWAP_V3_FEE_AMOUNT_TICK_SPACING_BASE_SLOT,
UNISWAP_V3_CANONICAL_FEE_TIERS,
)
.with_creation_topic0(PoolCreated::SIGNATURE_HASH)
}
pub fn sushi_v3(factory: Address) -> Self {
Self::uniswap_v3(factory)
}
pub fn pancake_v3(factory: Address) -> Self {
Self::fee_keyed(
ProtocolId::PancakeV3,
factory,
PANCAKE_V3_GET_POOL_BASE_SLOT,
PANCAKE_V3_FEE_AMOUNT_TICK_SPACING_BASE_SLOT,
PANCAKE_V3_FEE_TIERS,
)
.with_create2(Some(PANCAKE_V3_POOL_DEPLOYER), PANCAKE_V3_INIT_CODE_HASH)
.with_quoter(PANCAKE_V3_QUOTER_V2)
.with_creation_topic0(PoolCreated::SIGNATURE_HASH)
.with_verify_derivations(true)
}
pub fn slipstream(factory: Address) -> Self {
Self::tick_spacing_keyed(
ProtocolId::Slipstream,
factory,
SLIPSTREAM_GET_POOL_BASE_SLOT,
SLIPSTREAM_TICK_SPACINGS,
)
.with_creation_topic0(PoolCreatedTickSpacing::SIGNATURE_HASH)
}
}
#[cfg(feature = "uniswap-v3")]
pub type UniswapV3FactoryConfig = ClFactorySpec;
#[cfg(feature = "solidly-v2")]
const SOLIDLY_AERODROME_LAYOUT: SolidlyStorageLayout = SolidlyStorageLayout::new(
U256::from_limbs([20, 0, 0, 0]),
U256::from_limbs([21, 0, 0, 0]),
U256::from_limbs([13, 0, 0, 0]),
U256::from_limbs([14, 0, 0, 0]),
);
#[cfg(feature = "solidly-v2")]
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SolidlyFactoryConfig {
pub factory: Address,
pub get_pool_base_slot: U256,
pub storage_layout: SolidlyStorageLayout,
#[cfg(feature = "uniswap-v3")]
pub create2: Option<ClCreate2>,
#[cfg(not(feature = "uniswap-v3"))]
pub create2: Option<SolidlyCreate2>,
pub creation_topic0: Option<B256>,
pub verify_derivations: bool,
}
#[cfg(all(feature = "solidly-v2", not(feature = "uniswap-v3")))]
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SolidlyCreate2 {
pub deployer: Option<Address>,
pub init_code_hash: B256,
}
#[cfg(all(feature = "solidly-v2", feature = "uniswap-v3"))]
type SolidlyCreate2Params = ClCreate2;
#[cfg(all(feature = "solidly-v2", not(feature = "uniswap-v3")))]
type SolidlyCreate2Params = SolidlyCreate2;
#[cfg(feature = "solidly-v2")]
impl SolidlyFactoryConfig {
pub fn new(
factory: Address,
get_pool_base_slot: U256,
storage_layout: SolidlyStorageLayout,
) -> Self {
Self {
factory,
get_pool_base_slot,
storage_layout,
create2: None,
creation_topic0: None,
verify_derivations: false,
}
}
pub fn aerodrome(factory: Address) -> Self {
Self::new(
factory,
SOLIDLY_GET_POOL_BASE_SLOT,
SOLIDLY_AERODROME_LAYOUT,
)
.with_creation_topic0(SolidlyPoolCreated::SIGNATURE_HASH)
}
pub fn velodrome(factory: Address) -> Self {
Self::aerodrome(factory)
}
pub fn with_create2(mut self, deployer: Option<Address>, init_code_hash: B256) -> Self {
self.create2 = Some(SolidlyCreate2Params {
deployer,
init_code_hash,
});
self
}
pub fn with_verify_derivations(mut self, verify: bool) -> Self {
self.verify_derivations = verify;
self
}
pub fn with_creation_topic0(mut self, topic0: B256) -> Self {
self.creation_topic0 = Some(topic0);
self
}
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DiscoverySource {
Query,
CreationEvent {
block_number: Option<u64>,
log_index: Option<u64>,
},
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct CreationLogContext {
pub block_number: Option<u64>,
pub log_index: Option<u64>,
}
impl CreationLogContext {
pub const fn new(block_number: Option<u64>, log_index: Option<u64>) -> Self {
Self {
block_number,
log_index,
}
}
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct DiscoveredPool {
pub key: PoolKey,
pub registration: PoolRegistration,
pub source: DiscoverySource,
}
impl DiscoveredPool {
pub fn new(key: PoolKey, registration: PoolRegistration, source: DiscoverySource) -> Self {
Self {
key,
registration,
source,
}
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum DiscoveryError {
MissingFactory(ProtocolId),
Factory(Box<dyn std::error::Error + Send + Sync + 'static>),
Malformed(&'static str),
DerivationMismatch {
mapping: Address,
derived: Address,
},
PreparedModeUnsupported {
protocol: ProtocolId,
factory: Address,
},
MissingPreparedValue {
address: Address,
slot: U256,
},
DuplicatePreparedValue {
address: Address,
slot: U256,
},
PreparedFactoryMismatch {
protocol: ProtocolId,
factory: Address,
},
}
impl fmt::Display for DiscoveryError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MissingFactory(protocol) => write!(f, "missing factory for {protocol:?}"),
Self::Factory(err) => write!(f, "factory query failed: {err}"),
Self::Malformed(message) => write!(f, "malformed factory response: {message}"),
Self::DerivationMismatch { mapping, derived } => write!(
f,
"factory mapping answer {mapping:?} disagrees with CREATE2 derivation {derived:?}"
),
Self::PreparedModeUnsupported { protocol, factory } => write!(
f,
"factory {factory:?} for {protocol:?} requires mutable-cache discovery and cannot run in prepared mode"
),
Self::MissingPreparedValue { address, slot } => write!(
f,
"prepared discovery omitted storage value {address:?}[{slot}]"
),
Self::DuplicatePreparedValue { address, slot } => write!(
f,
"prepared discovery supplied storage value {address:?}[{slot}] more than once"
),
Self::PreparedFactoryMismatch { protocol, factory } => write!(
f,
"prepared discovery factory {protocol:?}@{factory:?} is absent or moved"
),
}
}
}
impl std::error::Error for DiscoveryError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Factory(err) => Some(&**err as &(dyn std::error::Error + 'static)),
_ => None,
}
}
}
impl From<super::CacheError> for DiscoveryError {
fn from(err: super::CacheError) -> Self {
Self::Factory(Box::new(err))
}
}
pub trait PoolFactory: Send + Sync {
fn protocol(&self) -> ProtocolId;
fn factory_address(&self) -> Address;
fn find_pools(
&self,
cache: &mut dyn AdapterCache,
pair: (Address, Address),
) -> Result<Vec<DiscoveredPool>, DiscoveryError> {
let slots = self.candidate_reads(&[pair]);
if slots.is_empty() {
return Ok(Vec::new());
}
let values = cache.read_storage_slots(&slots)?;
let resolved = slots.into_iter().zip(values).collect();
self.assemble_pairs(&[pair], &resolved)
}
fn candidate_reads(&self, pairs: &[(Address, Address)]) -> Vec<(Address, U256)> {
let _ = pairs;
Vec::new()
}
fn assemble_pairs(
&self,
pairs: &[(Address, Address)],
values: &HashMap<(Address, U256), U256>,
) -> Result<Vec<DiscoveredPool>, DiscoveryError> {
let _ = (pairs, values);
Ok(Vec::new())
}
fn creation_sources(&self) -> Vec<EventSource>;
fn decode_creation(
&self,
log: &Log,
context: CreationLogContext,
) -> Result<Option<DiscoveredPool>, DiscoveryError>;
}
#[derive(Clone, Debug)]
struct PreparedFactoryQuery {
factory_index: usize,
protocol: ProtocolId,
factory: Address,
pairs: Vec<(Address, Address)>,
reads: Vec<(Address, U256)>,
legacy: bool,
}
#[derive(Clone, Debug)]
pub struct PreparedDiscoveryReads {
reads: Vec<(Address, U256)>,
factories: Vec<PreparedFactoryQuery>,
}
impl PreparedDiscoveryReads {
pub fn reads(&self) -> &[(Address, U256)] {
&self.reads
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum LegacyFactoryMode {
Allow,
Reject,
}
#[derive(Default)]
pub struct PoolDiscovery {
factories: Vec<Box<dyn PoolFactory>>,
}
impl PoolDiscovery {
pub fn new(factories: impl IntoIterator<Item = Box<dyn PoolFactory>>) -> Self {
let mut discovery = Self {
factories: Vec::new(),
};
for factory in factories {
discovery.push_unique(factory);
}
discovery
}
pub fn for_registry(registry: &AdapterRegistry, config: FactoryConfig) -> Self {
let mut factories = Vec::new();
let mut seen = Vec::new();
for adapter in registry.adapters() {
let ptr = std::sync::Arc::as_ptr(adapter);
if seen.contains(&ptr) {
continue;
}
seen.push(ptr);
factories.extend(adapter.pool_factories(&config));
}
Self::new(factories)
}
pub fn with_factory(mut self, factory: Box<dyn PoolFactory>) -> Self {
self.push_unique(factory);
self
}
fn push_unique(&mut self, factory: Box<dyn PoolFactory>) {
let identity = (factory.protocol(), factory.factory_address());
if self
.factories
.iter()
.any(|existing| (existing.protocol(), existing.factory_address()) == identity)
{
return;
}
self.factories.push(factory);
}
pub fn prepare_reads(
&self,
queries: impl IntoIterator<Item = PoolQuery>,
) -> Result<PreparedDiscoveryReads, DiscoveryError> {
self.prepare_reads_with_mode(queries, LegacyFactoryMode::Reject)
}
pub fn assemble_prepared(
&self,
prepared: &PreparedDiscoveryReads,
values: impl IntoIterator<Item = ((Address, U256), U256)>,
) -> Result<Vec<DiscoveredPool>, DiscoveryError> {
let mut resolved = HashMap::new();
for ((address, slot), value) in values {
if resolved.insert((address, slot), value).is_some() {
return Err(DiscoveryError::DuplicatePreparedValue { address, slot });
}
}
for &(address, slot) in prepared.reads() {
if !resolved.contains_key(&(address, slot)) {
return Err(DiscoveryError::MissingPreparedValue { address, slot });
}
}
self.assemble_plan(prepared, &resolved, None)
}
fn prepare_reads_with_mode(
&self,
queries: impl IntoIterator<Item = PoolQuery>,
legacy_mode: LegacyFactoryMode,
) -> Result<PreparedDiscoveryReads, DiscoveryError> {
let queries: Vec<_> = queries.into_iter().collect();
for query in &queries {
if let Some(protocol) = query.protocol
&& !self
.factories
.iter()
.any(|factory| factory.protocol() == protocol)
{
return Err(DiscoveryError::MissingFactory(protocol));
}
}
let mut reads = Vec::new();
let mut factories = Vec::new();
for query in queries {
for (factory_index, factory) in
self.factories.iter().enumerate().filter(|(_, factory)| {
query
.protocol
.is_none_or(|protocol| factory.protocol() == protocol)
})
{
let mut factory_reads = factory.candidate_reads(&query.pairs);
factory_reads.sort_unstable();
factory_reads.dedup();
let legacy = !query.pairs.is_empty() && factory_reads.is_empty();
if legacy && legacy_mode == LegacyFactoryMode::Reject {
return Err(DiscoveryError::PreparedModeUnsupported {
protocol: factory.protocol(),
factory: factory.factory_address(),
});
}
reads.extend(factory_reads.iter().copied());
factories.push(PreparedFactoryQuery {
factory_index,
protocol: factory.protocol(),
factory: factory.factory_address(),
pairs: query.pairs.clone(),
reads: factory_reads,
legacy,
});
}
}
reads.sort_unstable();
reads.dedup();
Ok(PreparedDiscoveryReads { reads, factories })
}
fn assemble_plan(
&self,
prepared: &PreparedDiscoveryReads,
resolved: &HashMap<(Address, U256), U256>,
mut cache: Option<&mut dyn AdapterCache>,
) -> Result<Vec<DiscoveredPool>, DiscoveryError> {
let mut found = Vec::new();
let mut seen = std::collections::HashSet::new();
for plan in &prepared.factories {
let factory = self.factories.get(plan.factory_index).ok_or(
DiscoveryError::PreparedFactoryMismatch {
protocol: plan.protocol,
factory: plan.factory,
},
)?;
if factory.protocol() != plan.protocol || factory.factory_address() != plan.factory {
return Err(DiscoveryError::PreparedFactoryMismatch {
protocol: plan.protocol,
factory: plan.factory,
});
}
debug_assert!(plan.reads.iter().all(|read| prepared.reads.contains(read)));
let pools = if plan.legacy {
let cache = cache
.as_deref_mut()
.expect("legacy plans are never exposed by prepare_reads");
let mut pools = Vec::new();
for pair in &plan.pairs {
pools.extend(factory.find_pools(cache, *pair)?);
}
pools
} else {
factory.assemble_pairs(&plan.pairs, resolved)?
};
for pool in pools {
if seen.insert(pool.key.clone()) {
found.push(pool);
}
}
}
Ok(found)
}
pub fn find_many(
&self,
cache: &mut dyn AdapterCache,
queries: impl IntoIterator<Item = PoolQuery>,
) -> Result<Vec<DiscoveredPool>, DiscoveryError> {
let prepared = self.prepare_reads_with_mode(queries, LegacyFactoryMode::Allow)?;
let resolved: HashMap<(Address, U256), U256> = if prepared.reads.is_empty() {
HashMap::new()
} else {
let values = cache.read_storage_slots(&prepared.reads)?;
prepared.reads.iter().copied().zip(values).collect()
};
self.assemble_plan(&prepared, &resolved, Some(cache))
}
pub fn find(
&self,
cache: &mut dyn AdapterCache,
query: PoolQuery,
) -> Result<Vec<DiscoveredPool>, DiscoveryError> {
self.find_many(cache, std::iter::once(query))
}
pub fn creation_sources(&self) -> Vec<EventSource> {
self.factories
.iter()
.flat_map(|factory| factory.creation_sources())
.collect()
}
pub fn decode_creation(
&self,
log: &Log,
context: CreationLogContext,
) -> Result<Option<DiscoveredPool>, DiscoveryError> {
for factory in &self.factories {
if let Some(pool) = factory.decode_creation(log, context)? {
return Ok(Some(pool));
}
}
Ok(None)
}
}
#[cfg(feature = "uniswap-v2")]
#[derive(Debug)]
pub(crate) struct UniswapV2Factory {
config: UniswapV2FactoryConfig,
verify_derivations: bool,
derivation_verified: std::sync::OnceLock<()>,
}
#[cfg(feature = "uniswap-v2")]
impl UniswapV2Factory {
pub(crate) fn new(config: UniswapV2FactoryConfig, verify_derivations: bool) -> Self {
Self {
config,
verify_derivations,
derivation_verified: std::sync::OnceLock::new(),
}
}
fn registration(
&self,
pair: Address,
token0: Address,
token1: Address,
source: DiscoverySource,
) -> DiscoveredPool {
let mut metadata = UniswapV2Metadata::default()
.with_token0(token0)
.with_token1(token1);
if let Some(fee_bps) = self.config.fee_bps {
metadata = metadata.with_fee_bps(fee_bps);
}
let registration = PoolRegistration::new(PoolKey::UniswapV2(pair))
.with_state_address(pair)
.with_metadata(ProtocolMetadata::UniswapV2(metadata));
let adapter = crate::adapters::UniswapV2Adapter::default();
let sources = super::AmmAdapter::event_sources(&adapter, ®istration);
let registration = registration.with_event_sources(sources);
DiscoveredPool {
key: registration.key.clone(),
registration,
source,
}
}
fn ensure_derivation_matches(
&self,
mapping: Address,
token0: Address,
token1: Address,
) -> Result<(), DiscoveryError> {
if !self.verify_derivations || self.derivation_verified.get().is_some() {
return Ok(());
}
let Some(init_code_hash) = self.config.init_code_hash else {
return Ok(());
};
let derived = derive::v2_pair_address(self.config.factory, init_code_hash, token0, token1);
if derived != mapping {
return Err(DiscoveryError::DerivationMismatch { mapping, derived });
}
let _ = self.derivation_verified.set(());
Ok(())
}
}
#[cfg(feature = "uniswap-v2")]
impl PoolFactory for UniswapV2Factory {
fn protocol(&self) -> ProtocolId {
ProtocolId::UniswapV2
}
fn factory_address(&self) -> Address {
self.config.factory
}
fn candidate_reads(&self, pairs: &[(Address, Address)]) -> Vec<(Address, U256)> {
pairs
.iter()
.map(|(token0, token1)| {
(
self.config.factory,
derive::v2_get_pair_slot(self.config.get_pair_base_slot, *token0, *token1),
)
})
.collect()
}
fn assemble_pairs(
&self,
pairs: &[(Address, Address)],
values: &HashMap<(Address, U256), U256>,
) -> Result<Vec<DiscoveredPool>, DiscoveryError> {
let mut found = Vec::new();
for (token0, token1) in pairs {
let slot = derive::v2_get_pair_slot(self.config.get_pair_base_slot, *token0, *token1);
let Some(&word) = values.get(&(self.config.factory, slot)) else {
continue;
};
let pair = address_from_word(word)?;
if pair == Address::ZERO {
continue;
}
self.ensure_derivation_matches(pair, *token0, *token1)?;
found.push(self.registration(pair, *token0, *token1, DiscoverySource::Query));
}
Ok(found)
}
fn creation_sources(&self) -> Vec<EventSource> {
vec![EventSource::adapter_defined(
self.config.factory,
vec![PairCreated::SIGNATURE_HASH],
)]
}
fn decode_creation(
&self,
log: &Log,
context: CreationLogContext,
) -> Result<Option<DiscoveredPool>, DiscoveryError> {
if log.address != self.config.factory
|| log.topics().first() != Some(&PairCreated::SIGNATURE_HASH)
{
return Ok(None);
}
let event = PairCreated::decode_log(log)
.map_err(|_| DiscoveryError::Malformed("PairCreated failed to decode"))?
.data;
self.ensure_derivation_matches(event.pair, event.token0, event.token1)?;
Ok(Some(self.registration(
event.pair,
event.token0,
event.token1,
DiscoverySource::CreationEvent {
block_number: context.block_number,
log_index: context.log_index,
},
)))
}
}
#[cfg(feature = "uniswap-v3")]
#[derive(Debug)]
pub struct ConcentratedLiquidityFactory {
spec: ClFactorySpec,
derivation_verified: std::sync::OnceLock<()>,
}
#[cfg(feature = "uniswap-v3")]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum ClLookupKey {
Fee(u32),
TickSpacing(i32),
}
#[cfg(feature = "uniswap-v3")]
impl ConcentratedLiquidityFactory {
pub fn new(spec: ClFactorySpec) -> Self {
Self {
spec,
derivation_verified: std::sync::OnceLock::new(),
}
}
fn registration(
&self,
pool: Address,
token0: Address,
token1: Address,
fee: u32,
tick_spacing: i32,
source: DiscoverySource,
) -> DiscoveredPool {
let storage_layout = match self.spec.protocol {
ProtocolId::UniswapV3 => V3StorageLayout::uniswap(tick_spacing),
ProtocolId::PancakeV3 => V3StorageLayout::pancake(tick_spacing),
ProtocolId::Slipstream => V3StorageLayout::slipstream(tick_spacing),
_ => V3StorageLayout::uniswap(tick_spacing),
};
let mut metadata = V3Metadata::default()
.with_token0(token0)
.with_token1(token1)
.with_tick_spacing(tick_spacing)
.with_storage_layout(storage_layout)
.with_factory(self.spec.factory);
if fee != 0 {
metadata = metadata.with_fee(fee);
}
let metadata = if let Some(quoter) = self.spec.quoter {
metadata.with_quoter(quoter)
} else {
metadata
};
let key = match self.spec.protocol {
ProtocolId::PancakeV3 => PoolKey::PancakeV3(pool),
ProtocolId::Slipstream => PoolKey::Slipstream(pool),
_ => PoolKey::UniswapV3(pool),
};
let protocol_metadata = match self.spec.protocol {
ProtocolId::PancakeV3 => ProtocolMetadata::PancakeV3(metadata),
ProtocolId::Slipstream => ProtocolMetadata::Slipstream(metadata),
_ => ProtocolMetadata::UniswapV3(metadata),
};
let registration = PoolRegistration::new(key)
.with_state_address(pool)
.with_metadata(protocol_metadata);
let adapter = crate::adapters::ConcentratedLiquidityAdapter::default();
let sources = super::AmmAdapter::event_sources(&adapter, ®istration);
let registration = registration.with_event_sources(sources);
DiscoveredPool {
key: registration.key.clone(),
registration,
source,
}
}
fn ensure_derivation_matches(
&self,
mapping: Address,
token0: Address,
token1: Address,
key: ClLookupKey,
) -> Result<(), DiscoveryError> {
if !self.spec.verify_derivations || self.derivation_verified.get().is_some() {
return Ok(());
}
let Some(create2) = self.spec.create2 else {
return Ok(());
};
let deployer = create2.deployer.unwrap_or(self.spec.factory);
let derived = match key {
ClLookupKey::Fee(fee) => {
derive::v3_pool_address(deployer, create2.init_code_hash, token0, token1, fee)
}
ClLookupKey::TickSpacing(spacing) => derive::v3_pool_address_by_spacing(
deployer,
create2.init_code_hash,
token0,
token1,
spacing,
),
};
if derived != mapping {
return Err(DiscoveryError::DerivationMismatch { mapping, derived });
}
let _ = self.derivation_verified.set(());
Ok(())
}
fn fee_for_key(
&self,
key: ClLookupKey,
values: &HashMap<(Address, U256), U256>,
) -> Result<u32, DiscoveryError> {
match self.spec.fee_source {
FeeSource::Key => match key {
ClLookupKey::Fee(fee) => Ok(fee),
ClLookupKey::TickSpacing(_) => Err(DiscoveryError::Malformed(
"fee source Key is not valid for tickSpacing-keyed factories",
)),
},
FeeSource::Fixed(fee) => Ok(fee),
FeeSource::FactoryMapping { base_slot } => {
let slot = match key {
ClLookupKey::Fee(fee) => {
derive::v3_fee_amount_tick_spacing_slot(base_slot, fee)
}
ClLookupKey::TickSpacing(spacing) => {
derive::v3_tick_spacing_fee_slot(base_slot, spacing)
}
};
let word = values
.get(&(self.spec.factory, slot))
.copied()
.unwrap_or_default();
Ok(u32_from_word(word))
}
}
}
}
#[cfg(feature = "uniswap-v3")]
impl PoolFactory for ConcentratedLiquidityFactory {
fn protocol(&self) -> ProtocolId {
self.spec.protocol
}
fn factory_address(&self) -> Address {
self.spec.factory
}
fn candidate_reads(&self, pairs: &[(Address, Address)]) -> Vec<(Address, U256)> {
let mut slots = Vec::new();
match &self.spec.keying {
ClKeying::Fee {
tiers,
fee_amount_tick_spacing_base_slot,
} => {
for &fee in tiers {
slots.push((
self.spec.factory,
derive::v3_fee_amount_tick_spacing_slot(
*fee_amount_tick_spacing_base_slot,
fee,
),
));
}
for (token0, token1) in pairs {
for &fee in tiers {
slots.push((
self.spec.factory,
derive::v3_get_pool_slot(
self.spec.get_pool_base_slot,
*token0,
*token1,
fee,
),
));
}
}
}
ClKeying::TickSpacing { spacings } => {
for (token0, token1) in pairs {
for &spacing in spacings {
slots.push((
self.spec.factory,
derive::v3_get_pool_slot_by_spacing(
self.spec.get_pool_base_slot,
*token0,
*token1,
spacing,
),
));
if let FeeSource::FactoryMapping { base_slot } = self.spec.fee_source {
slots.push((
self.spec.factory,
derive::v3_tick_spacing_fee_slot(base_slot, spacing),
));
}
}
}
}
}
slots
}
fn assemble_pairs(
&self,
pairs: &[(Address, Address)],
values: &HashMap<(Address, U256), U256>,
) -> Result<Vec<DiscoveredPool>, DiscoveryError> {
let mut found = Vec::new();
for (token0, token1) in pairs {
match &self.spec.keying {
ClKeying::Fee {
tiers,
fee_amount_tick_spacing_base_slot,
} => {
for &fee in tiers {
let key = ClLookupKey::Fee(fee);
let pool_slot = derive::v3_get_pool_slot(
self.spec.get_pool_base_slot,
*token0,
*token1,
fee,
);
let Some(&word) = values.get(&(self.spec.factory, pool_slot)) else {
continue;
};
let pool = address_from_word(word)?;
if pool == Address::ZERO {
continue;
}
self.ensure_derivation_matches(pool, *token0, *token1, key)?;
let tick_slot = derive::v3_fee_amount_tick_spacing_slot(
*fee_amount_tick_spacing_base_slot,
fee,
);
let tick_spacing = i24_from_word(
values
.get(&(self.spec.factory, tick_slot))
.copied()
.unwrap_or_default(),
);
if tick_spacing <= 0 {
return Err(DiscoveryError::Malformed(
"V3 feeAmountTickSpacing returned a non-positive spacing",
));
}
found.push(self.registration(
pool,
*token0,
*token1,
self.fee_for_key(key, values)?,
tick_spacing,
DiscoverySource::Query,
));
}
}
ClKeying::TickSpacing { spacings } => {
for &spacing in spacings {
let key = ClLookupKey::TickSpacing(spacing);
let pool_slot = derive::v3_get_pool_slot_by_spacing(
self.spec.get_pool_base_slot,
*token0,
*token1,
spacing,
);
let Some(&word) = values.get(&(self.spec.factory, pool_slot)) else {
continue;
};
let pool = address_from_word(word)?;
if pool == Address::ZERO {
continue;
}
self.ensure_derivation_matches(pool, *token0, *token1, key)?;
found.push(self.registration(
pool,
*token0,
*token1,
self.fee_for_key(key, values)?,
spacing,
DiscoverySource::Query,
));
}
}
}
}
Ok(found)
}
fn creation_sources(&self) -> Vec<EventSource> {
self.spec
.creation_topic0
.map(|topic| EventSource::adapter_defined(self.spec.factory, vec![topic]))
.into_iter()
.collect()
}
fn decode_creation(
&self,
log: &Log,
context: CreationLogContext,
) -> Result<Option<DiscoveredPool>, DiscoveryError> {
let Some(topic0) = self.spec.creation_topic0 else {
return Ok(None);
};
if log.address != self.spec.factory || log.topics().first() != Some(&topic0) {
return Ok(None);
}
if topic0 == PoolCreated::SIGNATURE_HASH {
let event = PoolCreated::decode_log(log)
.map_err(|_| DiscoveryError::Malformed("PoolCreated failed to decode"))?
.data;
let fee: u32 = event.fee.to();
let tick_spacing: i32 = event.tickSpacing.as_i32();
self.ensure_derivation_matches(
event.pool,
event.token0,
event.token1,
ClLookupKey::Fee(fee),
)?;
return Ok(Some(self.registration(
event.pool,
event.token0,
event.token1,
fee,
tick_spacing,
DiscoverySource::CreationEvent {
block_number: context.block_number,
log_index: context.log_index,
},
)));
}
if topic0 == PoolCreatedTickSpacing::SIGNATURE_HASH {
let event = PoolCreatedTickSpacing::decode_log(log)
.map_err(|_| DiscoveryError::Malformed("PoolCreatedTickSpacing failed to decode"))?
.data;
let fee: u32 = event.fee.to();
let tick_spacing: i32 = event.tickSpacing.as_i32();
self.ensure_derivation_matches(
event.pool,
event.token0,
event.token1,
ClLookupKey::TickSpacing(tick_spacing),
)?;
return Ok(Some(self.registration(
event.pool,
event.token0,
event.token1,
fee,
tick_spacing,
DiscoverySource::CreationEvent {
block_number: context.block_number,
log_index: context.log_index,
},
)));
}
Ok(None)
}
}
#[cfg(feature = "solidly-v2")]
#[derive(Debug)]
pub struct SolidlyFactory {
config: SolidlyFactoryConfig,
derivation_verified: std::sync::OnceLock<()>,
}
#[cfg(feature = "solidly-v2")]
const SOLIDLY_VARIANTS: [bool; 2] = [false, true];
#[cfg(feature = "solidly-v2")]
impl SolidlyFactory {
pub fn new(config: SolidlyFactoryConfig) -> Self {
Self {
config,
derivation_verified: std::sync::OnceLock::new(),
}
}
fn registration(
&self,
pool: Address,
token0: Address,
token1: Address,
stable: bool,
source: DiscoverySource,
) -> DiscoveredPool {
let metadata = SolidlyV2Metadata::default()
.with_token0(token0)
.with_token1(token1)
.with_stable(stable)
.with_storage_layout(self.config.storage_layout);
let registration = PoolRegistration::new(PoolKey::SolidlyV2(pool))
.with_state_address(pool)
.with_metadata(ProtocolMetadata::SolidlyV2(metadata));
let adapter = crate::adapters::SolidlyV2Adapter::default();
let sources = super::AmmAdapter::event_sources(&adapter, ®istration);
let registration = registration.with_event_sources(sources);
DiscoveredPool {
key: registration.key.clone(),
registration,
source,
}
}
fn ensure_derivation_matches(
&self,
mapping: Address,
token0: Address,
token1: Address,
stable: bool,
) -> Result<(), DiscoveryError> {
if !self.config.verify_derivations || self.derivation_verified.get().is_some() {
return Ok(());
}
let Some(create2) = self.config.create2 else {
return Ok(());
};
let deployer = create2.deployer.unwrap_or(self.config.factory);
let derived =
derive::solidly_pool_address(deployer, create2.init_code_hash, token0, token1, stable);
if derived != mapping {
return Err(DiscoveryError::DerivationMismatch { mapping, derived });
}
let _ = self.derivation_verified.set(());
Ok(())
}
}
#[cfg(feature = "solidly-v2")]
impl PoolFactory for SolidlyFactory {
fn protocol(&self) -> ProtocolId {
ProtocolId::SolidlyV2
}
fn factory_address(&self) -> Address {
self.config.factory
}
fn candidate_reads(&self, pairs: &[(Address, Address)]) -> Vec<(Address, U256)> {
let mut slots = Vec::with_capacity(pairs.len() * SOLIDLY_VARIANTS.len());
for (token0, token1) in pairs {
for stable in SOLIDLY_VARIANTS {
slots.push((
self.config.factory,
derive::solidly_get_pool_slot(
self.config.get_pool_base_slot,
*token0,
*token1,
stable,
),
));
}
}
slots
}
fn assemble_pairs(
&self,
pairs: &[(Address, Address)],
values: &HashMap<(Address, U256), U256>,
) -> Result<Vec<DiscoveredPool>, DiscoveryError> {
let mut found = Vec::new();
for (token0, token1) in pairs {
for stable in SOLIDLY_VARIANTS {
let slot = derive::solidly_get_pool_slot(
self.config.get_pool_base_slot,
*token0,
*token1,
stable,
);
let Some(&word) = values.get(&(self.config.factory, slot)) else {
continue;
};
let pool = address_from_word(word)?;
if pool == Address::ZERO {
continue;
}
self.ensure_derivation_matches(pool, *token0, *token1, stable)?;
found.push(self.registration(
pool,
*token0,
*token1,
stable,
DiscoverySource::Query,
));
}
}
Ok(found)
}
fn creation_sources(&self) -> Vec<EventSource> {
self.config
.creation_topic0
.map(|topic| EventSource::adapter_defined(self.config.factory, vec![topic]))
.into_iter()
.collect()
}
fn decode_creation(
&self,
log: &Log,
context: CreationLogContext,
) -> Result<Option<DiscoveredPool>, DiscoveryError> {
let Some(topic0) = self.config.creation_topic0 else {
return Ok(None);
};
if log.address != self.config.factory || log.topics().first() != Some(&topic0) {
return Ok(None);
}
if topic0 != SolidlyPoolCreated::SIGNATURE_HASH {
return Ok(None);
}
let event = SolidlyPoolCreated::decode_log(log)
.map_err(|_| DiscoveryError::Malformed("SolidlyPoolCreated failed to decode"))?
.data;
self.ensure_derivation_matches(event.pool, event.token0, event.token1, event.stable)?;
Ok(Some(self.registration(
event.pool,
event.token0,
event.token1,
event.stable,
DiscoverySource::CreationEvent {
block_number: context.block_number,
log_index: context.log_index,
},
)))
}
}
#[cfg(feature = "uniswap-v3")]
fn u32_from_word(word: U256) -> u32 {
let bytes = word.to_be_bytes::<32>();
u32::from_be_bytes([bytes[28], bytes[29], bytes[30], bytes[31]])
}
#[cfg(any(feature = "uniswap-v2", feature = "uniswap-v3", feature = "solidly-v2"))]
fn address_from_word(word: U256) -> Result<Address, DiscoveryError> {
let bytes = word.to_be_bytes::<32>();
if bytes[..12].iter().any(|b| *b != 0) {
return Err(DiscoveryError::Malformed(
"factory address word has non-zero high bytes",
));
}
Ok(Address::from_slice(&bytes[12..]))
}
#[cfg(feature = "uniswap-v3")]
fn i24_from_word(word: U256) -> i32 {
let bytes = word.to_be_bytes::<32>();
let raw = u32::from_be_bytes([0, bytes[29], bytes[30], bytes[31]]);
if (raw & 0x0080_0000) != 0 {
(raw | 0xff00_0000) as i32
} else {
raw as i32
}
}
#[cfg(all(test, feature = "solidly-v2"))]
mod solidly_tests {
use super::*;
use alloy_primitives::keccak256;
#[test]
fn solidly_pool_created_topic0_matches_onchain_signature() {
let expected = keccak256(b"PoolCreated(address,address,bool,address,uint256)");
assert_eq!(SolidlyPoolCreated::SIGNATURE_HASH, expected);
assert_eq!(
SolidlyPoolCreated::SIGNATURE,
"PoolCreated(address,address,bool,address,uint256)"
);
}
#[test]
fn solidly_get_pool_slot_distinguishes_stable_flag() {
let base = U256::from(3);
let t0 = Address::repeat_byte(0x0a);
let t1 = Address::repeat_byte(0x0b);
let volatile = derive::solidly_get_pool_slot(base, t0, t1, false);
let stable = derive::solidly_get_pool_slot(base, t0, t1, true);
assert_ne!(volatile, stable);
assert_ne!(volatile, U256::ZERO);
assert_ne!(stable, U256::ZERO);
}
}