Skip to content

Commit 9ce81d1

Browse files
fix: check contract balance before deposit (#3416)
* fix: check contract balance before deposit * refactor: cargo fmt * refactor: cargo clippy * refactor: pr comments * Fix bnb_to_receive and payout_amount (#3421) * refactor: rename bnb_received to bnb_acquired Renamed the variable `bnb_received` to `bnb_acquired` to better reflect its purpose. This variable represents the amount of BNB resulting from a trade executed via the Binance spot trading API, which is held in the service's Binance account and not yet transferred to the user's account. * fix(cross-chain): assign acquired BNB to bnb_to_receive Set `bnb_to_receive` to `bnb_acquired` after successful acquisition, ensuring the correct value is used in subsequent logic. --------- Co-authored-by: Francisco Silva <franjs.francisco@gmail.com>
1 parent 72ce27e commit 9ce81d1

File tree

2 files changed

+129
-67
lines changed
  • tee-worker/omni-executor
    • accounting-contract-client/src
    • intent/executors/cross-chain/src

2 files changed

+129
-67
lines changed

tee-worker/omni-executor/accounting-contract-client/src/lib.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,18 @@ impl<P: RpcProvider<Transaction = TransactionRequest>> AccountingContractClient<
5656
Ok(nonce)
5757
})
5858
}
59+
60+
pub async fn get_balance(&self) -> Result<U256, ()> {
61+
let call =
62+
AccountingContractCalls::getBalance(AccountingContract::getBalanceCall {}).abi_encode();
63+
let tx = TransactionRequest {
64+
to: Some(TxKind::Call(self.contract_address)),
65+
input: TransactionInput { data: Some(call.into()), ..Default::default() },
66+
..Default::default()
67+
};
68+
self.provider.call(tx).await.map_or(Err(()), |balance| {
69+
let balance = U256::abi_decode(&balance, true).unwrap();
70+
Ok(balance)
71+
})
72+
}
5973
}

tee-worker/omni-executor/intent/executors/cross-chain/src/lib.rs

Lines changed: 115 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,54 @@ impl<Provider: EthereumRpcProvider<Transaction = TransactionRequest> + Send + Sy
603603
return Err(());
604604
};
605605

606+
let (trade_symbol, order_side) = match binance_coin_name.as_str() {
607+
"USDC" => ("BNBUSDC".to_string(), BinanceOrderSide::BUY),
608+
"USDT" => ("BNBUSDT".to_string(), BinanceOrderSide::BUY),
609+
"SOL" => ("SOLBNB".to_string(), BinanceOrderSide::SELL),
610+
_ => {
611+
log::error!("Unsupported asset: {:?}", binance_coin_name);
612+
return Err(());
613+
},
614+
};
615+
616+
let estimated_bnb_receive = estimate_bnb_amount(
617+
&self.binance_api,
618+
&trade_symbol,
619+
&binance_coin_name,
620+
from_amount_decimal,
621+
)
622+
.await?;
623+
let mut payout_amount = match str_to_u256(&estimated_bnb_receive, 18) {
624+
Some(a) => a,
625+
None => {
626+
log::error!(
627+
"Fail to convert bnb amount {} to U256",
628+
estimated_bnb_receive
629+
);
630+
let body = CrossFailBody {
631+
request_id: intent_id,
632+
fail_reason:
633+
"Fail to construct payout request due to U256 conversion error"
634+
.to_string(),
635+
};
636+
self.pumpx_api.cross_fail(&access_token, body).await.map_err(|_| {
637+
log::error!("Failed to notify pumpx-signer");
638+
})?;
639+
return Err(());
640+
},
641+
};
642+
643+
// Fetch contract balance
644+
let balance = self.accounting_contract_client.get_balance().await?;
645+
if balance < payout_amount {
646+
log::error!(
647+
"There is not enough balance in the accounting contract, {} < {}",
648+
balance,
649+
payout_amount
650+
);
651+
return Err(());
652+
}
653+
606654
let remote_signer = RemoteSigner::new(
607655
self.pumpx_signer_client.clone(),
608656
pumpx_config.wallet_index,
@@ -644,16 +692,6 @@ impl<Provider: EthereumRpcProvider<Transaction = TransactionRequest> + Send + Sy
644692
tx_id = Some(signature);
645693
}
646694

