diff --git a/src/contract.rs b/src/contract.rs index 593ca1a..73d91cb 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -189,9 +189,11 @@ pub mod execute { // Precheck if enough balance in pool for user swap request let available_offer = offer.query_pool(&deps.querier, pool_address.clone())?; - // TODO: Use a better error type if available_offer < amount.into() { - return Err(ContractError::Unauthorized {}); + return Err(ContractError::InsufficientFunds { + asked_amount: amount.into(), + available_amount: available_offer, + }); } let asked_asset = if offer.equal(&asset_infos[0]) { diff --git a/src/error.rs b/src/error.rs index 732b241..f597189 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,7 +1,8 @@ use cosmwasm_std::StdError; +use cosmwasm_std::Uint128; use cw_utils::PaymentError; use thiserror::Error; -#[derive(Error, Debug)] +#[derive(Error, Debug, PartialEq)] pub enum ContractError { #[error("{0}")] Std(#[from] StdError), @@ -20,4 +21,9 @@ pub enum ContractError { PoolNotExist {}, #[error("Offer and ask token should not be identical")] DoublingAssets {}, + #[error("Insufficient funds available in the pool to complete the swap: {asked_amount} > {available_amount}")] + InsufficientFunds { + asked_amount: Uint128, + available_amount: Uint128, + }, } diff --git a/src/integration_tests.rs b/src/integration_tests.rs index 46a95ce..aafc9b6 100644 --- a/src/integration_tests.rs +++ b/src/integration_tests.rs @@ -446,4 +446,76 @@ mod tests { Uint128::from(500u128) ); } + #[test] + fn test_pool_insufficient_balance_for_swap() { + let owner = Addr::unchecked(ADMIN); + let user = Addr::unchecked(USER); + let (mut app, cw_contract) = proper_instantiate(); + let (token_contract, factory_contract, pair_contract) = instantiate_contracts(&mut app); + let my_contract = instantiate_my_contract(&mut app); + let inj_amount = Uint128::new(1_000_000); + let ttt_amount = Uint128::new(1_000_000); + let inj_offer = Uint128::new(10_000_000); + + let (msg, coins) = + provide_liquidity_msg(ttt_amount, inj_amount, None, None, &token_contract); + + app.execute_contract(owner.clone(), pair_contract.addr(), &msg, &coins) + .unwrap(); + // Enable pool balance tracking + let msg = PairExecuteMsg::UpdateConfig { + params: to_json_binary(&XYKPoolUpdateParams::EnableAssetBalancesTracking).unwrap(), + }; + app.execute_contract(owner.clone(), pair_contract.addr(), &msg, &[]) + .unwrap(); + + app.update_block(|b| b.height += 1); + + // Check pool balances + let res: Option = app + .wrap() + .query_wasm_smart( + pair_contract.addr(), + &PairQueryMsg::AssetBalanceAt { + asset_info: AssetInfo::NativeToken { + denom: "ttt".to_string(), + }, + block_height: app.block_info().height.into(), + }, + ) + .unwrap(); + assert_eq!(res.unwrap(), Uint128::new(1_000_000)); + // Check pool balances + let res: Option = app + .wrap() + .query_wasm_smart( + pair_contract.addr(), + &PairQueryMsg::AssetBalanceAt { + asset_info: AssetInfo::NativeToken { + denom: "inj".to_owned(), + }, + block_height: app.block_info().height.into(), + }, + ) + .unwrap(); + assert_eq!(res.unwrap(), Uint128::new(1_000_000)); + // Perform swap of two native tokens + let swap_msg = crate::msg::ExecuteMsg::MySwap { + pool_address: pair_contract.addr(), + }; + let send_funds = vec![Coin { + denom: "inj".to_string(), + amount: inj_offer, + }]; + let res = app + .execute_contract(owner.clone(), my_contract.addr(), &swap_msg, &send_funds) + .unwrap_err(); + assert_eq!( + crate::error::ContractError::InsufficientFunds { + asked_amount: inj_offer, + available_amount: Uint128::new(1_000_000) + }, + res.downcast().unwrap(), + ); + } }