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 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 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 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_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 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("e_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("e_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}