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