Skip to content

Commit df2ed3c

Browse files
committed
add zero check for listings
1 parent 823518c commit df2ed3c

File tree

2 files changed

+108
-5
lines changed

2 files changed

+108
-5
lines changed

contracts/marketplace/src/execute.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,13 @@ pub fn execute_create_listing(
136136
only_owner(&deps.querier, &info, &collection, &token_id)?;
137137
not_listed(&deps.querier, &collection, &token_id)?;
138138
let config = CONFIG.load(deps.storage)?;
139+
if price.amount.is_zero() {
140+
return Err(ContractError::InvalidPrice {
141+
expected: price.clone(),
142+
actual: price,
143+
});
144+
}
145+
139146
ensure_eq!(
140147
price.denom,
141148
CONFIG.load(deps.storage)?.listing_denom,
@@ -281,7 +288,7 @@ pub fn execute_buy_item(
281288
.amount
282289
.checked_sub(asset_price.amount)
283290
.map_err(|_| ContractError::InsuficientFunds {})?;
284-
Ok(Response::new()
291+
let mut response = Response::new()
285292
.add_event(item_sold_event(
286293
listing.id,
287294
listing.collection.clone(),
@@ -296,14 +303,19 @@ pub fn execute_buy_item(
296303
contract_addr: listing.collection.clone().to_string(),
297304
msg: to_json_binary(&buy_msg)?,
298305
funds: vec![asset_price],
299-
})
300-
.add_message(BankMsg::Send {
306+
});
307+
308+
if !marketplace_fee.is_zero() {
309+
response = response.add_message(BankMsg::Send {
301310
to_address: config.fee_recipient.to_string(),
302311
amount: vec![Coin {
303312
denom: payment.denom,
304313
amount: marketplace_fee,
305314
}],
306-
}))
315+
});
316+
}
317+
318+
Ok(response)
307319
}
308320

309321
fn execute_create_pending_sale(

contracts/marketplace/tests/tests/test_buy_item.rs

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use cosmwasm_std::{coin, Uint128};
33
use cw721_base::msg::ExecuteMsg as Cw721ExecuteMsg;
44
use cw_multi_test::Executor;
55
use xion_nft_marketplace::helpers::query_listing;
6-
use xion_nft_marketplace::msg::ExecuteMsg;
6+
use xion_nft_marketplace::msg::{ExecuteMsg, InstantiateMsg};
77

88
#[test]
99
fn test_buy_item_success() {
@@ -650,3 +650,94 @@ fn test_buy_reserved_for() {
650650
.unwrap();
651651
assert_eq!(owner_resp.owner, reserved_buyer.to_string());
652652
}
653+
654+
#[test]
655+
fn test_buy_item_with_zero_marketplace_fee() {
656+
let mut app = setup_app_with_balances();
657+
let minter = app.api().addr_make("minter");
658+
let seller = app.api().addr_make("seller");
659+
let buyer = app.api().addr_make("buyer");
660+
let manager = app.api().addr_make("manager");
661+
662+
let asset_contract = setup_asset_contract(&mut app, &minter);
663+
664+
// Create marketplace with zero fee and approvals disabled (direct buy path)
665+
let marketplace_code_id = app.store_code(marketplace_contract());
666+
let config_json = serde_json::json!({
667+
"manager": manager.to_string(),
668+
"fee_recipient": manager.to_string(),
669+
"sale_approvals": false,
670+
"fee_bps": 0,
671+
"listing_denom": "uxion"
672+
});
673+
let instantiate_msg = InstantiateMsg {
674+
config: serde_json::from_value(config_json).unwrap(),
675+
};
676+
let marketplace_addr = app
677+
.instantiate_contract(
678+
marketplace_code_id,
679+
manager.clone(),
680+
&instantiate_msg,
681+
&[],
682+
"test-marketplace-zero-fee",
683+
None,
684+
)
685+
.unwrap();
686+
687+
mint_nft(&mut app, &asset_contract, &minter, &seller, "token1");
688+
689+
let price = coin(1000, "uxion");
690+
691+
let seller_balance_before = app.wrap().query_balance(&seller, "uxion").unwrap().amount;
692+
let manager_balance_before = app.wrap().query_balance(&manager, "uxion").unwrap().amount;
693+
694+
let listing_id = create_listing_helper(
695+
&mut app,
696+
&marketplace_addr,
697+
&asset_contract,
698+
&seller,
699+
"token1",
700+
price.clone(),
701+
);
702+
703+
// Direct buy (no approval flow)
704+
let buy_msg = ExecuteMsg::BuyItem {
705+
listing_id,
706+
price: price.clone(),
707+
};
708+
709+
let result = app.execute_contract(
710+
buyer.clone(),
711+
marketplace_addr.clone(),
712+
&buy_msg,
713+
std::slice::from_ref(&price),
714+
);
715+
716+
assert!(result.is_ok(), "Direct buy with zero fee should succeed: {:?}", result.err());
717+
718+
// Seller receives full price (no fee deducted)
719+
let seller_balance_after = app.wrap().query_balance(&seller, "uxion").unwrap().amount;
720+
assert_eq!(
721+
seller_balance_after,
722+
seller_balance_before + price.amount,
723+
"Seller should receive full price with zero fee"
724+
);
725+
726+
// Manager receives nothing
727+
let manager_balance_after = app.wrap().query_balance(&manager, "uxion").unwrap().amount;
728+
assert_eq!(
729+
manager_balance_after, manager_balance_before,
730+
"Manager should not receive any fee"
731+
);
732+
733+
// NFT transferred to buyer
734+
let owner_query = cw721_base::msg::QueryMsg::OwnerOf {
735+
token_id: "token1".to_string(),
736+
include_expired: Some(false),
737+
};
738+
let owner_resp: cw721::msg::OwnerOfResponse = app
739+
.wrap()
740+
.query_wasm_smart(asset_contract.clone(), &owner_query)
741+
.unwrap();
742+
assert_eq!(owner_resp.owner, buyer.to_string());
743+
}

0 commit comments

Comments
 (0)