use std::any::Any;
use std::fmt;
use std::sync::Arc;
use alloy_primitives::{Address, B256, Bytes, U256};
use super::cache::{SlotChange, StateDiff, StateUpdate};
use super::sim::SimConfig;
use super::storage::{SolidlyStorageLayout, V3StorageLayout};
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum ProtocolId {
UniswapV2,
UniswapV3,
PancakeV3,
Slipstream,
SolidlyV2,
BalancerV2,
#[cfg(feature = "experimental-protocols")]
BalancerV3,
Curve,
#[cfg(feature = "experimental-protocols")]
Erc4626,
#[cfg(feature = "experimental-protocols")]
UniswapV4,
Custom(&'static str),
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum PoolKey {
UniswapV2(Address),
UniswapV3(Address),
PancakeV3(Address),
Slipstream(Address),
SolidlyV2(Address),
BalancerV2(B256),
#[cfg(feature = "experimental-protocols")]
BalancerV3(Address),
Curve(Address),
#[cfg(feature = "experimental-protocols")]
Erc4626(Address),
#[cfg(feature = "experimental-protocols")]
UniswapV4(B256),
Custom(CustomPoolKey),
}
impl PoolKey {
pub fn protocol(&self) -> ProtocolId {
match self {
Self::UniswapV2(_) => ProtocolId::UniswapV2,
Self::UniswapV3(_) => ProtocolId::UniswapV3,
Self::PancakeV3(_) => ProtocolId::PancakeV3,
Self::Slipstream(_) => ProtocolId::Slipstream,
Self::SolidlyV2(_) => ProtocolId::SolidlyV2,
Self::BalancerV2(_) => ProtocolId::BalancerV2,
#[cfg(feature = "experimental-protocols")]
Self::BalancerV3(_) => ProtocolId::BalancerV3,
Self::Curve(_) => ProtocolId::Curve,
#[cfg(feature = "experimental-protocols")]
Self::Erc4626(_) => ProtocolId::Erc4626,
#[cfg(feature = "experimental-protocols")]
Self::UniswapV4(_) => ProtocolId::UniswapV4,
Self::Custom(key) => key.protocol(),
}
}
pub fn address(&self) -> Option<Address> {
match self {
Self::UniswapV2(address)
| Self::UniswapV3(address)
| Self::PancakeV3(address)
| Self::Slipstream(address)
| Self::SolidlyV2(address)
| Self::Curve(address) => Some(*address),
#[cfg(feature = "experimental-protocols")]
Self::BalancerV3(address) | Self::Erc4626(address) => Some(*address),
Self::Custom(key) => key.address(),
Self::BalancerV2(_) => None,
#[cfg(feature = "experimental-protocols")]
Self::UniswapV4(_) => None,
}
}
pub fn bytes32(&self) -> Option<B256> {
match self {
Self::BalancerV2(id) => Some(*id),
#[cfg(feature = "experimental-protocols")]
Self::UniswapV4(id) => Some(*id),
Self::Custom(key) => key.bytes32(),
Self::UniswapV2(_)
| Self::UniswapV3(_)
| Self::PancakeV3(_)
| Self::Slipstream(_)
| Self::SolidlyV2(_)
| Self::Curve(_) => None,
#[cfg(feature = "experimental-protocols")]
Self::BalancerV3(_) | Self::Erc4626(_) => None,
}
}
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum CustomPoolKey {
Address {
protocol: &'static str,
address: Address,
},
Bytes32 {
protocol: &'static str,
id: B256,
},
Composite {
protocol: &'static str,
address: Address,
id: B256,
},
}
impl CustomPoolKey {
pub fn protocol(&self) -> ProtocolId {
match self {
Self::Address { protocol, .. }
| Self::Bytes32 { protocol, .. }
| Self::Composite { protocol, .. } => ProtocolId::Custom(protocol),
}
}
pub fn address(&self) -> Option<Address> {
match self {
Self::Address { address, .. } | Self::Composite { address, .. } => Some(*address),
Self::Bytes32 { .. } => None,
}
}
pub fn bytes32(&self) -> Option<B256> {
match self {
Self::Bytes32 { id, .. } | Self::Composite { id, .. } => Some(*id),
Self::Address { .. } => None,
}
}
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EventSource {
pub emitter: Address,
pub topics: Vec<B256>,
pub route: EventRoute,
}
impl EventSource {
pub fn direct(emitter: Address, topics: Vec<B256>) -> Self {
Self {
emitter,
topics,
route: EventRoute::Direct,
}
}
pub fn indexed_address(emitter: Address, topics: Vec<B256>, topic_index: usize) -> Self {
Self {
emitter,
topics,
route: EventRoute::IndexedAddress { topic_index },
}
}
pub fn indexed_bytes32(emitter: Address, topics: Vec<B256>, topic_index: usize) -> Self {
Self {
emitter,
topics,
route: EventRoute::IndexedBytes32 { topic_index },
}
}
pub fn adapter_defined(emitter: Address, topics: Vec<B256>) -> Self {
Self {
emitter,
topics,
route: EventRoute::AdapterDefined,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum EventRoute {
Direct,
IndexedAddress {
topic_index: usize,
},
IndexedBytes32 {
topic_index: usize,
},
AdapterDefined,
}
#[non_exhaustive]
#[derive(Clone, Debug)]
pub struct PoolRegistration {
pub key: PoolKey,
pub state_addresses: Vec<Address>,
pub event_sources: Vec<EventSource>,
pub metadata: ProtocolMetadata,
pub status: PoolStatus,
}
impl PoolRegistration {
pub fn new(key: PoolKey) -> Self {
Self {
key,
state_addresses: Vec::new(),
event_sources: Vec::new(),
metadata: ProtocolMetadata::Unknown,
status: PoolStatus::Pending,
}
}
pub fn protocol(&self) -> ProtocolId {
self.key.protocol()
}
pub fn tokens(&self) -> Option<Vec<Address>> {
self.metadata.tokens()
}
pub fn quote_code_targets(&self, config: &SimConfig) -> Vec<Address> {
self.metadata.quote_code_targets(config)
}
pub fn with_state_address(mut self, address: Address) -> Self {
self.state_addresses.push(address);
self
}
pub fn with_state_addresses(mut self, addresses: impl IntoIterator<Item = Address>) -> Self {
self.state_addresses.extend(addresses);
self
}
pub fn with_event_source(mut self, source: EventSource) -> Self {
self.event_sources.push(source);
self
}
pub fn with_event_sources(mut self, sources: impl IntoIterator<Item = EventSource>) -> Self {
self.event_sources.extend(sources);
self
}
pub fn with_metadata(mut self, metadata: ProtocolMetadata) -> Self {
self.metadata = metadata;
self
}
pub fn with_status(mut self, status: PoolStatus) -> Self {
self.status = status;
self
}
}
#[non_exhaustive]
#[derive(Clone, Default)]
pub enum ProtocolMetadata {
#[default]
Unknown,
UniswapV2(UniswapV2Metadata),
UniswapV3(V3Metadata),
PancakeV3(V3Metadata),
Slipstream(V3Metadata),
BalancerV2(BalancerV2Metadata),
SolidlyV2(SolidlyV2Metadata),
Curve(CurveMetadata),
Custom(Arc<dyn Any + Send + Sync>),
}
impl fmt::Debug for ProtocolMetadata {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Unknown => f.write_str("Unknown"),
Self::UniswapV2(metadata) => f.debug_tuple("UniswapV2").field(metadata).finish(),
Self::UniswapV3(metadata) => f.debug_tuple("UniswapV3").field(metadata).finish(),
Self::PancakeV3(metadata) => f.debug_tuple("PancakeV3").field(metadata).finish(),
Self::Slipstream(metadata) => f.debug_tuple("Slipstream").field(metadata).finish(),
Self::BalancerV2(metadata) => f.debug_tuple("BalancerV2").field(metadata).finish(),
Self::SolidlyV2(metadata) => f.debug_tuple("SolidlyV2").field(metadata).finish(),
Self::Curve(metadata) => f.debug_tuple("Curve").field(metadata).finish(),
Self::Custom(_) => f.write_str("Custom(..)"),
}
}
}
impl ProtocolMetadata {
pub fn tokens(&self) -> Option<Vec<Address>> {
fn pair(token0: Option<Address>, token1: Option<Address>) -> Option<Vec<Address>> {
Some(vec![token0?, token1?])
}
fn many(tokens: &[Address]) -> Option<Vec<Address>> {
(!tokens.is_empty()).then(|| tokens.to_vec())
}
match self {
ProtocolMetadata::UniswapV2(metadata) => pair(metadata.token0, metadata.token1),
ProtocolMetadata::UniswapV3(metadata)
| ProtocolMetadata::PancakeV3(metadata)
| ProtocolMetadata::Slipstream(metadata) => pair(metadata.token0, metadata.token1),
ProtocolMetadata::SolidlyV2(metadata) => pair(metadata.token0, metadata.token1),
ProtocolMetadata::BalancerV2(metadata) => many(&metadata.tokens),
ProtocolMetadata::Curve(metadata) => many(&metadata.coins),
ProtocolMetadata::Unknown | ProtocolMetadata::Custom(_) => None,
}
}
pub fn quote_code_targets(&self, config: &SimConfig) -> Vec<Address> {
match self {
ProtocolMetadata::UniswapV2(_) => vec![config.v2_router],
ProtocolMetadata::UniswapV3(metadata)
| ProtocolMetadata::PancakeV3(metadata)
| ProtocolMetadata::Slipstream(metadata) => vec![metadata.quote_target(config)],
ProtocolMetadata::BalancerV2(metadata) => metadata.vault.into_iter().collect(),
ProtocolMetadata::SolidlyV2(_)
| ProtocolMetadata::Curve(_)
| ProtocolMetadata::Unknown
| ProtocolMetadata::Custom(_) => Vec::new(),
}
}
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct UniswapV2Metadata {
pub token0: Option<Address>,
pub token1: Option<Address>,
pub fee_bps: Option<u32>,
}
impl UniswapV2Metadata {
pub fn with_token0(mut self, token0: Address) -> Self {
self.token0 = Some(token0);
self
}
pub fn with_token1(mut self, token1: Address) -> Self {
self.token1 = Some(token1);
self
}
pub fn with_fee_bps(mut self, fee_bps: u32) -> Self {
self.fee_bps = Some(fee_bps);
self
}
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct V3Metadata {
pub token0: Option<Address>,
pub token1: Option<Address>,
pub fee: Option<u32>,
pub tick_spacing: Option<i32>,
pub factory: Option<Address>,
pub quoter: Option<Address>,
pub storage_layout: Option<V3StorageLayout>,
pub warm_word_radius: Option<i16>,
pub warmed_slots: Vec<U256>,
}
impl V3Metadata {
pub fn with_token0(mut self, token0: Address) -> Self {
self.token0 = Some(token0);
self
}
pub fn with_token1(mut self, token1: Address) -> Self {
self.token1 = Some(token1);
self
}
pub fn with_fee(mut self, fee: u32) -> Self {
self.fee = Some(fee);
self
}
pub fn with_tick_spacing(mut self, tick_spacing: i32) -> Self {
self.tick_spacing = Some(tick_spacing);
self
}
pub fn with_factory(mut self, factory: Address) -> Self {
self.factory = Some(factory);
self
}
pub fn with_quoter(mut self, quoter: Address) -> Self {
self.quoter = Some(quoter);
self
}
pub fn with_storage_layout(mut self, storage_layout: V3StorageLayout) -> Self {
self.storage_layout = Some(storage_layout);
self
}
pub fn with_warm_word_radius(mut self, warm_word_radius: i16) -> Self {
self.warm_word_radius = Some(warm_word_radius);
self
}
pub fn with_warmed_slots(mut self, warmed_slots: impl IntoIterator<Item = U256>) -> Self {
self.warmed_slots = warmed_slots.into_iter().collect();
self.warmed_slots.sort_unstable();
self.warmed_slots.dedup();
self
}
pub fn quote_target(&self, config: &SimConfig) -> Address {
self.quoter.unwrap_or(config.v3_quoter)
}
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct SolidlyV2Metadata {
pub token0: Option<Address>,
pub token1: Option<Address>,
pub stable: Option<bool>,
pub storage_layout: Option<SolidlyStorageLayout>,
}
impl SolidlyV2Metadata {
pub fn with_token0(mut self, token0: Address) -> Self {
self.token0 = Some(token0);
self
}
pub fn with_token1(mut self, token1: Address) -> Self {
self.token1 = Some(token1);
self
}
pub fn with_stable(mut self, stable: bool) -> Self {
self.stable = Some(stable);
self
}
pub fn with_storage_layout(mut self, storage_layout: SolidlyStorageLayout) -> Self {
self.storage_layout = Some(storage_layout);
self
}
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct BalancerV2Metadata {
pub vault: Option<Address>,
pub pool_address: Option<Address>,
pub tokens: Vec<Address>,
pub balance_slots: Vec<U256>,
pub token_cash: Vec<BalancerTokenBalance>,
}
impl BalancerV2Metadata {
pub fn with_vault(mut self, vault: Address) -> Self {
self.vault = Some(vault);
self
}
pub fn with_pool_address(mut self, pool_address: Address) -> Self {
self.pool_address = Some(pool_address);
self
}
pub fn with_tokens(mut self, tokens: impl IntoIterator<Item = Address>) -> Self {
self.tokens = tokens.into_iter().collect();
self
}
pub fn with_balance_slots(mut self, balance_slots: impl IntoIterator<Item = U256>) -> Self {
self.balance_slots = balance_slots.into_iter().collect();
self
}
pub fn with_token_cash(
mut self,
token_cash: impl IntoIterator<Item = BalancerTokenBalance>,
) -> Self {
self.token_cash = token_cash.into_iter().collect();
self
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct BalancerTokenBalance {
pub token: Address,
pub slot: U256,
pub high_field: bool,
}
impl BalancerTokenBalance {
pub fn new(token: Address, slot: U256, high_field: bool) -> Self {
Self {
token,
slot,
high_field,
}
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum CurveVariant {
#[default]
StableSwap,
CryptoSwap,
CryptoSwapNG,
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct CurveMetadata {
pub coins: Vec<Address>,
pub discovered_slots: Vec<U256>,
pub variant: CurveVariant,
pub code_seed: Option<Bytes>,
}
impl CurveMetadata {
pub fn with_coins(mut self, coins: impl IntoIterator<Item = Address>) -> Self {
self.coins = coins.into_iter().collect();
self
}
pub fn with_discovered_slots(
mut self,
discovered_slots: impl IntoIterator<Item = U256>,
) -> Self {
self.discovered_slots = discovered_slots.into_iter().collect();
self
}
pub fn with_variant(mut self, variant: CurveVariant) -> Self {
self.variant = variant;
self
}
pub fn with_code_seed(mut self, code_seed: impl Into<Bytes>) -> Self {
self.code_seed = Some(code_seed.into());
self
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum PoolStatus {
#[default]
Pending,
Cold,
Ready,
Degraded,
Disabled,
Unsupported,
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AdapterEvent {
pub pool: PoolKey,
pub emitter: Address,
pub topic0: B256,
pub kind: AdapterEventKind,
pub updates: Vec<StateUpdate>,
pub quality: UpdateQuality,
pub repair: RepairAction,
}
impl AdapterEvent {
pub fn new(
pool: PoolKey,
emitter: Address,
topic0: B256,
kind: AdapterEventKind,
quality: UpdateQuality,
) -> Self {
Self {
pool,
emitter,
topic0,
kind,
updates: Vec::new(),
quality,
repair: RepairAction::None,
}
}
pub fn with_updates(mut self, updates: impl IntoIterator<Item = StateUpdate>) -> Self {
self.updates = updates.into_iter().collect();
self
}
pub fn with_repair(mut self, repair: RepairAction) -> Self {
self.repair = repair;
self
}
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AdapterEventReport {
pub pool: PoolKey,
pub event: AdapterEvent,
pub applied: StateDiff,
pub repair: RepairAction,
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum AdapterEventKind {
Swap,
LiquidityAdded,
LiquidityRemoved,
Sync,
Deposit,
Withdraw,
Unknown,
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct AdapterEventResult {
pub event: Option<AdapterEvent>,
pub error: Option<AdapterEventError>,
}
impl AdapterEventResult {
pub fn event(event: AdapterEvent) -> Self {
Self {
event: Some(event),
error: None,
}
}
pub fn ignored() -> Self {
Self::default()
}
pub fn error(error: AdapterEventError) -> Self {
Self {
event: None,
error: Some(error),
}
}
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AdapterEventError {
MalformedLog(&'static str),
MissingState {
address: Address,
slot: U256,
},
Unsupported(UnsupportedReason),
Custom(String),
}
impl fmt::Display for AdapterEventError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MalformedLog(what) => write!(f, "malformed log: {what}"),
Self::MissingState { address, slot } => {
write!(f, "missing state at {address}:{slot}")
}
Self::Unsupported(reason) => write!(f, "unsupported: {reason:?}"),
Self::Custom(message) => write!(f, "{message}"),
}
}
}
impl std::error::Error for AdapterEventError {}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum UpdateQuality {
Exact,
ExactIfApplied,
RequiresRepair,
ConservativeInvalidation,
Ignored,
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub enum RepairAction {
#[default]
None,
VerifySlots(Vec<(Address, U256)>),
PurgeStorage(Address),
PurgeSlots {
address: Address,
slots: Vec<U256>,
},
ColdStart {
pool: PoolKey,
policy: ColdStartPolicy,
},
V3TickRange {
pool: PoolKey,
tick_lower: i32,
tick_upper: i32,
},
V3Incremental {
pool: PoolKey,
},
V3Full {
pool: PoolKey,
},
}
impl RepairAction {
pub(crate) fn combine(self, other: RepairAction) -> RepairAction {
match (self, other) {
(RepairAction::None, repair) | (repair, RepairAction::None) => repair,
(RepairAction::VerifySlots(mut left), RepairAction::VerifySlots(right)) => {
for slot in right {
if !left.contains(&slot) {
left.push(slot);
}
}
RepairAction::VerifySlots(left)
}
(
RepairAction::PurgeSlots {
address: left_address,
slots: mut left_slots,
},
RepairAction::PurgeSlots {
address: right_address,
slots: right_slots,
},
) if left_address == right_address => {
for slot in right_slots {
if !left_slots.contains(&slot) {
left_slots.push(slot);
}
}
RepairAction::PurgeSlots {
address: left_address,
slots: left_slots,
}
}
(_, other) => other,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ColdStartPolicy {
Strict,
Eager,
Lazy,
HotSlotsOnly,
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ColdStartOutcome {
Ready(ColdStartReport),
ReadyWithDeferred(ColdStartReport, Vec<DeferredWork>),
NeedsRepair(ColdStartReport, RepairAction),
Unsupported(UnsupportedReason),
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ColdStartReport {
pub pool: PoolKey,
pub policy: ColdStartPolicy,
pub status: PoolStatus,
pub verified_slots: Vec<(Address, U256)>,
pub changed_slots: Vec<SlotChange>,
pub applied: StateDiff,
pub deferred: Vec<DeferredWork>,
pub code_seeds: Option<crate::adapters::cold_start::CodeSeedReport>,
}
impl ColdStartReport {
pub fn new(pool: PoolKey, policy: ColdStartPolicy) -> Self {
Self {
pool,
policy,
status: PoolStatus::Pending,
verified_slots: Vec::new(),
changed_slots: Vec::new(),
applied: StateDiff::default(),
deferred: Vec::new(),
code_seeds: None,
}
}
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DeferredWork {
VerifySlots(Vec<(Address, U256)>),
Repair(RepairAction),
ColdStart {
pool: PoolKey,
policy: ColdStartPolicy,
},
Custom(String),
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct DeferredOutcome {
pub verified: Vec<SlotChange>,
pub unhandled: Vec<DeferredWork>,
}
impl DeferredOutcome {
pub fn is_fully_handled(&self) -> bool {
self.unhandled.is_empty()
}
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum UnsupportedReason {
Protocol(ProtocolId),
MissingMetadata(&'static str),
AdapterDefinedRouting,
Custom(String),
}
#[cfg(test)]
mod tests {
use super::*;
fn addr(byte: u8) -> Address {
Address::repeat_byte(byte)
}
#[test]
fn tokens_two_token_protocols_need_both_token0_and_token1() {
let (a, b) = (addr(0xaa), addr(0xbb));
let v2 =
ProtocolMetadata::UniswapV2(UniswapV2Metadata::default().with_token0(a).with_token1(b));
assert_eq!(v2.tokens(), Some(vec![a, b]));
let v3 = ProtocolMetadata::UniswapV3(V3Metadata::default().with_token0(a).with_token1(b));
assert_eq!(v3.tokens(), Some(vec![a, b]));
let pancake =
ProtocolMetadata::PancakeV3(V3Metadata::default().with_token0(a).with_token1(b));
assert_eq!(pancake.tokens(), Some(vec![a, b]));
let solidly =
ProtocolMetadata::SolidlyV2(SolidlyV2Metadata::default().with_token0(a).with_token1(b));
assert_eq!(solidly.tokens(), Some(vec![a, b]));
}
#[test]
fn tokens_multi_token_protocols_return_pool_order() {
let coins = [addr(1), addr(2), addr(3)];
let curve = ProtocolMetadata::Curve(CurveMetadata::default().with_coins(coins));
assert_eq!(curve.tokens(), Some(coins.to_vec()));
let tokens = [addr(4), addr(5)];
let balancer =
ProtocolMetadata::BalancerV2(BalancerV2Metadata::default().with_tokens(tokens));
assert_eq!(balancer.tokens(), Some(tokens.to_vec()));
}
#[test]
fn tokens_are_none_when_the_set_is_not_fully_known() {
let partial =
ProtocolMetadata::UniswapV2(UniswapV2Metadata::default().with_token1(addr(9)));
assert_eq!(partial.tokens(), None);
assert_eq!(
ProtocolMetadata::UniswapV2(UniswapV2Metadata::default()).tokens(),
None
);
assert_eq!(
ProtocolMetadata::Curve(CurveMetadata::default()).tokens(),
None
);
assert_eq!(
ProtocolMetadata::BalancerV2(BalancerV2Metadata::default()).tokens(),
None
);
assert_eq!(ProtocolMetadata::Unknown.tokens(), None);
assert_eq!(ProtocolMetadata::Custom(Arc::new(0u8)).tokens(), None);
}
#[test]
fn pool_registration_tokens_delegates_to_metadata() {
let (a, b) = (addr(0x10), addr(0x11));
let registration = PoolRegistration::new(PoolKey::UniswapV2(addr(0x01))).with_metadata(
ProtocolMetadata::UniswapV2(UniswapV2Metadata::default().with_token0(a).with_token1(b)),
);
assert_eq!(registration.tokens(), Some(vec![a, b]));
assert_eq!(
PoolRegistration::new(PoolKey::UniswapV2(addr(0x02))).tokens(),
None
);
}
#[test]
fn quote_code_targets_v3_family_uses_pool_quoter_or_config_default() {
let config = SimConfig::default();
let bare = ProtocolMetadata::UniswapV3(V3Metadata::default());
assert_eq!(bare.quote_code_targets(&config), vec![config.v3_quoter]);
let quoter = addr(0x77);
for metadata in [
ProtocolMetadata::UniswapV3(V3Metadata::default().with_quoter(quoter)),
ProtocolMetadata::PancakeV3(V3Metadata::default().with_quoter(quoter)),
ProtocolMetadata::Slipstream(V3Metadata::default().with_quoter(quoter)),
] {
assert_eq!(metadata.quote_code_targets(&config), vec![quoter]);
}
}
#[test]
fn quote_code_targets_v2_is_the_config_router() {
let router = addr(0x42);
let config = SimConfig::default().with_v2_router(router);
let v2 = ProtocolMetadata::UniswapV2(UniswapV2Metadata::default());
assert_eq!(v2.quote_code_targets(&config), vec![router]);
}
#[test]
fn quote_code_targets_balancer_is_the_vault_when_known() {
let config = SimConfig::default();
let vault = addr(0x88);
let known = ProtocolMetadata::BalancerV2(BalancerV2Metadata::default().with_vault(vault));
assert_eq!(known.quote_code_targets(&config), vec![vault]);
assert!(
ProtocolMetadata::BalancerV2(BalancerV2Metadata::default())
.quote_code_targets(&config)
.is_empty()
);
}
#[test]
fn quote_code_targets_self_quoting_and_opaque_protocols_are_empty() {
let config = SimConfig::default();
for metadata in [
ProtocolMetadata::SolidlyV2(SolidlyV2Metadata::default()),
ProtocolMetadata::Curve(CurveMetadata::default()),
ProtocolMetadata::Unknown,
ProtocolMetadata::Custom(Arc::new(0u8)),
] {
assert!(metadata.quote_code_targets(&config).is_empty());
}
}
#[test]
fn pool_registration_quote_code_targets_delegates_to_metadata() {
let router = addr(0x21);
let config = SimConfig::default().with_v2_router(router);
let registration = PoolRegistration::new(PoolKey::UniswapV2(addr(0x01)))
.with_metadata(ProtocolMetadata::UniswapV2(UniswapV2Metadata::default()));
assert_eq!(registration.quote_code_targets(&config), vec![router]);
}
#[test]
fn combine_none_is_absorbing() {
let verify = RepairAction::VerifySlots(vec![(addr(0x11), U256::from(1))]);
assert_eq!(RepairAction::None.combine(verify.clone()), verify);
assert_eq!(verify.clone().combine(RepairAction::None), verify);
assert_eq!(
RepairAction::None.combine(RepairAction::None),
RepairAction::None
);
}
#[test]
fn combine_verify_slots_unions_and_dedupes() {
let a = addr(0x11);
let left = RepairAction::VerifySlots(vec![(a, U256::from(1)), (a, U256::from(2))]);
let right = RepairAction::VerifySlots(vec![(a, U256::from(2)), (a, U256::from(3))]);
assert_eq!(
left.combine(right),
RepairAction::VerifySlots(vec![
(a, U256::from(1)),
(a, U256::from(2)),
(a, U256::from(3)),
])
);
}
#[test]
fn combine_purge_slots_same_address_unions() {
let a = addr(0x22);
let left = RepairAction::PurgeSlots {
address: a,
slots: vec![U256::from(1), U256::from(2)],
};
let right = RepairAction::PurgeSlots {
address: a,
slots: vec![U256::from(2), U256::from(3)],
};
assert_eq!(
left.combine(right),
RepairAction::PurgeSlots {
address: a,
slots: vec![U256::from(1), U256::from(2), U256::from(3)],
}
);
}
#[test]
fn combine_purge_slots_different_address_prefers_other() {
let left = RepairAction::PurgeSlots {
address: addr(0x22),
slots: vec![U256::from(1)],
};
let right = RepairAction::PurgeSlots {
address: addr(0x33),
slots: vec![U256::from(9)],
};
assert_eq!(left.combine(right.clone()), right);
}
#[test]
fn combine_fallthrough_prefers_other() {
let left = RepairAction::VerifySlots(vec![(addr(0x11), U256::from(1))]);
let right = RepairAction::PurgeStorage(addr(0x44));
assert_eq!(left.combine(right.clone()), right);
}
}