o2-tools 0.1.11

Reusable tooling for trade account and order book contract interactions on Fuel
Documentation
use crate::{
    helpers::get_total_amount,
    market_data::{
        OrderData,
        order_book::MatchedEvent,
    },
    order_book_deploy::{
        OrderCreatedEvent,
        OrderMatchedEvent,
    },
};
use o2_api_types::primitives::Side;

pub fn assert_orders(orders_events: &[OrderCreatedEvent], orders_state: &[OrderData]) {
    assert_eq!(
        orders_events
            .iter()
            .map(|order| (
                Side::from(format!("{:?}", order.order_side)),
                order.price,
                order.quantity,
                order.trader_id
            ))
            .collect::<Vec<_>>(),
        orders_state
            .iter()
            .map(|order| (order.side, order.price, order.quantity, order.trader_id))
            .collect::<Vec<_>>()
    )
}

pub fn assert_matches(
    orders_events: &[OrderMatchedEvent],
    orders_state: &[MatchedEvent],
    base_decimals: u64,
) {
    assert_eq!(
        orders_events
            .iter()
            .map(|trade| (
                trade.quantity,
                trade.price,
                get_total_amount(trade.quantity, trade.price, base_decimals)
            ))
            .collect::<Vec<_>>(),
        orders_state
            .iter()
            .map(|trade| (trade.quantity, trade.price, trade.total))
            .collect::<Vec<_>>()
    )
}

#[cfg(test)]
mod tests {
    use crate::{
        CallOption,
        e2e::{
            assert_matches,
            assert_orders,
        },
        helpers::{
            create_order_handlers,
            fund_trade_accounts,
            get_trade_accounts_balances,
            send_transactions,
            settle_trade_accounts_balances,
            setup_order_book,
            setup_trade_accounts,
            wait_for_book_events,
        },
        market_data::{
            MarketData,
            OrderBookAction,
            OrderData,
            order_book::get_default_orders_test,
        },
        order_book::Side,
        order_book_deploy::{
            OrderBookConfigurables,
            OrderBookDeployConfig,
        },
    };
    use fuels::{
        prelude::*,
        test_helpers::{
            WalletsConfig,
            launch_custom_provider_and_get_wallets,
        },
    };

    #[tokio::test]
    #[ignore = "@FIX: this test is been flaky on ci so ignoring for now"]
    async fn test_e2e_book() {
        let base_asset = AssetId::from([1; 32]);
        let quote_asset = AssetId::from([2; 32]);
        let initial_balance = 1_000_000_000_000_000u64;
        let gas_per_method = Some(1_000_000);

        // Start fuel-core
        let mut wallets = launch_custom_provider_and_get_wallets(
            WalletsConfig::new_multiple_assets(
                4,
                vec![
                    AssetConfig {
                        id: AssetId::default(),
                        num_coins: 2,
                        coin_amount: initial_balance,
                    },
                    AssetConfig {
                        id: base_asset,
                        num_coins: 2,
                        coin_amount: initial_balance,
                    },
                    AssetConfig {
                        id: quote_asset,
                        num_coins: 2,
                        coin_amount: initial_balance,
                    },
                ],
            ),
            None,
            None,
        )
        .await
        .unwrap();
        let deployer_wallet = wallets.pop().unwrap();
        let gaspayer_wallet = wallets.pop().unwrap();
        let provider = deployer_wallet.provider().clone();
        let order_book_configurables = OrderBookConfigurables::default()
            .with_MAKER_FEE(0.into())
            .unwrap()
            .with_TAKER_FEE(0.into())
            .unwrap()
            .with_MIN_ORDER(0)
            .unwrap()
            .with_DUST(0)
            .unwrap();
        let order_book = setup_order_book(
            &deployer_wallet,
            base_asset,
            quote_asset,
            &OrderBookDeployConfig::with_configurables(order_book_configurables),
        )
        .await
        .unwrap();

        let mut trade_accounts = setup_trade_accounts(
            &deployer_wallet,
            &[order_book.contract.id()],
            &mut wallets,
        )
        .await
        .unwrap();

        println!("Order book deployed: {}", order_book.contract.contract_id());

        fund_trade_accounts(
            &trade_accounts,
            base_asset,
            2_000_000_000_000u64,
            quote_asset,
            2_000_000_000_000u64,
        )
        .await
        .unwrap();

        let balances = get_trade_accounts_balances(
            &provider,
            &trade_accounts,
            &base_asset,
            &quote_asset,
        )
        .await
        .unwrap();
        let (order_book_state, test_order_book_state) =
            get_default_orders_test(base_asset, quote_asset, balances);
        // Create order handlers
        let create_orders_handlers = create_order_handlers(
            &order_book,
            &test_order_book_state.orders,
            trade_accounts.iter_mut(),
            gas_per_method,
        )
        .await
        .unwrap();
        // Execte orders in batches
        let tx_ids = send_transactions(
            create_orders_handlers,
            gaspayer_wallet.clone(),
            CallOption::AwaitBlock,
        )
        .await;
        // Get receipts from the results
        let trade_account = trade_accounts.first().unwrap();
        let order_book_events = wait_for_book_events(
            &tx_ids,
            trade_account,
            &order_book,
            gaspayer_wallet.clone(),
        )
        .await
        .unwrap();

        assert_orders(&order_book_events.orders, &test_order_book_state.orders);
        assert_matches(
            &order_book_events.matches,
            &test_order_book_state.matches,
            9,
        );
        settle_trade_accounts_balances(
            &gaspayer_wallet,
            &order_book,
            trade_accounts.iter(),
            CallOption::AwaitBlock,
        )
        .await
        .unwrap();

        let balances = get_trade_accounts_balances(
            &provider,
            &trade_accounts,
            &base_asset,
            &quote_asset,
        )
        .await
        .unwrap();
        assert_eq!(balances, order_book_state.balances);
    }

