use alloy_primitives::{address, b256, Address, B256, U256};
use alloy_provider::{network::Ethereum, Provider};
use alloy_sol_types::SolCall;
use anyhow::{anyhow, Result};
use wp_evm_base::{chain::Chain, types::SlippageBps};
use wp_evm_ramses_provider as ramses;
use wp_evm_ramses_provider::data::RamsesProtocolConfig;
use wp_evm_v3_provider::plan::PeripherySelectors;
use wp_evm_velodrome_interfaces::periphery::router::ISlipstreamPeripheryRouter;
const SELECTORS: PeripherySelectors = PeripherySelectors {
multicall: ISlipstreamPeripheryRouter::multicallCall::SELECTOR,
unwrap_native: ISlipstreamPeripheryRouter::unwrapWETH9Call::SELECTOR,
sweep_token: ISlipstreamPeripheryRouter::sweepTokenCall::SELECTOR,
refund_native: ISlipstreamPeripheryRouter::refundETHCall::SELECTOR,
};
pub use wp_evm_ramses_provider::data::{
CollectFeesParams, ExactInParams, ExactOutParams, PlanFragment, PoolState, PositionState,
Quote, RamsesAddLiquidityParams, RemoveAndCollectParams, RemoveLiquidityParams,
};
pub use wp_evm_ramses_provider::position::{
position_key, RamsesPositionView, VelodromePositionRow,
};
pub use wp_evm_ramses_provider::position_views::{PositionFees, PositionViewEntry};
pub use wp_evm_ramses_provider::quote::QuoteError;
pub use wp_evm_ramses_provider::Enumeration;
pub use wp_evm_ramses_provider::plan;
pub use wp_evm_v3_provider::pool_views::{PoolReadEntry, PoolViewData};
pub async fn pool_views<P: Provider<Ethereum>>(
provider: &P,
pools: &[Address],
) -> Result<Vec<PoolReadEntry>> {
wp_evm_v3_provider::pool_views::pool_views(
provider,
ramses::MULTICALL3_ADDRESS,
pools,
&ramses::pool_views::VelodromePoolViewSource,
)
.await
}
pub async fn position_token_pair<P: Provider<Ethereum>>(
provider: &P,
nfpm: Address,
token_id: U256,
) -> Result<(Address, Address)> {
ramses::hydrate::velodrome_position_token_pair(provider, nfpm, token_id).await
}
pub const CONFIG: RamsesProtocolConfig = RamsesProtocolConfig {
factory: address!("Cc0bDDB707055e04e497aB22a59c2aF4391cd12F"),
pool_deployer: Address::ZERO,
router: address!("0792a633F0c19c351081CF4B211F68F79bCc9676"),
position_mgr: address!("416b433906b1B72FA758e166e239c43d68dC6F29"),
init_code_hash: b256!("339492e30b7a68609e535da9b0773082bfe60230ca47639ee5566007d525f5a7"),
tick_spacings: &[1, 50, 100, 200, 2000],
multicall: address!("cA11bde05977b3631167028862bE2a173976CA11"),
quoter: None,
voter: address!("41C914ee0c7E1A5edCD0295623e6dC557B5aBf3C"),
};
pub fn config_for_chain(chain: Chain) -> Option<&'static RamsesProtocolConfig> {
match chain {
Chain::Optimism => Some(&CONFIG),
Chain::Ethereum
| Chain::Arbitrum
| Chain::Polygon
| Chain::Base
| Chain::Bsc
| Chain::Sonic
| Chain::HyperEvm
| Chain::Avalanche
| Chain::Celo => None,
}
}
pub fn factory(chain: Chain) -> Option<Address> {
config_for_chain(chain).map(|c| c.factory)
}
pub fn pool_deployer(chain: Chain) -> Option<Address> {
config_for_chain(chain).map(|c| c.pool_deployer)
}
pub fn position_manager(chain: Chain) -> Option<Address> {
config_for_chain(chain).map(|c| c.position_mgr)
}
pub fn router(chain: Chain) -> Option<Address> {
config_for_chain(chain).map(|c| c.router)
}
pub fn quoter(chain: Chain) -> Option<Address> {
config_for_chain(chain).and_then(|c| c.quoter)
}
pub fn multicall(chain: Chain) -> Option<Address> {
config_for_chain(chain).map(|c| c.multicall)
}
pub fn init_code_hash(chain: Chain) -> Option<B256> {
config_for_chain(chain).map(|c| c.init_code_hash)
}
pub fn voter(chain: Chain) -> Option<Address> {
config_for_chain(chain).map(|c| c.voter)
}
pub fn supports(chain: Chain) -> bool {
config_for_chain(chain).is_some()
}
pub async fn pool_state<P: Provider<Ethereum>>(
provider: &P,
chain: Chain,
pool: Address,
) -> Result<PoolState> {
let cfg =
config_for_chain(chain).ok_or_else(|| anyhow!("Velodrome not deployed on {chain:?}"))?;
ramses::hydrate::pool_state_velodrome(provider, cfg.multicall, pool).await
}
pub async fn position_state<P: Provider<Ethereum>>(
provider: &P,
chain: Chain,
token_id: U256,
) -> Result<PositionState> {
let cfg =
config_for_chain(chain).ok_or_else(|| anyhow!("Velodrome not deployed on {chain:?}"))?;
ramses::hydrate::position_state_slipstream(provider, cfg.multicall, cfg.position_mgr, token_id)
.await
}
pub async fn position_views<P: Provider<Ethereum>>(
provider: &P,
chain: Chain,
token_ids: &[U256],
) -> Result<Vec<PositionViewEntry<VelodromePositionRow>>> {
let cfg =
config_for_chain(chain).ok_or_else(|| anyhow!("Velodrome not deployed on {chain:?}"))?;
ramses::position_views::velodrome_position_views(
provider,
cfg.multicall,
cfg.position_mgr,
token_ids,
)
.await
}
pub async fn position_views_with_nfpm<P: Provider<Ethereum>>(
provider: &P,
chain: Chain,
nfpm: Address,
token_ids: &[U256],
) -> Result<Vec<PositionViewEntry<VelodromePositionRow>>> {
let multicall =
config_for_chain(chain).map(|cfg| cfg.multicall).unwrap_or(ramses::MULTICALL3_ADDRESS);
ramses::position_views::velodrome_position_views(provider, multicall, nfpm, token_ids).await
}
pub async fn enumerate_owner_token_ids<P: Provider<Ethereum>>(
provider: &P,
chain: Chain,
nfpm: Address,
owner: Address,
) -> Result<Enumeration> {
let multicall =
config_for_chain(chain).map(|cfg| cfg.multicall).unwrap_or(ramses::MULTICALL3_ADDRESS);
ramses::enumerate_owner_token_ids(provider, multicall, nfpm, owner, chain).await
}
pub async fn populate_positions_fees<P: Provider<Ethereum>>(
provider: &P,
chain: Chain,
entries: &mut [PositionViewEntry<VelodromePositionRow>],
) -> Result<()> {
let cfg =
config_for_chain(chain).ok_or_else(|| anyhow!("Velodrome not deployed on {chain:?}"))?;
ramses::position_views::velodrome_populate_position_fees(
provider,
cfg.multicall,
entries,
|v| pool_address(chain, v.token0, v.token1, v.tick_spacing, None),
)
.await
}
pub fn quote_exact_in(s: &PoolState, p: &ExactInParams) -> Result<Quote, QuoteError> {
ramses::quote::exact_in(s, p)
}
pub fn quote_exact_out(s: &PoolState, p: &ExactOutParams) -> Result<Quote, QuoteError> {
ramses::quote::exact_out(s, p)
}
pub async fn populate_ticks<P: Provider<Ethereum>>(
provider: &P,
chain: Chain,
pool: Address,
state: &mut PoolState,
) -> Result<()> {
config_for_chain(chain).ok_or_else(|| anyhow!("Velodrome not deployed on {chain:?}"))?;
ramses::populate_ticks::populate_ticks(provider, pool, state).await
}
pub async fn quote_online_exact_in<P: Provider<Ethereum>>(
provider: &P,
chain: Chain,
state: &PoolState,
params: &ExactInParams,
) -> Result<Quote> {
let cfg =
config_for_chain(chain).ok_or_else(|| anyhow!("Velodrome not deployed on {chain:?}"))?;
let quoter = cfg
.quoter
.ok_or_else(|| anyhow!("Velodrome Slipstream quoter not registered on {chain:?}"))?;
ramses::quote_online::quote_online_exact_in(provider, quoter, state, params).await
}
pub fn plan_swap_exact_in(
s: &PoolState,
q: &Quote,
p: &ExactInParams,
slippage: SlippageBps,
deadline: u64,
chain: Chain,
) -> Result<PlanFragment> {
let cfg =
config_for_chain(chain).ok_or_else(|| anyhow!("Velodrome not deployed on {chain:?}"))?;
Ok(ramses::plan::swap_exact_in(s, q, p, slippage, deadline, cfg.router))
}
pub fn plan_add_liquidity(
p: &RamsesAddLiquidityParams,
slippage: SlippageBps,
deadline: u64,
chain: Chain,
) -> Result<PlanFragment> {
let cfg =
config_for_chain(chain).ok_or_else(|| anyhow!("Velodrome not deployed on {chain:?}"))?;
Ok(ramses::plan::add_liquidity_slipstream(p, slippage, deadline, cfg.position_mgr))
}
pub fn plan_add_liquidity_with_min(
p: &RamsesAddLiquidityParams,
amount0_min: U256,
amount1_min: U256,
deadline: u64,
chain: Chain,
) -> Result<PlanFragment> {
let cfg =
config_for_chain(chain).ok_or_else(|| anyhow!("Velodrome not deployed on {chain:?}"))?;
Ok(ramses::plan::add_liquidity_slipstream_with_min(
p,
amount0_min,
amount1_min,
deadline,
cfg.position_mgr,
))
}
#[allow(clippy::too_many_arguments)]
pub fn plan_increase_liquidity(
token_id: U256,
token0: Address,
token1: Address,
amount0_desired: U256,
amount1_desired: U256,
slippage: SlippageBps,
deadline: u64,
chain: Chain,
) -> Result<PlanFragment> {
let cfg =
config_for_chain(chain).ok_or_else(|| anyhow!("Velodrome not deployed on {chain:?}"))?;
Ok(ramses::plan::increase_liquidity(
token_id,
token0,
token1,
amount0_desired,
amount1_desired,
slippage,
deadline,
cfg.position_mgr,
))
}
pub fn plan_remove_liquidity(
p: &RemoveLiquidityParams,
deadline: u64,
chain: Chain,
) -> Result<PlanFragment> {
let cfg =
config_for_chain(chain).ok_or_else(|| anyhow!("Velodrome not deployed on {chain:?}"))?;
Ok(ramses::plan::remove_liquidity(p, deadline, cfg.position_mgr))
}
pub fn plan_remove_liquidity_and_collect(
p: &RemoveAndCollectParams,
deadline: u64,
chain: Chain,
) -> Result<PlanFragment> {
let cfg =
config_for_chain(chain).ok_or_else(|| anyhow!("Velodrome not deployed on {chain:?}"))?;
let wrap = wp_evm_v3_provider::plan::resolve_native_wrap_remove_and_collect(
p,
cfg.position_mgr,
chain,
)?;
let mut core_params = (*p).clone();
core_params.recipient = wrap.effective_collect_recipient;
let frag = ramses::plan::remove_liquidity_and_collect(&core_params, deadline, cfg.position_mgr);
wp_evm_v3_provider::plan::compose_native_remove_collect_multicall(frag, &wrap, SELECTORS)
}
pub fn plan_collect_fees(p: &CollectFeesParams, chain: Chain) -> Result<PlanFragment> {
let cfg =
config_for_chain(chain).ok_or_else(|| anyhow!("Velodrome not deployed on {chain:?}"))?;
let wrap = wp_evm_v3_provider::plan::resolve_native_wrap_collect(p, cfg.position_mgr, chain)?;
let core_params = CollectFeesParams {
token_id: p.token_id,
recipient: wrap.effective_recipient,
token0: p.token0,
token1: p.token1,
caller: p.caller,
};
let frag = ramses::plan::collect_fees(&core_params, cfg.position_mgr);
Ok(wp_evm_v3_provider::plan::compose_native_collect_multicall(frag, &wrap, SELECTORS))
}
pub fn pool_address(
chain: Chain,
token_a: Address,
token_b: Address,
tick_spacing: i32,
init_code_hash_override: Option<B256>,
) -> Option<Address> {
let cfg = config_for_chain(chain)?;
let init_code_hash = init_code_hash_override.unwrap_or(cfg.init_code_hash);
Some(ramses::pool_address::compute(cfg.factory, init_code_hash, token_a, token_b, tick_spacing))
}
pub async fn pending_emissions<P: Provider<Ethereum>>(
provider: &P,
chain: Chain,
pool: Address,
account: Address,
token_id: U256,
) -> Result<Option<wp_evm_ramses_provider::velodrome_gauge::VelodromePendingEmissions>> {
let cfg =
config_for_chain(chain).ok_or_else(|| anyhow!("Velodrome not deployed on {chain:?}"))?;
wp_evm_ramses_provider::velodrome_gauge::pending_emissions(
provider,
cfg.multicall,
cfg.voter,
pool,
account,
token_id,
)
.await
}
pub fn plan_claim_gauge(gauge: Address, token_id: U256) -> PlanFragment {
wp_evm_ramses_provider::plan::claim_gauge(gauge, token_id)
}
#[cfg(test)]
mod tests {
use super::*;
use wp_evm_ramses_provider::data::TickInfo;
#[test]
fn config_router_is_known_slipstream_address() {
assert_eq!(CONFIG.router, address!("0792a633F0c19c351081CF4B211F68F79bCc9676"));
}
#[test]
fn config_factory_is_known_slipstream_address() {
assert_eq!(CONFIG.factory, address!("Cc0bDDB707055e04e497aB22a59c2aF4391cd12F"));
}
#[test]
fn config_tick_spacings_match_cl_factory_constructor() {
assert_eq!(CONFIG.tick_spacings, &[1, 50, 100, 200, 2000]);
}
#[test]
fn config_for_chain_returns_some_for_optimism_only() {
assert_eq!(config_for_chain(Chain::Optimism), Some(&CONFIG));
for unsupported in [
Chain::Ethereum,
Chain::Arbitrum,
Chain::Polygon,
Chain::Base,
Chain::Bsc,
Chain::Sonic,
Chain::Avalanche,
Chain::Celo,
] {
assert!(
config_for_chain(unsupported).is_none(),
"slipstream should not surface {unsupported:?}",
);
}
}
#[test]
fn pool_address_matches_canonical_velodrome_usdc_weth_cl100() {
let pool = pool_address(
Chain::Optimism,
address!("0b2C639c533813f4Aa9D7837CAf62653d097Ff85"),
address!("4200000000000000000000000000000000000006"),
100,
None,
)
.expect("Optimism supported");
assert_eq!(pool, address!("478946BcD4a5a22b316470F5486fAfb928C0bA25"));
}
#[test]
fn pool_address_token_order_independent() {
let usdc = address!("0b2C639c533813f4Aa9D7837CAf62653d097Ff85");
let weth = address!("4200000000000000000000000000000000000006");
assert_eq!(
pool_address(Chain::Optimism, usdc, weth, 100, None),
pool_address(Chain::Optimism, weth, usdc, 100, None)
);
}
#[test]
fn plan_claim_gauge_targets_gauge_with_get_reward_selector() {
let gauge = address!("4444444444444444444444444444444444444444");
let token_id = U256::from(42u64);
let frag = plan_claim_gauge(gauge, token_id);
assert_eq!(frag.calls.len(), 1);
assert_eq!(frag.calls[0].target, gauge);
assert!(frag.approvals.is_empty());
assert_eq!(frag.value, U256::ZERO);
assert_eq!(&frag.calls[0].calldata[..4], &[0x1c, 0x4b, 0x77, 0x4b]);
}
#[test]
fn plan_add_liquidity_accepts_ramses_params_with_tick_spacing() {
let p = RamsesAddLiquidityParams {
token0: address!("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"),
token1: address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"),
tick_spacing: 100,
tick_lower: -887_200,
tick_upper: 887_200,
amount0_desired: U256::from(1_000_000u64),
amount1_desired: U256::from(500_000_000_000_000u64),
recipient: address!("0000000000000000000000000000000000000099"),
};
let frag = plan_add_liquidity(&p, SlippageBps::new(50), 9_999_999_999, Chain::Optimism)
.expect("Optimism supported");
assert_eq!(frag.calls.len(), 1);
assert_eq!(frag.calls[0].target, CONFIG.position_mgr);
assert_eq!(frag.approvals.len(), 2);
assert_eq!(frag.approvals[0].token, p.token0);
assert_eq!(frag.approvals[1].token, p.token1);
assert_eq!(frag.value, U256::ZERO);
}
#[test]
fn plan_add_liquidity_with_min_threads_precomputed_mins_at_position_manager() {
let p = RamsesAddLiquidityParams {
token0: address!("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"),
token1: address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"),
tick_spacing: 100,
tick_lower: -887_200,
tick_upper: 887_200,
amount0_desired: U256::from(1_000_000u64),
amount1_desired: U256::from(500_000_000_000_000u64),
recipient: address!("0000000000000000000000000000000000000099"),
};
let m0 = U256::from(123_456u64);
let m1 = U256::from(789_012u64);
let frag = plan_add_liquidity_with_min(&p, m0, m1, 9_999_999_999, Chain::Optimism)
.expect("Optimism supported");
assert_eq!(frag.calls.len(), 1);
assert_eq!(frag.calls[0].target, CONFIG.position_mgr);
assert_eq!(frag.approvals.len(), 2);
let bare = ramses::plan::add_liquidity_slipstream_with_min(
&p,
m0,
m1,
9_999_999_999,
CONFIG.position_mgr,
);
assert_eq!(frag.calls[0].calldata, bare.calls[0].calldata);
let other = ramses::plan::add_liquidity_slipstream_with_min(
&p,
m0 + U256::from(1u64),
m1,
9_999_999_999,
CONFIG.position_mgr,
);
assert_ne!(frag.calls[0].calldata, other.calls[0].calldata);
}
fn fixture_usdc_weth() -> PoolState {
let sqrt_price_x96 = U256::from_str_radix("3543191142285914205922034323214", 10).unwrap();
PoolState {
token0: address!("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"),
token1: address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"),
fee: 3000,
tick_spacing: 100,
sqrt_price_x96,
liquidity: 2_000_000_000_000_000_000_000u128,
tick: 76012,
ticks: vec![
TickInfo {
tick: 74900,
liquidity_net: 1_000_000_000_000_000_000_000i128,
liquidity_gross: 1_000_000_000_000_000_000_000u128,
},
TickInfo {
tick: 75900,
liquidity_net: 1_000_000_000_000_000_000_000i128,
liquidity_gross: 1_000_000_000_000_000_000_000u128,
},
TickInfo {
tick: 76100,
liquidity_net: -2_000_000_000_000_000_000_000i128,
liquidity_gross: 2_000_000_000_000_000_000_000u128,
},
],
}
}
#[test]
fn quote_exact_in_delegates_to_ramses_family() {
let state = fixture_usdc_weth();
let params = ExactInParams {
token_in: state.token0,
token_out: state.token1,
amount_in: U256::from(1_000_000u64),
recipient: address!("0000000000000000000000000000000000000099"),
};
let quote = quote_exact_in(&state, ¶ms).expect("quote should succeed");
assert!(quote.amount_out > U256::ZERO);
assert_eq!(quote.amount_in, params.amount_in);
}
#[test]
fn plan_swap_exact_in_targets_slipstream_router() {
let state = fixture_usdc_weth();
let quote = Quote {
amount_in: U256::from(1_000_000u64),
amount_out: U256::from(500_000_000_000_000u64),
sqrt_price_x96_after: state.sqrt_price_x96,
price_impact_bps: 0,
};
let params = ExactInParams {
token_in: state.token0,
token_out: state.token1,
amount_in: quote.amount_in,
recipient: address!("0000000000000000000000000000000000000099"),
};
let frag = plan_swap_exact_in(
&state,
"e,
¶ms,
SlippageBps::new(50),
u64::MAX,
Chain::Optimism,
)
.expect("Optimism supported");
assert_eq!(frag.calls.len(), 1);
assert_eq!(frag.calls[0].target, CONFIG.router);
assert_eq!(frag.approvals.len(), 1);
}
#[test]
fn quote_exact_out_delegates_to_ramses_family() {
let state = fixture_usdc_weth();
let params = ExactOutParams {
token_in: state.token0,
token_out: state.token1,
amount_out: U256::from(500_000_000_000_000u64),
recipient: address!("0000000000000000000000000000000000000099"),
};
let quote = quote_exact_out(&state, ¶ms).expect("exact-out quote should succeed");
assert!(quote.amount_in > U256::ZERO);
assert_eq!(quote.amount_out, params.amount_out);
}
#[test]
fn plan_remove_liquidity_targets_position_manager_no_approvals() {
let params = RemoveLiquidityParams {
token_id: U256::from(42u64),
liquidity: 1_000_000_000_000u128,
amount0_min: None,
amount1_min: None,
};
let frag =
plan_remove_liquidity(¶ms, u64::MAX, Chain::Optimism).expect("Optimism supported");
assert_eq!(frag.calls.len(), 1);
assert_eq!(frag.calls[0].target, CONFIG.position_mgr);
assert!(frag.approvals.is_empty());
assert_eq!(frag.value, U256::ZERO);
}
#[test]
fn plan_collect_fees_targets_position_manager_no_approvals() {
let params = CollectFeesParams {
token_id: U256::from(42u64),
recipient: address!("0000000000000000000000000000000000000099"),
token0: address!("0000000000000000000000000000000000000001"),
token1: address!("0000000000000000000000000000000000000002"),
caller: Address::ZERO,
};
let frag = plan_collect_fees(¶ms, Chain::Optimism).expect("Optimism supported");
assert_eq!(frag.calls.len(), 1);
assert_eq!(frag.calls[0].target, CONFIG.position_mgr);
assert!(frag.approvals.is_empty());
assert_eq!(frag.value, U256::ZERO);
}
#[test]
fn plan_collect_fees_native_recipient_emits_multicall_with_unwrap_and_sweep() {
let params = CollectFeesParams {
token_id: U256::from(1u64),
recipient: Address::ZERO,
token0: address!("0b2C639c533813f4Aa9D7837CAf62653d097Ff85"), token1: address!("4200000000000000000000000000000000000006"), caller: address!("dEaDbEEFdeAdBeEfDEadBeEFDeaDbEEfdeadbEEF"),
};
let frag = plan_collect_fees(¶ms, Chain::Optimism).expect("Optimism supported");
assert_eq!(&frag.calls[0].calldata[..4], &[0xac, 0x96, 0x50, 0xd8]);
assert_eq!(frag.calls[0].target, CONFIG.position_mgr);
assert_eq!(frag.value, U256::ZERO);
assert_eq!(frag.calls[0].value, U256::ZERO);
assert!(
frag.calls[0].calldata.windows(4).any(|w| w == [0x49, 0x40, 0x4b, 0x7c]),
"native collect multicall must include unwrapWETH9(uint256,address) tail"
);
assert!(
frag.calls[0].calldata.windows(4).any(|w| w == [0xdf, 0x2a, 0xb5, 0xbb]),
"native collect multicall must include sweepToken(address,uint256,address) tail"
);
}
#[test]
fn plan_collect_fees_non_native_recipient_passthrough() {
let params = CollectFeesParams {
token_id: U256::from(1u64),
recipient: address!("0000000000000000000000000000000000000099"),
token0: address!("0b2C639c533813f4Aa9D7837CAf62653d097Ff85"),
token1: address!("4200000000000000000000000000000000000006"),
caller: Address::ZERO,
};
let frag = plan_collect_fees(¶ms, Chain::Optimism).expect("Optimism supported");
let bare = ramses::plan::collect_fees(¶ms, CONFIG.position_mgr);
assert_ne!(
&frag.calls[0].calldata[..4],
&[0xac, 0x96, 0x50, 0xd8],
"non-native case must NOT be wrapped in multicall"
);
assert_eq!(
frag.calls[0].calldata, bare.calls[0].calldata,
"non-native pass-through must stay byte-identical to bare collect()"
);
}
#[test]
fn plan_collect_fees_no_native_side_rejects() {
let params = CollectFeesParams {
token_id: U256::from(1u64),
recipient: Address::ZERO,
token0: address!("0b2C639c533813f4Aa9D7837CAf62653d097Ff85"), token1: address!("94b008aA00579c1307B0EF2c499aD98a8ce58e58"), caller: address!("dEaDbEEFdeAdBeEfDEadBeEFDeaDbEEfdeadbEEF"),
};
let err = plan_collect_fees(¶ms, Chain::Optimism).unwrap_err();
let msg = format!("{err:#}");
assert!(msg.contains("neither") || msg.contains("native"), "got: {msg}");
}
fn fixture_remove_and_collect_params_weth_paired() -> RemoveAndCollectParams {
RemoveAndCollectParams {
token_id: U256::from(1u64),
liquidity: 1_000_000u128,
amount0_min: Some(U256::from(100u64)),
amount1_min: Some(U256::from(200u64)),
recipient: Address::ZERO,
token0: address!("0b2C639c533813f4Aa9D7837CAf62653d097Ff85"), token1: address!("4200000000000000000000000000000000000006"), caller: address!("dEaDbEEFdeAdBeEfDEadBeEFDeaDbEEfdeadbEEF"),
burn: false,
}
}
#[test]
fn plan_remove_liquidity_and_collect_native_recipient_emits_4_call_multicall() {
let params = fixture_remove_and_collect_params_weth_paired();
let frag = plan_remove_liquidity_and_collect(¶ms, 9_999_999_999, Chain::Optimism)
.expect("Optimism supported");
assert_eq!(&frag.calls[0].calldata[..4], &[0xac, 0x96, 0x50, 0xd8]);
assert_eq!(frag.calls[0].target, CONFIG.position_mgr);
assert_eq!(frag.value, U256::ZERO);
assert!(frag.approvals.is_empty());
use alloy_sol_types::SolValue;
let (inner,): (Vec<alloy_primitives::Bytes>,) =
<(Vec<alloy_primitives::Bytes>,)>::abi_decode_params(&frag.calls[0].calldata[4..])
.expect("decode outer multicall params");
assert_eq!(inner.len(), 4, "expected 4 inner calls");
assert_eq!(&inner[0][..4], &[0x0c, 0x49, 0xcc, 0xbe], "inner[0] = decreaseLiquidity");
assert_eq!(&inner[1][..4], &[0xfc, 0x6f, 0x78, 0x65], "inner[1] = collect");
assert_eq!(&inner[2][..4], &[0x49, 0x40, 0x4b, 0x7c], "inner[2] = unwrapWETH9");
assert_eq!(&inner[3][..4], &[0xdf, 0x2a, 0xb5, 0xbb], "inner[3] = sweepToken");
}
#[test]
fn plan_remove_liquidity_and_collect_non_native_recipient_passthrough() {
let mut params = fixture_remove_and_collect_params_weth_paired();
params.recipient = address!("0000000000000000000000000000000000000099");
let frag = plan_remove_liquidity_and_collect(¶ms, 9_999_999_999, Chain::Optimism)
.expect("Optimism supported");
assert_eq!(&frag.calls[0].calldata[..4], &[0xac, 0x96, 0x50, 0xd8]);
let bare =
ramses::plan::remove_liquidity_and_collect(¶ms, 9_999_999_999, CONFIG.position_mgr);
assert_eq!(
frag.calls[0].calldata, bare.calls[0].calldata,
"non-native pass-through must stay byte-identical to bare multicall([decrease, collect])"
);
}
#[test]
fn plan_remove_liquidity_and_collect_no_native_side_rejects() {
let mut params = fixture_remove_and_collect_params_weth_paired();
params.token1 = address!("94b008aA00579c1307B0EF2c499aD98a8ce58e58"); let err =
plan_remove_liquidity_and_collect(¶ms, 9_999_999_999, Chain::Optimism).unwrap_err();
let msg = format!("{err:#}");
assert!(msg.contains("neither") || msg.contains("native"), "got: {msg}");
}
#[test]
fn factory_returns_chain_specific_address_via_layer2() {
assert_eq!(factory(Chain::Optimism), Some(CONFIG.factory));
for unsupported in [
Chain::Ethereum,
Chain::Arbitrum,
Chain::Polygon,
Chain::Base,
Chain::Bsc,
Chain::Sonic,
Chain::Avalanche,
Chain::Celo,
] {
assert_eq!(factory(unsupported), None);
}
}
#[tokio::test]
async fn pool_state_routes_to_chain_specific_multicall() {
let Some(rpc) = std::env::var("OPTIMISM_RPC_URL").ok() else {
eprintln!(
"SKIP pool_state_routes_to_chain_specific_multicall: \
set OPTIMISM_RPC_URL to an Optimism archive RPC to enable"
);
return;
};
let anvil = alloy::node_bindings::Anvil::new().fork(rpc).spawn();
let provider = alloy::providers::ProviderBuilder::new().connect_http(anvil.endpoint_url());
let optimism_pool = address!("478946BcD4a5a22b316470F5486fAfb928C0bA25");
let state = pool_state(&provider, Chain::Optimism, optimism_pool)
.await
.expect("Optimism pool_state must succeed via Layer 2 chain-aware routing");
assert!(
state.liquidity > 0,
"Real on-chain Velodrome Slipstream pool should have non-zero liquidity"
);
}
#[test]
fn pool_address_with_override_uses_override_not_config_hash() {
let usdc = address!("0b2C639c533813f4Aa9D7837CAf62653d097Ff85");
let weth = address!("4200000000000000000000000000000000000006");
let custom_hash = b256!("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
let with_override = pool_address(Chain::Optimism, usdc, weth, 100, Some(custom_hash))
.expect("Optimism supported");
let without_override =
pool_address(Chain::Optimism, usdc, weth, 100, None).expect("Optimism supported");
assert_ne!(with_override, without_override);
}
}