Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions contracts/marketplace/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,10 @@ pub enum ContractError {
collection: String,
token_id: String,
},

#[error("Pending sale expired: {id}")]
PendingSaleExpired { id: String },

#[error("Pending sale not yet expired: {id}")]
PendingSaleNotExpired { id: String },
}
2 changes: 2 additions & 0 deletions contracts/marketplace/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ pub fn sale_rejected_event(
buyer: Addr,
seller: Addr,
price: Coin,
reason: &str,
) -> Event {
Event::new(format!("{}/sale-rejected", env!("CARGO_PKG_NAME")))
.add_attribute("pending_sale_id", pending_sale_id)
Expand All @@ -163,4 +164,5 @@ pub fn sale_rejected_event(
.add_attribute("buyer", buyer.to_string())
.add_attribute("seller", seller.to_string())
.add_attribute("price", price.to_string())
.add_attribute("reason", reason)
}
84 changes: 72 additions & 12 deletions contracts/marketplace/src/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,9 @@ pub fn execute(
),

ExecuteMsg::CancelCollectionOffer { id } => execute_cancel_collection_offer(deps, info, id),
ExecuteMsg::ApproveSale { id } => execute_approve_sale(deps, info, id),
ExecuteMsg::ApproveSale { id } => execute_approve_sale(deps, env, info, id),
ExecuteMsg::RejectSale { id } => execute_reject_sale(deps, info, id),
ExecuteMsg::ReclaimExpiredSale { id } => execute_reclaim_expired_sale(deps, env, info, id),
ExecuteMsg::UpdateConfig { config } => execute_update_config(deps, info, config),
}
}
Expand Down Expand Up @@ -135,6 +136,13 @@ pub fn execute_create_listing(
only_owner(&deps.querier, &info, &collection, &token_id)?;
not_listed(&deps.querier, &collection, &token_id)?;
let config = CONFIG.load(deps.storage)?;
if price.amount.is_zero() {
return Err(ContractError::InvalidPrice {
expected: price.clone(),
actual: price,
});
}

ensure_eq!(
price.denom,
CONFIG.load(deps.storage)?.listing_denom,
Expand Down Expand Up @@ -246,6 +254,13 @@ pub fn execute_buy_item(
let config = CONFIG.load(deps.storage)?;
let listing = listings().load(deps.storage, listing_id.clone())?;

if listing.status != ListingStatus::Active {
return Err(ContractError::InvalidListingStatus {
expected: ListingStatus::Active.to_string(),
actual: listing.status.to_string(),
});
}

if let Some(reserved_for) = listing.reserved_for.clone() {
ensure_eq!(
reserved_for,
Expand Down Expand Up @@ -280,7 +295,7 @@ pub fn execute_buy_item(
.amount
.checked_sub(asset_price.amount)
.map_err(|_| ContractError::InsuficientFunds {})?;
Ok(Response::new()
let mut response = Response::new()
.add_event(item_sold_event(
listing.id,
listing.collection.clone(),
Expand All @@ -295,14 +310,19 @@ pub fn execute_buy_item(
contract_addr: listing.collection.clone().to_string(),
msg: to_json_binary(&buy_msg)?,
funds: vec![asset_price],
})
.add_message(BankMsg::Send {
});

if !marketplace_fee.is_zero() {
response = response.add_message(BankMsg::Send {
to_address: config.fee_recipient.to_string(),
amount: vec![Coin {
denom: payment.denom,
amount: marketplace_fee,
}],
}))
});
}

Ok(response)
}

fn execute_create_pending_sale(
Expand Down Expand Up @@ -400,6 +420,7 @@ fn execute_create_pending_sale(

pub fn execute_approve_sale(
deps: DepsMut,
env: Env,
info: MessageInfo,
pending_sale_id: String,
) -> Result<Response, ContractError> {
Expand All @@ -409,6 +430,12 @@ pub fn execute_approve_sale(
let config = CONFIG.load(deps.storage)?;
let pending_sale = pending_sales().load(deps.storage, pending_sale_id.clone())?;

if env.block.time.seconds() >= pending_sale.expiration {
return Err(ContractError::PendingSaleExpired {
id: pending_sale_id,
});
}

// Generate listing_id to find the listing
let listing_id = generate_id(vec![
pending_sale.collection.as_bytes(),
Expand Down Expand Up @@ -478,16 +505,12 @@ pub fn execute_approve_sale(
Ok(response)
}

pub fn execute_reject_sale(
fn remove_pending_sale(
deps: DepsMut,
info: MessageInfo,
pending_sale_id: String,
pending_sale: PendingSale,
reason: &str,
) -> Result<Response, ContractError> {
// Only manager can reject
only_manager(&info, &deps)?;

let pending_sale = pending_sales().load(deps.storage, pending_sale_id.clone())?;

let listing_id = generate_id(vec![
pending_sale.collection.as_bytes(),
pending_sale.token_id.as_bytes(),
Expand All @@ -514,6 +537,7 @@ pub fn execute_reject_sale(
funds: vec![],
});
}

// refund buyer
let refund_msg = BankMsg::Send {
to_address: pending_sale.buyer.to_string(),
Expand All @@ -531,7 +555,43 @@ pub fn execute_reject_sale(
pending_sale.buyer.clone(),
pending_sale.seller,
pending_sale.price,
reason,
))
.add_message(refund_msg)
.add_messages(sub_msgs))
}

pub fn execute_reject_sale(
deps: DepsMut,
info: MessageInfo,
pending_sale_id: String,
) -> Result<Response, ContractError> {
only_manager(&info, &deps)?;

let pending_sale = pending_sales().load(deps.storage, pending_sale_id.clone())?;

remove_pending_sale(deps, pending_sale_id, pending_sale, "rejected_by_manager")
}

pub fn execute_reclaim_expired_sale(
deps: DepsMut,
env: Env,
info: MessageInfo,
pending_sale_id: String,
) -> Result<Response, ContractError> {
let pending_sale = pending_sales().load(deps.storage, pending_sale_id.clone())?;

if env.block.time.seconds() < pending_sale.expiration {
return Err(ContractError::PendingSaleNotExpired {
id: pending_sale_id,
});
}

if info.sender != pending_sale.buyer {
return Err(ContractError::Unauthorized {
message: "only the buyer can reclaim an expired sale".to_string(),
});
}

remove_pending_sale(deps, pending_sale_id, pending_sale, "expired")
}
3 changes: 3 additions & 0 deletions contracts/marketplace/src/msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ pub enum ExecuteMsg {
RejectSale {
id: String,
},
ReclaimExpiredSale {
id: String,
},
UpdateConfig {
config: Config<String>,
},
Expand Down
3 changes: 2 additions & 1 deletion contracts/marketplace/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,8 @@ pub fn collection_offers<'a>(
pub const AUTO_INCREMENT: Item<u64> = Item::new("auto_increment");

// next_auto_increment is inteded to be used as a generator nonce for unique ids in combination
// with other sources of entropy to generate unique ids.
// with other sources of entropy to generate unique ids, and never to be used as source of unique ids
// as it wraps around effectily resetting the counter.
pub fn next_auto_increment(storage: &mut dyn Storage) -> Result<u64, ContractError> {
let auto_increment = AUTO_INCREMENT.load(storage)?.wrapping_add(1);
AUTO_INCREMENT.save(storage, &auto_increment)?;
Expand Down
1 change: 1 addition & 0 deletions contracts/marketplace/tests/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ mod test_create_collection_offer;
mod test_create_listing;
mod test_create_offer;
mod test_helpers;
mod test_reserved_listing_buy;
Loading
Loading