o2-deploy 0.4.7-rc5

Contract deployment logic for Fuel O2 exchange
use super::*;

fn pair() -> OrderBookConfig {
    let mut config: MarketsConfigPartial =
        serde_json::from_str(include_str!("../tests/fixtures/sidecar-markets.json"))
            .unwrap();
    let mut pair = config.pairs.remove(0);
    pair.contract_id = Some(ContractId::new([1; 32]));
    pair.sidecar.as_mut().unwrap().contract_id = Some(ContractId::new([2; 32]));
    pair
}

#[test]
fn sidecar_selection_survives_config_roundtrip() {
    let mut json: serde_json::Value =
        serde_json::from_str(super::tests::LIVE_LEGACY_TIER).unwrap();
    json["sidecar"] = serde_json::json!(true);
    let tier: MarginTierConfig = serde_json::from_value(json).unwrap();
    assert!(tier.sidecar);
    let copy: MarginTierConfig =
        serde_json::from_value(serde_json::to_value(tier).unwrap()).unwrap();
    assert!(copy.sidecar);
}

#[test]
fn markets_resolve_to_the_selected_venue_by_symbol_or_market_id() {
    let pair = pair();
    let names = [
        "BTC/USDT".to_owned(),
        " btc/usdt ".to_owned(),
        hex::encode(*pair.market_id),
        format!("0x{}", hex::encode(*pair.market_id)),
    ];
    for name in names {
        for (sidecar, expected) in [
            (false, pair.contract_id.unwrap()),
            (true, pair.sidecar.as_ref().unwrap().contract_id.unwrap()),
        ] {
            assert_eq!(
                resolve_tier_market(&name, std::slice::from_ref(&pair), sidecar).unwrap(),
                expected,
            );
        }
    }
}

#[test]
fn missing_sidecars_never_fall_back_to_canonical() {
    let mut pair = pair();
    pair.sidecar.as_mut().unwrap().contract_id = None;
    for sidecar in [pair.sidecar.clone(), None] {
        pair.sidecar = sidecar;
        let err = resolve_tier_market("BTC/USDT", std::slice::from_ref(&pair), true)
            .unwrap_err();
        assert!(err.to_string().contains("BTC/USDT"), "{err}");
        assert!(err.to_string().contains("no deployed sidecar"), "{err}");
        assert_eq!(
            resolve_tier_market("BTC/USDT", std::slice::from_ref(&pair), false).unwrap(),
            pair.contract_id.unwrap(),
        );
    }
}

#[test]
fn unknown_markets_and_missing_canonical_books_are_rejected() {
    let mut pair = pair();
    for sidecar in [false, true] {
        let err =
            resolve_tier_market("UNKNOWN/USDT", std::slice::from_ref(&pair), sidecar)
                .unwrap_err();
        assert!(err.to_string().contains("unknown market"), "{err}");
    }
    pair.contract_id = None;
    let err = resolve_tier_market("BTC/USDT", &[pair], false).unwrap_err();
    assert!(err.to_string().contains("no deployed order book"), "{err}");
}

#[test]
fn switching_venues_replaces_the_allowlist_in_both_directions() {
    let pair = pair();
    let canonical = pair.contract_id.unwrap();
    let house = pair.sidecar.as_ref().unwrap().contract_id.unwrap();
    let pairs = [pair];
    assert!(tier_book_route_changed(true, &pairs, &[canonical]));
    assert!(tier_book_route_changed(false, &pairs, &[house]));
    assert!(tier_book_route_changed(true, &pairs, &[canonical, house]));
    assert!(tier_book_route_changed(false, &pairs, &[canonical, house]));
    assert!(!tier_book_route_changed(false, &pairs, &[canonical]));
    assert!(!tier_book_route_changed(true, &pairs, &[house]));
    assert!(!tier_book_route_changed(true, &pairs, &[]));
}