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