use crate::{
CallOption,
call_handler_ext::CallHandlerExt,
market_data::{
OrderData,
order_book::Balances,
},
order_book::{
CreateOrderParams,
OrderBookManager,
OrderType,
},
order_book_deploy::{
OrderBookDeploy,
OrderCancelledEvent,
OrderCreatedEvent,
OrderMatchedEvent,
},
trade_account::{
CallContractArgs,
TradeAccountManager,
},
trade_account_deploy::{
DeployConfig,
TradeAccountDeploy,
},
};
use fuels::{
prelude::*,
programs::responses::CallResponse,
types::{
Address,
Bytes32,
ContractId,
Identity,
tx_status::TxStatus,
},
};
use std::collections::{
HashMap,
HashSet,
};
pub async fn setup_order_book<W: Account + Clone>(
deployer_wallet: &W,
base_asset: AssetId,
quote_asset: AssetId,
deploy_config: &crate::order_book_deploy::OrderBookDeployConfig,
) -> anyhow::Result<OrderBookManager<W>, anyhow::Error> {
let order_book_deploy =
OrderBookDeploy::deploy(deployer_wallet, base_asset, quote_asset, deploy_config)
.await?;
let order_book = OrderBookManager::new(deployer_wallet, 9, 9, &order_book_deploy);
Ok(order_book)
}
pub async fn setup_trade_accounts(
deployer_wallet: &Wallet,
contract_ids: &[ContractId],
wallets: &mut Vec<Wallet>,
) -> anyhow::Result<Vec<TradeAccountManager<Wallet>>, anyhow::Error> {
let config = crate::trade_account_deploy::TradeAccountDeployConfig::default();
let deploy_config = DeployConfig::Latest(config);
let trade_account_deploy: TradeAccountDeploy<Wallet> =
TradeAccountDeploy::deploy(deployer_wallet, &deploy_config).await?;
let mut trade_accounts = Vec::with_capacity(wallets.len());
for user_wallet in wallets {
let deployment = trade_account_deploy
.deploy_with_account(
&user_wallet.address().into(),
&deploy_config,
&CallOption::AwaitBlock,
)
.await?;
let trade_account = TradeAccountManager::create_with_session(
&user_wallet.clone(),
&user_wallet.clone(),
contract_ids,
&deployment,
CallOption::AwaitBlock,
)
.await?;
trade_accounts.push(trade_account);
}
Ok(trade_accounts)
}
pub async fn fund_trade_accounts<W: Account + Clone>(
trade_accounts: &[TradeAccountManager<W>],
base_asset: AssetId,
base_asset_amount: u64,
quote_asset: AssetId,
quote_asset_amount: u64,
) -> anyhow::Result<(), anyhow::Error> {
for trade_account in trade_accounts.iter() {
let _ = trade_account
.owner
.force_transfer_to_contract(
trade_account.contract.contract_id(),
base_asset_amount,
base_asset,
TxPolicies::default(),
)
.await?;
let _ = trade_account
.owner
.force_transfer_to_contract(
trade_account.contract.contract_id(),
quote_asset_amount,
quote_asset,
TxPolicies::default(),
)
.await?;
}
Ok(())
}
pub async fn create_order_call(
order_book: &OrderBookManager<Wallet>,
order_data: &OrderData,
order_type: OrderType,
trade_account: &mut TradeAccountManager<Wallet>,
gas_per_method: Option<u64>,
) -> anyhow::Result<CallContractArgs> {
let create_order_params = CreateOrderParams {
price: order_data.price,
quantity: order_data.quantity,
side: order_data.side,
asset_id: order_book.get_order_side_asset(&order_data.side),
order_type,
};
let contract_call_args = trade_account
.create_order(order_book, &create_order_params, gas_per_method)
.await?;
Ok(contract_call_args)
}
pub async fn create_order_handlers<'a, I>(
order_book: &OrderBookManager<Wallet>,
orders: &[OrderData],
trade_accounts: I,
gas_per_method: Option<u64>,
) -> anyhow::Result<
Vec<CallHandler<Wallet, fuels::programs::calls::ContractCall, ()>>,
anyhow::Error,
>
where
I: Iterator<Item = &'a mut TradeAccountManager<Wallet>>,
{
let mut create_orders_handlers = Vec::with_capacity(orders.len());
let mut trade_accounts = trade_accounts.into_iter().collect::<Vec<_>>();
for order_data in orders.iter() {
let trade_account = trade_accounts
.iter_mut()
.find(|ta| ta.identity() == order_data.trader_id)
.unwrap();
let contract_call_args = create_order_call(
order_book,
order_data,
OrderType::Spot,
trade_account,
gas_per_method,
)
.await?;
create_orders_handlers.push(
trade_account
.session_call_contract(&contract_call_args)
.with_contract_ids(&[order_book.contract.contract_id()]),
);
}
Ok(create_orders_handlers)
}
pub async fn cancel_order_call(
order_book: &OrderBookManager<Wallet>,
order_id: &Bytes32,
trade_account: &mut TradeAccountManager<Wallet>,
gas_per_method: Option<u64>,
) -> anyhow::Result<CallContractArgs> {
trade_account
.cancel_order(order_book, *order_id, gas_per_method)
.await
}
pub async fn cancel_order_handlers(
order_book: &OrderBookManager<Wallet>,
orders: &[Bytes32],
trade_account: &mut TradeAccountManager<Wallet>,
gas_per_method: Option<u64>,
) -> anyhow::Result<
Vec<CallHandler<Wallet, fuels::programs::calls::ContractCall, ()>>,
anyhow::Error,
> {
let mut cancel_orders_handlers = Vec::with_capacity(orders.len());
for order_id in orders.iter() {
let contract_call_args =
cancel_order_call(order_book, order_id, trade_account, gas_per_method)
.await?;
cancel_orders_handlers
.push(trade_account.session_call_contract(&contract_call_args));
}
Ok(cancel_orders_handlers)
}
pub async fn send_transactions(
create_orders_handlers: Vec<
CallHandler<Wallet, fuels::programs::calls::ContractCall, ()>,
>,
gaspayer_wallet: Wallet,
call_option: CallOption,
) -> Vec<Bytes32> {
let mut order_results = Vec::with_capacity(create_orders_handlers.len());
for mut call_handler in create_orders_handlers {
call_handler.account = gaspayer_wallet.clone();
let tx_id = match call_option.clone() {
CallOption::AwaitBlock => call_handler.submit().await.unwrap().tx_id(),
CallOption::AwaitPreconfirmation(ops) => {
call_handler
.almost_sync_call(
&ops.data_builder,
&ops.utxo_manager,
&ops.tx_config,
)
.await
.unwrap()
.tx_id
}
};
order_results.push(tx_id);
}
order_results
}
#[derive(Debug, Clone, Default)]
pub struct OrderBookEvents {
pub matches: Vec<OrderMatchedEvent>,
pub orders: Vec<OrderCreatedEvent>,
pub cancels: Vec<OrderCancelledEvent>,
}
pub fn get_order_book_events<W: Account + Clone>(
order_book: &OrderBookManager<W>,
order_book_events: &mut OrderBookEvents,
tx_result: &TxStatus,
) -> anyhow::Result<(), anyhow::Error> {
if let TxStatus::Success(success) = tx_result {
let mut order_created_events =
order_book
.contract
.log_decoder()
.decode_logs_with_type::<OrderCreatedEvent>(&success.receipts)?;
let mut order_match_events =
order_book
.contract
.log_decoder()
.decode_logs_with_type::<OrderMatchedEvent>(&success.receipts)?;
let mut order_cancel_events =
order_book
.contract
.log_decoder()
.decode_logs_with_type::<OrderCancelledEvent>(&success.receipts)?;
order_book_events.orders.append(&mut order_created_events);
order_book_events.matches.append(&mut order_match_events);
order_book_events.cancels.append(&mut order_cancel_events);
}
Ok(())
}
pub async fn get_wallets_balances<W: Account + Clone>(
wallets: &[W],
base_asset: AssetId,
quote_asset: AssetId,
) -> anyhow::Result<HashMap<Address, (u128, u128)>, anyhow::Error> {
let mut balances = HashMap::new();
for wallet in wallets {
let balance = wallet.get_balances().await?;
let base_asset_balance =
balance.get(&base_asset.to_string()).cloned().unwrap_or(0);
let quote_asset_balance =
balance.get("e_asset.to_string()).cloned().unwrap_or(0);
balances.insert(wallet.address(), (base_asset_balance, quote_asset_balance));
}
Ok(balances)
}
pub async fn get_contracts_balances(
provider: &Provider,
contracts: &[ContractId],
base_asset: &AssetId,
quote_asset: &AssetId,
) -> anyhow::Result<Balances, anyhow::Error> {
let mut balances = Balances::new();
for contract_id in contracts {
let balance = provider.get_contract_balances(contract_id).await?;
let base_asset_balance = balance.get(&base_asset.clone()).cloned().unwrap_or(0);
let quote_asset_balance = balance.get("e_asset.clone()).cloned().unwrap_or(0);
balances.insert(
Identity::ContractId(*contract_id),
(base_asset_balance, quote_asset_balance),
);
}
Ok(balances)
}
pub async fn settle_trade_accounts_balances<'a, I>(
fee_payer: &Wallet,
order_book: &OrderBookManager<Wallet>,
trade_accounts: I,
call_option: CallOption,
) -> anyhow::Result<CallResponse<()>, anyhow::Error>
where
I: Iterator<Item = &'a TradeAccountManager<Wallet>>,
{
let mut accounts = vec![];
let mut contracts = vec![];
for account in trade_accounts {
accounts.push(Identity::from(account.contract.contract_id()));
contracts.push(account.contract.contract_id());
}
let mut call_handler = order_book
.contract
.methods()
.settle_balances(accounts)
.with_contract_ids(&contracts);
call_handler.account = fee_payer.clone();
let result = match call_option {
CallOption::AwaitBlock => call_handler.call().await?,
CallOption::AwaitPreconfirmation(ops) => {
call_handler
.almost_sync_call(&ops.data_builder, &ops.utxo_manager, &ops.tx_config)
.await?
.tx_status?
}
};
Ok(result)
}
pub async fn get_trade_accounts_balances<W: Account + Clone>(
provider: &Provider,
trade_accounts: &[TradeAccountManager<W>],
base_asset: &AssetId,
quote_asset: &AssetId,
) -> anyhow::Result<Balances, anyhow::Error> {
let contracts = trade_accounts
.iter()
.map(|trade_account| trade_account.contract.contract_id())
.collect::<Vec<_>>();
get_contracts_balances(provider, &contracts, base_asset, quote_asset).await
}
pub async fn wait_for_book_events<W: Account + Clone>(
tx_ids: &[Bytes32],
trade_account: &TradeAccountManager<W>,
order_book: &OrderBookManager<W>,
gaspayer_wallet: W,
) -> anyhow::Result<OrderBookEvents, anyhow::Error> {
let mut order_book_events = OrderBookEvents::default();
let mut tx_completed = HashSet::new();
while tx_completed.len() != tx_ids.len() {
let provider = gaspayer_wallet.try_provider()?;
for order_result in tx_ids.iter() {
let result = provider.get_transaction_by_id(order_result).await?;
if let Some(result) = result {
get_order_book_events(
order_book,
&mut order_book_events,
&result.status,
)?;
match result.status {
TxStatus::Success(_) => {
tx_completed.insert(*order_result);
}
TxStatus::Failure(failure) => {
let logs = order_book
.contract
.log_decoder()
.decode_logs(&failure.receipts);
let logs_trade = trade_account
.contract
.log_decoder()
.decode_logs(&failure.receipts);
println!("{logs:#?}");
println!("{logs_trade:#?}");
panic!("{:#}", failure.reason);
}
_ => {
continue;
}
}
}
}
}
Ok(order_book_events)
}
pub fn get_asset_balance(balances: &HashMap<String, u128>, asset_id: &AssetId) -> u128 {
*balances.get(&asset_id.to_string()).unwrap_or(&0)
}
pub fn get_total_amount(quantity: u64, price: u64, decimals: u64) -> u64 {
(quantity * price) / 10u64.pow(decimals as u32)
}