Skip to main content

o2_tools/
helpers.rs

1use crate::{
2    CallOption,
3    call_handler_ext::CallHandlerExt,
4    market_data::{
5        OrderData,
6        order_book::Balances,
7    },
8    order_book::{
9        CreateOrderParams,
10        OrderBookManager,
11        OrderType,
12    },
13    order_book_deploy::{
14        OrderBookDeploy,
15        OrderCancelledEvent,
16        OrderCreatedEvent,
17        OrderMatchedEvent,
18        TriggerOrderCreatedEvent,
19        TriggerOrderEjectedEvent,
20    },
21    trade_account::{
22        CallContractArgs,
23        TradeAccountManager,
24    },
25    trade_account_deploy::{
26        DeployConfig,
27        TradeAccountDeploy,
28    },
29};
30use fuels::{
31    prelude::*,
32    programs::responses::CallResponse,
33    types::{
34        Address,
35        Bytes32,
36        ContractId,
37        Identity,
38        tx_status::TxStatus,
39    },
40};
41use std::collections::{
42    HashMap,
43    HashSet,
44};
45
46pub async fn setup_order_book<W: Account + Clone>(
47    deployer_wallet: &W,
48    base_asset: AssetId,
49    quote_asset: AssetId,
50    deploy_config: &crate::order_book_deploy::OrderBookDeployConfig,
51) -> anyhow::Result<OrderBookManager<W>, anyhow::Error> {
52    let order_book_deploy =
53        OrderBookDeploy::deploy(deployer_wallet, base_asset, quote_asset, deploy_config)
54            .await?;
55    let order_book = OrderBookManager::new(deployer_wallet, 9, 9, &order_book_deploy);
56    Ok(order_book)
57}
58
59pub async fn setup_trade_accounts(
60    deployer_wallet: &Wallet,
61    contract_ids: &[ContractId],
62    wallets: &mut Vec<Wallet>,
63) -> anyhow::Result<Vec<TradeAccountManager<Wallet>>, anyhow::Error> {
64    let config = crate::trade_account_deploy::TradeAccountDeployConfig::default();
65    let deploy_config = DeployConfig::Latest(config);
66    let trade_account_deploy: TradeAccountDeploy<Wallet> =
67        TradeAccountDeploy::deploy(deployer_wallet, &deploy_config).await?;
68    let mut trade_accounts = Vec::with_capacity(wallets.len());
69
70    // Create remaining trade accounts
71    for user_wallet in wallets {
72        let deployment = trade_account_deploy
73            .deploy_with_account(
74                &user_wallet.address().into(),
75                &deploy_config,
76                &CallOption::AwaitBlock,
77                None,
78                &[],
79            )
80            .await?;
81        let trade_account = TradeAccountManager::create_with_session(
82            &user_wallet.clone(),
83            &user_wallet.clone(),
84            contract_ids,
85            &deployment,
86            CallOption::AwaitBlock,
87        )
88        .await?;
89        trade_accounts.push(trade_account);
90    }
91
92    Ok(trade_accounts)
93}
94
95pub async fn fund_trade_accounts<W: Account + Clone>(
96    trade_accounts: &[TradeAccountManager<W>],
97    base_asset: AssetId,
98    base_asset_amount: u64,
99    quote_asset: AssetId,
100    quote_asset_amount: u64,
101) -> anyhow::Result<(), anyhow::Error> {
102    for trade_account in trade_accounts.iter() {
103        let _ = trade_account
104            .owner
105            .force_transfer_to_contract(
106                trade_account.contract.contract_id(),
107                base_asset_amount,
108                base_asset,
109                TxPolicies::default(),
110            )
111            .await?;
112        let _ = trade_account
113            .owner
114            .force_transfer_to_contract(
115                trade_account.contract.contract_id(),
116                quote_asset_amount,
117                quote_asset,
118                TxPolicies::default(),
119            )
120            .await?;
121    }
122    Ok(())
123}
124
125pub async fn create_order_call(
126    order_book: &OrderBookManager<Wallet>,
127    order_data: &OrderData,
128    order_type: OrderType,
129    trade_account: &mut TradeAccountManager<Wallet>,
130    gas_per_method: Option<u64>,
131) -> anyhow::Result<CallContractArgs> {
132    let create_order_params = CreateOrderParams {
133        price: order_data.price,
134        quantity: order_data.quantity,
135        side: order_data.side,
136        asset_id: order_book.get_order_side_asset(&order_data.side),
137        order_type,
138    };
139    // Create orders args with signatures
140    let contract_call_args = trade_account
141        .create_order(order_book, &create_order_params, gas_per_method)
142        .await?;
143
144    Ok(contract_call_args)
145}
146
147pub async fn create_order_handlers<'a, I>(
148    order_book: &OrderBookManager<Wallet>,
149    orders: &[OrderData],
150    trade_accounts: I,
151    gas_per_method: Option<u64>,
152) -> anyhow::Result<
153    Vec<CallHandler<Wallet, fuels::programs::calls::ContractCall, ()>>,
154    anyhow::Error,
155>
156where
157    I: Iterator<Item = &'a mut TradeAccountManager<Wallet>>,
158{
159    let mut create_orders_handlers = Vec::with_capacity(orders.len());
160    let mut trade_accounts = trade_accounts.into_iter().collect::<Vec<_>>();
161    for order_data in orders.iter() {
162        let trade_account = trade_accounts
163            .iter_mut()
164            .find(|ta| ta.identity() == order_data.trader_id)
165            .unwrap();
166
167        // Create orders args with signatures
168        let contract_call_args = create_order_call(
169            order_book,
170            order_data,
171            OrderType::Spot,
172            trade_account,
173            gas_per_method,
174        )
175        .await?;
176
177        // Create function handler
178        create_orders_handlers.push(
179            trade_account
180                .session_call_contract(&contract_call_args)
181                .with_contract_ids(&[order_book.contract.contract_id()]),
182        );
183    }
184    Ok(create_orders_handlers)
185}
186
187pub async fn cancel_order_call(
188    order_book: &OrderBookManager<Wallet>,
189    order_id: &Bytes32,
190    trade_account: &mut TradeAccountManager<Wallet>,
191    gas_per_method: Option<u64>,
192) -> anyhow::Result<CallContractArgs> {
193    trade_account
194        .cancel_order(order_book, *order_id, gas_per_method)
195        .await
196}
197
198pub async fn cancel_order_handlers(
199    order_book: &OrderBookManager<Wallet>,
200    orders: &[Bytes32],
201    trade_account: &mut TradeAccountManager<Wallet>,
202    gas_per_method: Option<u64>,
203) -> anyhow::Result<
204    Vec<CallHandler<Wallet, fuels::programs::calls::ContractCall, ()>>,
205    anyhow::Error,
206> {
207    let mut cancel_orders_handlers = Vec::with_capacity(orders.len());
208    for order_id in orders.iter() {
209        let contract_call_args =
210            cancel_order_call(order_book, order_id, trade_account, gas_per_method)
211                .await?;
212
213        // Create function handler
214        cancel_orders_handlers
215            .push(trade_account.session_call_contract(&contract_call_args));
216    }
217    Ok(cancel_orders_handlers)
218}
219
220pub async fn send_transactions(
221    create_orders_handlers: Vec<
222        CallHandler<Wallet, fuels::programs::calls::ContractCall, ()>,
223    >,
224    gaspayer_wallet: Wallet,
225    call_option: CallOption,
226) -> Vec<Bytes32> {
227    let mut order_results = Vec::with_capacity(create_orders_handlers.len());
228    for mut call_handler in create_orders_handlers {
229        call_handler.account = gaspayer_wallet.clone();
230
231        let tx_id = match call_option.clone() {
232            CallOption::AwaitBlock => call_handler.submit().await.unwrap().tx_id(),
233            CallOption::AwaitPreconfirmation(ops) => {
234                call_handler
235                    .almost_sync_call(
236                        &ops.data_builder,
237                        &ops.utxo_manager,
238                        &ops.tx_config,
239                        None,
240                        &[],
241                    )
242                    .await
243                    .unwrap()
244                    .tx_id
245            }
246        };
247        order_results.push(tx_id);
248    }
249    order_results
250}
251
252#[derive(Debug, Clone, Default)]
253pub struct OrderBookEvents {
254    pub matches: Vec<OrderMatchedEvent>,
255    pub orders: Vec<OrderCreatedEvent>,
256    pub cancels: Vec<OrderCancelledEvent>,
257    pub trigger_orders: Vec<TriggerOrderCreatedEvent>,
258    pub trigger_ejections: Vec<TriggerOrderEjectedEvent>,
259}
260
261pub fn get_order_book_events<W: Account + Clone>(
262    order_book: &OrderBookManager<W>,
263    order_book_events: &mut OrderBookEvents,
264    tx_result: &TxStatus,
265) -> anyhow::Result<(), anyhow::Error> {
266    if let TxStatus::Success(success) = tx_result {
267        let mut order_created_events =
268            order_book
269                .contract
270                .log_decoder()
271                .decode_logs_with_type::<OrderCreatedEvent>(&success.receipts)?;
272        let mut order_match_events =
273            order_book
274                .contract
275                .log_decoder()
276                .decode_logs_with_type::<OrderMatchedEvent>(&success.receipts)?;
277        let mut order_cancel_events =
278            order_book
279                .contract
280                .log_decoder()
281                .decode_logs_with_type::<OrderCancelledEvent>(&success.receipts)?;
282
283        let mut trigger_created_events =
284            order_book
285                .contract
286                .log_decoder()
287                .decode_logs_with_type::<TriggerOrderCreatedEvent>(&success.receipts)?;
288        let mut trigger_ejected_events =
289            order_book
290                .contract
291                .log_decoder()
292                .decode_logs_with_type::<TriggerOrderEjectedEvent>(&success.receipts)?;
293
294        order_book_events.orders.append(&mut order_created_events);
295        order_book_events.matches.append(&mut order_match_events);
296        order_book_events.cancels.append(&mut order_cancel_events);
297        order_book_events
298            .trigger_orders
299            .append(&mut trigger_created_events);
300        order_book_events
301            .trigger_ejections
302            .append(&mut trigger_ejected_events);
303    }
304    Ok(())
305}
306
307pub async fn get_wallets_balances<W: Account + Clone>(
308    wallets: &[W],
309    base_asset: AssetId,
310    quote_asset: AssetId,
311) -> anyhow::Result<HashMap<Address, (u128, u128)>, anyhow::Error> {
312    let mut balances = HashMap::new();
313    for wallet in wallets {
314        let balance = wallet.get_balances().await?;
315        let base_asset_balance =
316            balance.get(&base_asset.to_string()).cloned().unwrap_or(0);
317        let quote_asset_balance =
318            balance.get(&quote_asset.to_string()).cloned().unwrap_or(0);
319        balances.insert(wallet.address(), (base_asset_balance, quote_asset_balance));
320    }
321    Ok(balances)
322}
323
324pub async fn get_contracts_balances(
325    provider: &Provider,
326    contracts: &[ContractId],
327    base_asset: &AssetId,
328    quote_asset: &AssetId,
329) -> anyhow::Result<Balances, anyhow::Error> {
330    let mut balances = Balances::new();
331    for contract_id in contracts {
332        let balance = provider.get_contract_balances(contract_id).await?;
333        let base_asset_balance = balance.get(&base_asset.clone()).cloned().unwrap_or(0);
334        let quote_asset_balance = balance.get(&quote_asset.clone()).cloned().unwrap_or(0);
335        balances.insert(
336            Identity::ContractId(*contract_id),
337            (base_asset_balance, quote_asset_balance),
338        );
339    }
340    Ok(balances)
341}
342
343pub async fn settle_trade_accounts_balances<'a, I>(
344    fee_payer: &Wallet,
345    order_book: &OrderBookManager<Wallet>,
346    trade_accounts: I,
347    call_option: CallOption,
348) -> anyhow::Result<CallResponse<()>, anyhow::Error>
349where
350    I: Iterator<Item = &'a TradeAccountManager<Wallet>>,
351{
352    let mut accounts = vec![];
353    let mut contracts = vec![];
354    for account in trade_accounts {
355        accounts.push(Identity::from(account.contract.contract_id()));
356        contracts.push(account.contract.contract_id());
357    }
358
359    let mut call_handler = order_book
360        .contract
361        .methods()
362        .settle_balances(accounts)
363        .with_contract_ids(&contracts);
364    call_handler.account = fee_payer.clone();
365
366    let result = match call_option {
367        CallOption::AwaitBlock => call_handler.call().await?,
368        CallOption::AwaitPreconfirmation(ops) => {
369            call_handler
370                .almost_sync_call(
371                    &ops.data_builder,
372                    &ops.utxo_manager,
373                    &ops.tx_config,
374                    None,
375                    &[],
376                )
377                .await?
378                .tx_status?
379        }
380    };
381
382    Ok(result)
383}
384
385pub async fn get_trade_accounts_balances<W: Account + Clone>(
386    provider: &Provider,
387    trade_accounts: &[TradeAccountManager<W>],
388    base_asset: &AssetId,
389    quote_asset: &AssetId,
390) -> anyhow::Result<Balances, anyhow::Error> {
391    let contracts = trade_accounts
392        .iter()
393        .map(|trade_account| trade_account.contract.contract_id())
394        .collect::<Vec<_>>();
395    get_contracts_balances(provider, &contracts, base_asset, quote_asset).await
396}
397
398pub async fn wait_for_book_events<W: Account + Clone>(
399    tx_ids: &[Bytes32],
400    trade_account: &TradeAccountManager<W>,
401    order_book: &OrderBookManager<W>,
402    gaspayer_wallet: W,
403) -> anyhow::Result<OrderBookEvents, anyhow::Error> {
404    let mut order_book_events = OrderBookEvents::default();
405    let mut tx_completed = HashSet::new();
406
407    while tx_completed.len() != tx_ids.len() {
408        let provider = gaspayer_wallet.try_provider()?;
409        for order_result in tx_ids.iter() {
410            let result = provider.get_transaction_by_id(order_result).await?;
411
412            if let Some(result) = result {
413                get_order_book_events(
414                    order_book,
415                    &mut order_book_events,
416                    &result.status,
417                )?;
418                match result.status {
419                    TxStatus::Success(_) => {
420                        tx_completed.insert(*order_result);
421                    }
422                    TxStatus::Failure(failure) => {
423                        let logs = order_book
424                            .contract
425                            .log_decoder()
426                            .decode_logs(&failure.receipts);
427                        let logs_trade = trade_account
428                            .contract
429                            .log_decoder()
430                            .decode_logs(&failure.receipts);
431                        println!("{logs:#?}");
432                        println!("{logs_trade:#?}");
433                        panic!("{:#}", failure.reason);
434                    }
435                    _ => {
436                        continue;
437                    }
438                }
439            }
440        }
441    }
442
443    Ok(order_book_events)
444}
445
446pub fn get_asset_balance(balances: &HashMap<String, u128>, asset_id: &AssetId) -> u128 {
447    *balances.get(&asset_id.to_string()).unwrap_or(&0)
448}
449
450pub fn get_total_amount(quantity: u64, price: u64, decimals: u64) -> u64 {
451    (quantity * price) / 10u64.pow(decimals as u32)
452}