647-
let (trade_symbol, order_side) = match binance_coin_name.as_str() {
648-
"USDC" => ("BNBUSDC".to_string(), BinanceOrderSide::BUY),
649-
"USDT" => ("BNBUSDT".to_string(), BinanceOrderSide::BUY),
650-
"SOL" => ("SOLBNB".to_string(), BinanceOrderSide::SELL),
651-
_ => {
652-
log::error!("Unsupported asset: {:?}", binance_coin_name);
653-
return Err(());
654-
},
655-
};
656-
657695
let mut bnb_to_receive = "".to_string();
658696

659697
if should_wait_for_deposit_confirm {
@@ -797,7 +835,7 @@ impl<Provider: EthereumRpcProvider<Transaction = TransactionRequest> + Send + Sy
797835
// - `executedQty` indicates the amount of the base-asset bought
798836
// - `cummulativeQuoteQty`` shows the total amount of the quote-asset spent
799837
let mut trade_success = false;
800-
let mut bnb_received = "".to_string();
838+
let mut bnb_acquired = "".to_string();
801839
loop {
802840
let trade_order = self
803841
.binance_api
@@ -811,7 +849,7 @@ impl<Provider: EthereumRpcProvider<Transaction = TransactionRequest> + Send + Sy
811849
match trade_order.status {
812850
BinanceOrderStatus::FILLED => {
813851
log::info!("Binance order filled");
814-
bnb_received = match trade_order.side {
852+
bnb_acquired = match trade_order.side {
815853
BinanceOrderSide::BUY => trade_order.executed_qty,
816854
BinanceOrderSide::SELL => trade_order.cummulative_quote_qty,
817855
};
@@ -868,70 +906,41 @@ impl<Provider: EthereumRpcProvider<Transaction = TransactionRequest> + Send + Sy
868906
return Err(());
869907
}
870908

871-
debug!("Total received {} bnb", bnb_received);
872-
} else {
873-
// Estimate BNB payout (simulate spot trade, apply service fee)
874-
let price_str = self
875-
.binance_api
876-
.spot_trading()
877-
.get_symbol_price(&trade_symbol)
878-
.await
879-
.map_err(|_| {
880-
log::error!("Failed to get symbol price for {}", trade_symbol);
881-
})?;
909+
debug!("Total acquired {} bnb", bnb_acquired);
882910

883-
let price = if binance_coin_name == "SOL" {
884-
// For SOL, we sell SOL to get BNB, so get SOLBNB price
885-
Decimal::from_str(&price_str).map_err(|_| {
886-
log::error!("Failed to parse symbol price {}", price_str);
887-
})?
888-
} else {
889-
// For USDC/USDT, we buy BNB with USDC/USDT, so get BNBUSDC/BNBUSDT price and invert
890-
let price = Decimal::from_str(&price_str).map_err(|_| {
891-
log::error!("Failed to parse symbol price {}", price_str);
892-
})?;
893-
if price.is_zero() {
894-
log::error!("Symbol price is zero for {}", trade_symbol);
911+
// We need to recalculate payout amount based on the order filled
912+
payout_amount = match str_to_u256(&bnb_acquired, 18) {
913+
Some(a) => a,
914+
None => {
915+
log::error!(
916+
"Fail to convert bnb amount {} to U256",
917+
bnb_to_receive
918+
);
919+
let body = CrossFailBody {
920+
request_id: intent_id,
921+
fail_reason:
922+
"Fail to construct payout request due to U256 conversion error"
923+
.to_string(),
924+
};
925+
self.pumpx_api.cross_fail(&access_token, body).await.map_err(
926+
|_| {
927+
log::error!("Failed to notify pumpx-signer");
928+
},
929+
)?;
930+
// TODO: what to do with user asset?
895931
return Err(());
896-
}
897-
Decimal::ONE / price
932+
},
898933
};
899934

900-
let bnb_estimated = from_amount_decimal * price;
901-
902-
// Apply 0.1% service fee
903-
let service_fee_rate =
904-
Decimal::from_str("0.001").expect("Failed to parse service fee rate");
905-
let decimal_bnb_to_receive =
906-
bnb_estimated * (Decimal::ONE - service_fee_rate);
907-
bnb_to_receive = decimal_bnb_to_receive.to_string();
908-
909-
debug!(
910-
"BNB estimated: {}, after 0.1% fee: {}",
911-
bnb_estimated, bnb_to_receive
912-
);
935+
bnb_to_receive = bnb_acquired
936+
} else {
937+
// Estimate BNB payout (simulate spot trade, apply service fee)
938+
bnb_to_receive = estimated_bnb_receive;
913939
}
914940

915941
let payout_address = Address::from_str(&to_address).map_err(|_| {
916942
log::error!("Failed to parse payout address");
917943
})?;
918-
let payout_amount = match str_to_u256(&bnb_to_receive, 18) {
919-
Some(a) => a,
920-
None => {
921-
log::error!("Fail to convert bnb amount {} to U256", bnb_to_receive);
922-
let body = CrossFailBody {
923-
request_id: intent_id,
924-
fail_reason:
925-
"Fail to construct payout request due to U256 conversion error"
926-
.to_string(),
927-
};
928-
self.pumpx_api.cross_fail(&access_token, body).await.map_err(|_| {
929-
log::error!("Failed to notify pumpx-signer");
930-
})?;
931-
// TODO: what to do with user asset?
932-
return Err(());
933-
},
934-
};
935944

936945
debug!("Getting {:?} nonce for payout request", payout_address);
937946
// 4. Call accounting contract on BSC
@@ -1088,3 +1097,42 @@ fn calculate_amount_in(amount: &str, gas: &str) -> Option<String> {
10881097
Some((amount - gas).to_string())
10891098
}
10901099
}
1100+
1101+
async fn estimate_bnb_amount(
1102+
binance_api: &Arc<BinanceApi>,
1103+
trade_symbol: &str,
1104+
binance_coin_name: &str,
1105+
from_amount_decimal: Decimal,
1106+
) -> Result<String, ()> {
1107+
let price_str =
1108+
binance_api.spot_trading().get_symbol_price(trade_symbol).await.map_err(|_| {
1109+
log::error!("Failed to get symbol price for {}", trade_symbol);
1110+
})?;
1111+
1112+
let price = if binance_coin_name == "SOL" {
1113+
// For SOL, we sell SOL to get BNB, so get SOLBNB price
1114+
Decimal::from_str(&price_str).map_err(|_| {
1115+
log::error!("Failed to parse symbol price {}", price_str);
1116+
})?
1117+
} else {
1118+
// For USDC/USDT, we buy BNB with USDC/USDT, so get BNBUSDC/BNBUSDT price and invert
1119+
let price = Decimal::from_str(&price_str).map_err(|_| {
1120+
log::error!("Failed to parse symbol price {}", price_str);
1121+
})?;
1122+
if price.is_zero() {
1123+
log::error!("Symbol price is zero for {}", trade_symbol);
1124+
return Err(());
1125+
}
1126+
Decimal::ONE / price
1127+
};
1128+
1129+
let bnb_estimated = from_amount_decimal * price;
1130+
1131+
// Apply 0.1% service fee
1132+
let service_fee_rate = Decimal::from_str("0.001").expect("Failed to parse service fee rate");
1133+
let decimal_bnb_to_receive = bnb_estimated * (Decimal::ONE - service_fee_rate);
1134+
1135+
debug!("BNB estimated: {}, after 0.1% fee: {}", bnb_estimated, decimal_bnb_to_receive);
1136+
1137+
Ok(decimal_bnb_to_receive.to_string())
1138+
}

0 commit comments

Comments
 (0)