#![expect(clippy::expect_used, reason = "test assertions fail loudly")]
use std::sync::Arc;
use alloy::primitives::aliases::U112;
use alloy::primitives::{Address, U256};
use degenbot::bot::arb_engine::EngineDriver;
use degenbot::bot::bot_core::Bot;
use degenbot::config::BotConfig;
use degenbot::strategy::{Settlement, Strategy, StrategyName, TxpoolBackrun};
use degenbot::substrate::session_registry::{PathObject, PoolIdentity, SessionObjectRegistry};
use degenbot::substrate::state_lock::LockSite;
use degenbot::substrate::RegisterV2PoolParams;
const CHAIN_ID: u64 = 1;
fn pool_a() -> Address {
Address::from([0x11u8; 20])
}
fn pool_b() -> Address {
Address::from([0x12u8; 20])
}
fn usdc(amount: u64) -> U112 {
(U256::from(amount) * U256::from(10u64).pow(U256::from(6))).to::<U112>()
}
fn weth(amount: u64) -> U112 {
(U256::from(amount) * U256::from(10u64).pow(U256::from(18))).to::<U112>()
}
fn v2_params(
address: Address,
token0: Address,
token1: Address,
r0: U112,
r1: U112,
) -> RegisterV2PoolParams {
RegisterV2PoolParams {
address,
token0,
token1,
reserve0: r0,
reserve1: r1,
fee_token0: (997, 1000),
fee_token1: (997, 1000),
factory: Address::ZERO,
update_block: 0,
..Default::default()
}
}
struct StrategySession {
registry: Arc<SessionObjectRegistry>,
driver: Arc<EngineDriver>,
}
impl StrategySession {
fn new() -> Self {
let bot = Arc::new(Bot::new(CHAIN_ID));
{
let core = bot.state_arc();
let mut state = core.write_at(LockSite::Registration);
let token_a = Address::from([0xaau8; 20]);
let token_b = Address::from([0xbbu8; 20]);
state
.register_v2_pool(&v2_params(
pool_a(),
token_a,
token_b,
usdc(1_500_000),
weth(800),
))
.expect("test setup: V2 pool A");
state
.register_v2_pool(&v2_params(
pool_b(),
token_b,
token_a,
weth(1000),
usdc(2_000_000),
))
.expect("test setup: V2 pool B");
}
let cfg = °enbot::config::holder::config_arc();
let driver = Arc::new(EngineDriver::new(bot, cfg));
let registry = driver.bot().session_registry();
assert!(
registry.has_path_owner(),
"a booted session reaches its paths through the engine it drives"
);
for address in [pool_a(), pool_b()] {
let _ = registry.get_or_create_pool(PoolIdentity::v2(address));
}
Self { registry, driver }
}
fn route() -> [(PoolIdentity, bool); 2] {
[
(PoolIdentity::v2(pool_a()), true),
(PoolIdentity::v2(pool_b()), true),
]
}
fn canonical_path(&self) -> Arc<PathObject> {
self.registry
.get_or_create_path(&Self::route())
.expect("canonical path")
}
}
#[derive(Debug, PartialEq, Eq)]
struct ArmPlan {
path_id: u64,
solver: &'static str,
submission: String,
}
fn settlement_plan(path: &PathObject, cfg: &BotConfig) -> ArmPlan {
let arm = Settlement::from_config(cfg);
ArmPlan {
path_id: path.path_id(),
solver: "engine-cycle",
submission: arm.config().endpoints.join(","),
}
}
fn txpool_backrun_plan(path: &PathObject, cfg: &BotConfig) -> ArmPlan {
let arm = TxpoolBackrun::from_config(cfg, String::from("http://node.invalid"));
ArmPlan {
path_id: path.path_id(),
solver: "frame",
submission: match &arm.config().submission {
degenbot::strategy::SubmissionSlot::PublicFanOut { relays } => relays.join(","),
other => format!("{other:?}"),
},
}
}
fn arms_config() -> BotConfig {
let mut cfg = BotConfig::default();
cfg.strategy.settlement.active = true;
cfg.strategy.settlement.endpoints = Some(String::from("https://a.local,https://b.local"));
cfg.strategy.txpool_backrun.bid_mode = true;
cfg.strategy.txpool_backrun.budget_wei = 1_000_000_000_000_000_000u128;
cfg.strategy.txpool_backrun.endpoints = Some(String::from("https://rpc.local"));
cfg
}
#[test]
fn the_settlement_and_txpool_backrun_arms_share_one_canonical_path() {
let session = StrategySession::new();
let from_settlement = session.canonical_path();
let from_backrun = session.canonical_path();
assert!(
Arc::ptr_eq(&from_settlement, &from_backrun),
"one route, one canonical object"
);
assert_eq!(from_settlement.path_id(), from_backrun.path_id());
assert_eq!(session.driver.stages().path_count(), 1);
assert_eq!(session.registry.path_count(), 1);
let route = StrategySession::route();
assert_eq!(from_settlement.identity().hops().len(), 2);
assert_eq!(from_settlement.hops()[0].pool_identity(), &route[0].0);
}
#[test]
fn arm_policy_does_not_reach_the_canonical_path() {
let session = StrategySession::new();
let cfg = arms_config();
let path = session.canonical_path();
let settlement = settlement_plan(&path, &cfg);
let backrun = txpool_backrun_plan(&path, &cfg);
assert_ne!(settlement, backrun);
assert_eq!(settlement.path_id, backrun.path_id);
assert_eq!(settlement.path_id, path.path_id());
assert_ne!(settlement.solver, backrun.solver);
assert_ne!(settlement.submission, backrun.submission);
let reread = session
.registry
.resolve_path(&StrategySession::route())
.expect("canonical path");
assert_eq!(reread, path);
assert_eq!(reread.chain_id(), CHAIN_ID);
assert_eq!(session.driver.stages().path_count(), 1);
}
#[test]
fn the_shared_path_is_reachable_from_either_named_arm() {
let session = StrategySession::new();
let cfg = arms_config();
let path = session.canonical_path();
assert_eq!(
Settlement::from_config(&cfg).name(),
StrategyName::Settlement
);
assert_eq!(
<TxpoolBackrun as Strategy>::NAME,
StrategyName::TxpoolBackrun
);
assert_eq!(settlement_plan(&path, &cfg).path_id, path.path_id());
assert_eq!(txpool_backrun_plan(&path, &cfg).path_id, path.path_id());
assert!(!format!("{path:?}").contains("engine-cycle"));
assert!(!format!("{path:?}").contains("https://a.local"));
}