in the place_order function of the ThunderExchange contract where user funds are not properly locked during the order placement process. This allows users to place multiple orders using the same funds
The current implementation of the place_order function only checks if the user has enough balance to cover the order but does not lock these funds immediately. This oversight allows users to place multiple buy orders simultaneously with the same pool balance, .
Impact
Place multiple buy orders with insufficient funds. leads funds loss
User A has a balance of 100 units in payment_asset. User A places a buy order for 100 units of payment_asset. Before the system processes and updates the balance, User A quickly places another buy order for 100 units of payment_asset. Both orders are accepted because the system checks the balance but does not lock the funds during the process. ( or minus balance after place order )
/// Places MakerOrder by calling the strategy contract /// Checks if the order is valid #[storage(read), payable] fn place_order(order_input: MakerOrderInput) { _validate_maker_order_input(order_input);
}
FIX
implement a mechanism to lock user funds during the order placement process. This can be achieved by:
Locking the required funds immediately after validating the order and before any further processing.
fn _lock_funds(account: Identity, asset: AssetId, amount: u64) { // Logic to lock the specified amount of funds for the account // This can involve updating a separate storage map to track locked funds }
let strategy = abi(ExecutionStrategy, order_input.strategy.bits());
let order = MakerOrder::new(order_input);
match order.side {
Side::Buy => {
// Buy MakerOrder (e.g. make offer)
// Checks if user has enough bid balance
let pool_balance = _get_pool_balance(order.maker, order.payment_asset);
require(order.price <= pool_balance, ThunderExchangeErrors::AmountHigherThanPoolBalance);
},
Side::Sell => {
// Sell MakerOrder (e.g. listing)
// Checks if assetId and amount mathces with the order
require(msg_asset_id() == AssetId::new(order.collection, order.token_id), ThunderExchangeErrors::AssetIdNotMatched);
require(msg_amount() == order_input.amount, ThunderExchangeErrors::AmountNotMatched);
},
}
strategy.place_order(order);
log(OrderPlaced {
order
});
let strategy = abi(ExecutionStrategy, order_input.strategy.bits());
let order = MakerOrder::new(order_input);
match order.side {
Side::Buy => {
// Lock user funds before placing the order
let pool_balance = _get_pool_balance(order.maker, order.payment_asset);
require(order.price <= pool_balance, ThunderExchangeErrors::AmountHigherThanPoolBalance);
// Lock the funds
_lock_funds(order.maker, order.payment_asset, order.price);
},
Side::Sell => {
require(msg_asset_id() == AssetId::new(order.collection, order.token_id), ThunderExchangeErrors::AssetIdNotMatched);
require(msg_amount() == order_input.amount, ThunderExchangeErrors::AmountNotMatched);
},
}
strategy.place_order(order);
log(OrderPlaced {
order
});