use super::bytecode::{AdapterCodeSeed, BytecodeTemplateError, uniswap_v2_pair_code_seed};
use super::cold_start::{
AdapterColdStartPlanner, ColdStartPlan, ColdStartResults, ColdStartRunReport, ColdStartStep,
SlotFetch,
};
use super::factory::{FactoryConfig, PoolFactory, UniswapV2Factory};
use super::sim::{SimConfig, SimError, SwapQuote, getAmountsOutCall, quote_via_call_from};
use super::storage::{V2_RESERVES_SLOT, V2_TOKEN0_SLOT, V2_TOKEN1_SLOT, decode_address_slot};
use super::{
AdapterCache, AdapterEvent, AdapterEventError, AdapterEventKind, AdapterEventResult,
AmmAdapter, ColdStartOutcome, ColdStartPolicy, ColdStartReport, DeferredWork, EventSource,
PoolRegistration, PoolStateDependencies, PoolStatus, ProtocolId, ProtocolMetadata,
RepairAction, SlotChange, StateDiff, StateSlot, StateUpdate, StateView, UniswapV2Metadata,
UnsupportedReason, UpdateQuality,
};
use alloy_primitives::{Address, Bytes, Log, U256};
use alloy_sol_types::{SolCall, SolEvent};
mod abi {
alloy_sol_types::sol! {
event Sync(uint112 reserve0, uint112 reserve1);
}
}
use abi::Sync;
#[derive(Clone, Debug, Default)]
pub struct UniswapV2Adapter {
_private: (),
}
impl AmmAdapter for UniswapV2Adapter {
fn protocol(&self) -> ProtocolId {
ProtocolId::UniswapV2
}
fn event_sources(&self, pool: &PoolRegistration) -> Vec<EventSource> {
pool.key
.address()
.map(|address| EventSource::direct(address, vec![Sync::SIGNATURE_HASH]))
.into_iter()
.collect()
}
fn state_dependencies(&self, pool: &PoolRegistration) -> PoolStateDependencies {
let Some(address) = pool.key.address() else {
return PoolStateDependencies::default();
};
PoolStateDependencies::default().with_slots([
StateSlot::new(address, V2_TOKEN0_SLOT),
StateSlot::new(address, V2_TOKEN1_SLOT),
StateSlot::new(address, V2_RESERVES_SLOT),
])
}
fn pool_factories(&self, config: &FactoryConfig) -> Vec<Box<dyn PoolFactory>> {
config
.uniswap_v2
.iter()
.map(|factory| {
Box::new(UniswapV2Factory::new(
factory.clone(),
config.verify_derivations,
)) as Box<dyn PoolFactory>
})
.collect()
}
fn cold_start_planner(
&self,
pool: &PoolRegistration,
policy: ColdStartPolicy,
) -> Result<Box<dyn AdapterColdStartPlanner>, UnsupportedReason> {
let Some(address) = pool.key.address() else {
return Err(UnsupportedReason::Custom(
"Uniswap V2 pool key is not address-keyed".into(),
));
};
Ok(Box::new(UniswapV2ColdStartPlanner::new(address, policy)))
}
fn code_seeds(
&self,
pool: &PoolRegistration,
) -> Result<Vec<AdapterCodeSeed>, BytecodeTemplateError> {
let Some(address) = pool.key.address() else {
return Ok(Vec::new());
};
let ProtocolMetadata::UniswapV2(_) = &pool.metadata else {
return Ok(Vec::new());
};
Ok(vec![uniswap_v2_pair_code_seed(address)])
}
fn decode_event(
&self,
pool: &PoolRegistration,
log: &Log,
_view: &dyn StateView,
) -> AdapterEventResult {
if log.topics().first() != Some(&Sync::SIGNATURE_HASH) {
return AdapterEventResult::ignored();
}
if Sync::decode_log_data_validate(&log.data).is_err() {
return AdapterEventResult::error(AdapterEventError::MalformedLog(
"malformed Uniswap V2 Sync log",
));
}
let Some(address) = pool.key.address() else {
return AdapterEventResult::error(AdapterEventError::MalformedLog(
"Uniswap V2 pool key is not address-keyed",
));
};
let Some(reserve0) = data_word(log, 0) else {
return AdapterEventResult::error(AdapterEventError::MalformedLog(
"missing Uniswap V2 reserve0",
));
};
let Some(reserve1) = data_word(log, 1) else {
return AdapterEventResult::error(AdapterEventError::MalformedLog(
"missing Uniswap V2 reserve1",
));
};
let value = reserve0 | (reserve1 << 112);
let mask = low_mask(224);
AdapterEventResult::event(AdapterEvent {
pool: pool.key.clone(),
emitter: log.address,
topic0: Sync::SIGNATURE_HASH,
kind: AdapterEventKind::Sync,
updates: vec![StateUpdate::slot_masked(
address,
V2_RESERVES_SLOT,
mask,
value,
)],
quality: UpdateQuality::ExactIfApplied,
repair: RepairAction::None,
})
}
fn after_apply(
&self,
pool: &PoolRegistration,
event: &AdapterEvent,
diff: &StateDiff,
) -> RepairAction {
if event.kind == AdapterEventKind::Sync
&& diff.has_skipped()
&& let Some(address) = pool.key.address()
{
RepairAction::VerifySlots(vec![(address, V2_RESERVES_SLOT)])
} else {
RepairAction::None
}
}
fn simulate_swap(
&self,
_pool: &PoolRegistration,
cache: &mut dyn AdapterCache,
token_in: Address,
token_out: Address,
amount_in: U256,
config: &SimConfig,
) -> Result<SwapQuote, SimError> {
let calldata = Bytes::from(
getAmountsOutCall {
amountIn: amount_in,
path: vec![token_in, token_out],
}
.abi_encode(),
);
let output = quote_via_call_from(cache, config.from, config.v2_router, calldata)?;
let amounts = getAmountsOutCall::abi_decode_returns_validate(&output)
.map_err(|_| SimError::MalformedOutput("getAmountsOut return"))?;
amounts
.last()
.copied()
.map(SwapQuote::new)
.ok_or(SimError::MalformedOutput("empty getAmountsOut amounts"))
}
}
struct UniswapV2ColdStartPlanner {
address: Address,
policy: ColdStartPolicy,
verified_slots: Vec<(Address, U256)>,
changed_slots: Vec<SlotChange>,
decoded_token0: Option<Address>,
decoded_token1: Option<Address>,
verdict: Option<V2Verdict>,
}
enum V2Verdict {
Ready,
DegenerateZero,
FetchFailed,
}
impl UniswapV2ColdStartPlanner {
fn new(address: Address, policy: ColdStartPolicy) -> Self {
Self {
address,
policy,
verified_slots: Vec::new(),
changed_slots: Vec::new(),
decoded_token0: None,
decoded_token1: None,
verdict: None,
}
}
}
impl AdapterColdStartPlanner for UniswapV2ColdStartPlanner {
fn initial_plan(&mut self, _state: &dyn StateView) -> ColdStartPlan {
let verify: Vec<(Address, U256)> = match self.policy {
ColdStartPolicy::Strict | ColdStartPolicy::Eager => vec![
(self.address, V2_TOKEN0_SLOT),
(self.address, V2_TOKEN1_SLOT),
(self.address, V2_RESERVES_SLOT),
],
ColdStartPolicy::Lazy | ColdStartPolicy::HotSlotsOnly => {
vec![(self.address, V2_RESERVES_SLOT)]
}
};
self.verified_slots = verify.clone();
ColdStartPlan {
verify,
..Default::default()
}
}
fn on_results(&mut self, results: &ColdStartResults, state: &dyn StateView) -> ColdStartStep {
self.changed_slots.extend(results.verified.iter().cloned());
let reserves = results
.fetched
.iter()
.find(|o| o.address == self.address && o.slot == V2_RESERVES_SLOT);
self.verdict = Some(match reserves.map(|o| &o.fetch) {
Some(SlotFetch::Value(_)) => V2Verdict::Ready,
Some(SlotFetch::Zero) => V2Verdict::DegenerateZero,
Some(SlotFetch::FetchFailed { .. }) | Some(SlotFetch::NotAttempted) | None => {
V2Verdict::FetchFailed
}
});
self.decoded_token0 = state
.storage(self.address, V2_TOKEN0_SLOT)
.map(decode_address_slot);
self.decoded_token1 = state
.storage(self.address, V2_TOKEN1_SLOT)
.map(decode_address_slot);
ColdStartStep::Done
}
fn finish(
&mut self,
pool: &mut PoolRegistration,
_report: &ColdStartRunReport,
) -> ColdStartOutcome {
let mut cold_report = ColdStartReport::new(pool.key.clone(), self.policy);
cold_report.verified_slots = self.verified_slots.clone();
cold_report.changed_slots = self.changed_slots.clone();
match self.verdict {
Some(V2Verdict::DegenerateZero) => {
cold_report.status = PoolStatus::Degraded;
ColdStartOutcome::NeedsRepair(
cold_report,
RepairAction::PurgeSlots {
address: self.address,
slots: vec![V2_RESERVES_SLOT],
},
)
}
Some(V2Verdict::FetchFailed) | None => {
cold_report.status = PoolStatus::Degraded;
ColdStartOutcome::NeedsRepair(
cold_report,
RepairAction::VerifySlots(vec![(self.address, V2_RESERVES_SLOT)]),
)
}
Some(V2Verdict::Ready) => {
let metadata = match &pool.metadata {
ProtocolMetadata::UniswapV2(existing) => UniswapV2Metadata {
token0: self.decoded_token0,
token1: self.decoded_token1,
fee_bps: existing.fee_bps,
},
_ => UniswapV2Metadata {
token0: self.decoded_token0,
token1: self.decoded_token1,
fee_bps: None,
},
};
pool.metadata = ProtocolMetadata::UniswapV2(metadata);
pool.status = PoolStatus::Ready;
cold_report.status = PoolStatus::Ready;
if self.policy == ColdStartPolicy::Lazy {
let deferred = vec![DeferredWork::VerifySlots(vec![
(self.address, V2_TOKEN0_SLOT),
(self.address, V2_TOKEN1_SLOT),
])];
cold_report.deferred = deferred.clone();
ColdStartOutcome::ReadyWithDeferred(cold_report, deferred)
} else {
ColdStartOutcome::Ready(cold_report)
}
}
}
}
}
fn data_word(log: &Log, index: usize) -> Option<U256> {
let start = index.checked_mul(32)?;
log.data
.data
.get(start..start + 32)
.map(U256::from_be_slice)
}
fn low_mask(bits: usize) -> U256 {
(U256::from(1) << bits) - U256::from(1)
}