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