    #[tokio::test]
    #[ignore = "@FIX: this test is been flaky on ci so ignoring for now"]
    async fn test_e2e_book_multiple_orders() {
        let base_asset = AssetId::from([1; 32]);
        let quote_asset = AssetId::from([2; 32]);
        let initial_balance = 1_000_000_000_000_000u64;
        let gas_per_method = Some(30_000_000);

        // Start fuel-core
        let mut wallets = launch_custom_provider_and_get_wallets(
            WalletsConfig::new_multiple_assets(
                6,
                vec![
                    AssetConfig {
                        id: AssetId::zeroed(),
                        num_coins: 2,
                        coin_amount: initial_balance,
                    },
                    AssetConfig {
                        id: base_asset,
                        num_coins: 2,
                        coin_amount: initial_balance,
                    },
                    AssetConfig {
                        id: quote_asset,
                        num_coins: 2,
                        coin_amount: initial_balance,
                    },
                ],
            ),
            None,
            None,
        )
        .await
        .unwrap();
        let deployer_wallet = wallets.pop().unwrap();
        let gaspayer_wallet = wallets.pop().unwrap();
        let order_book_configurables = OrderBookConfigurables::default()
            .with_MAKER_FEE(0.into())
            .unwrap()
            .with_TAKER_FEE(0.into())
            .unwrap()
            .with_MIN_ORDER(0)
            .unwrap()
            .with_DUST(0)
            .unwrap();
        let order_book = setup_order_book(
            &deployer_wallet,
            base_asset,
            quote_asset,
            &OrderBookDeployConfig::with_configurables(order_book_configurables),
        )
        .await
        .unwrap();

        let mut trade_accounts = setup_trade_accounts(
            &deployer_wallet,
            &[order_book.contract.id()],
            &mut wallets,
        )
        .await
        .unwrap();

        fund_trade_accounts(
            &trade_accounts,
            base_asset,
            2_000_000_000_000u64,
            quote_asset,
            2_000_000_000_000u64,
        )
        .await
        .unwrap();

        let order_actions = vec![
            (false, 469_000_000, 115_000_000),
            (true, 261_000_000, 113_000_000),
            (true, 869_000_000, 193_000_000),
            (true, 791_000_000, 137_000_000),
            (true, 285_000_000, 182_000_000),
            (false, 121_000_000, 136_000_000),
            (true, 103_000_000, 126_000_000),
            (false, 44_000_000, 174_000_000),
            (false, 918_000_000, 190_000_000),
            (false, 697_000_000, 149_000_000),
            (true, 716_000_000, 126_000_000),
            (true, 397_000_000, 177_000_000),
            (true, 490_000_000, 117_000_000),
            (false, 159_000_000, 191_000_000),
            (false, 134_000_000, 173_000_000),
            (false, 356_000_000, 127_000_000),
        ]
        .iter()
        .enumerate()
        .map(|(index, (is_bid, price, quantity))| {
            OrderBookAction::CreateOrder(OrderData {
                trader_id: trade_accounts
                    .get(index % trade_accounts.len())
                    .unwrap()
                    .identity(),
                price: *price,
                quantity: *quantity,
                side: if *is_bid { Side::Sell } else { Side::Buy },
            })
        })
        .collect::<Vec<OrderBookAction>>();
        let order_book_state =
            MarketData::generate_orderbook_state(base_asset, quote_asset, &order_actions);
        let orders = order_actions
            .iter()
            .map(|action| match action {
                OrderBookAction::CreateOrder(order) => order.clone(),
                OrderBookAction::CancelOrder(_) => {
                    panic!("Unexpected cancel order action")
                }
            })
            .collect::<Vec<OrderData>>();

        // Create order handlers
        let create_orders_handlers = create_order_handlers(
            &order_book,
            &orders,
            trade_accounts.iter_mut(),
            gas_per_method,
        )
        .await
        .unwrap();
        // Execte orders in batches
        let tx_ids = send_transactions(
            create_orders_handlers,
            gaspayer_wallet.clone(),
            CallOption::AwaitBlock,
        )
        .await;
        // Get receipts from the results
        let trade_account = trade_accounts.first().unwrap();
        let order_book_events =
            wait_for_book_events(&tx_ids, trade_account, &order_book, gaspayer_wallet)
                .await
                .unwrap();

        assert_orders(&order_book_events.orders, &orders);
        assert_matches(&order_book_events.matches, &order_book_state.trades, 9);
    }
}