Skip to content

Commit

Permalink
improves marketplace example with ofields (MystenLabs#6104)
Browse files Browse the repository at this point in the history
  • Loading branch information
damirka authored Nov 25, 2022
1 parent 988e384 commit f4b58ae
Showing 1 changed file with 102 additions and 37 deletions.
139 changes: 102 additions & 37 deletions sui_programmability/examples/nfts/sources/marketplace.move
Original file line number Diff line number Diff line change
@@ -1,102 +1,168 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

/// Basic `Marketplace` implementation. Supports listing of any assets,
/// and does not have constraints.
///
/// Makes use of `sui::dynamic_object_field` module by attaching `Listing`
/// objects as fields to the `Marketplace` object; as well as stores and
/// merges user profits as dynamic object fields (ofield).
///
/// Rough illustration of the dynamic field architecture for listings:
/// ```
/// /--->Listing--->Item
/// (Marketplace)--->Listing--->Item
/// \--->Listing--->Item
/// ```
///
/// Profits storage is also attached to the `Marketplace` (indexed by `address`):
/// ```
/// /--->Coin<COIN>
/// (Marketplace<COIN>)--->Coin<COIN>
/// \--->Coin<COIN>
/// ```
module nfts::marketplace {
use sui::dynamic_field;
use sui::dynamic_object_field as ofield;
use sui::tx_context::{Self, TxContext};
use sui::object::{Self, ID, UID};
use sui::transfer;
use sui::coin::{Self, Coin};
use sui::transfer;

// For when amount paid does not match the expected.
/// For when amount paid does not match the expected.
const EAmountIncorrect: u64 = 0;

// For when someone tries to delist without ownership.
/// For when someone tries to delist without ownership.
const ENotOwner: u64 = 1;

struct Marketplace has key {
/// A shared `Marketplace`. Can be created by anyone using the
/// `create` function. One instance of `Marketplace` accepts
/// only one type of Coin - `COIN` for all its listings.
struct Marketplace<phantom COIN> has key {
id: UID,
}

/// A single listing which contains the listed item and its price in [`Coin<C>`].
struct Listing<T: key + store, phantom C> has store {
item: T,
ask: u64, // Coin<C>
/// A single listing which contains the listed item and its
/// price in [`Coin<COIN>`].
struct Listing has key, store {
id: UID,
ask: u64,
owner: address,
}

/// Create a new shared Marketplace.
public entry fun create(ctx: &mut TxContext) {
public entry fun create<COIN>(ctx: &mut TxContext) {
let id = object::new(ctx);
let marketplace = Marketplace { id };
transfer::share_object(marketplace);
transfer::share_object(Marketplace<COIN> { id })
}

/// List an item at the Marketplace.
public entry fun list<T: key + store, C>(
marketplace: &mut Marketplace,
public entry fun list<T: key + store, COIN>(
marketplace: &mut Marketplace<COIN>,
item: T,
ask: u64,
ctx: &mut TxContext
) {
let item_id = object::id(&item);
let listing = Listing<T, C> {
item,
let listing = Listing {
ask,
id: object::new(ctx),
owner: tx_context::sender(ctx),
};
dynamic_field::add(&mut marketplace.id, item_id, listing);

ofield::add(&mut listing.id, true, item);
ofield::add(&mut marketplace.id, item_id, listing)
}

/// Remove listing and get an item back. Only owner can do that.
public fun delist<T: key + store, C>(
marketplace: &mut Marketplace,
public fun delist<T: key + store, COIN>(
marketplace: &mut Marketplace<COIN>,
item_id: ID,
ctx: &mut TxContext
): T {
let Listing<T, C> { item, ask: _, owner } =
dynamic_field::remove(&mut marketplace.id, item_id);
let Listing {
id,
owner,
ask: _,
} = ofield::remove(&mut marketplace.id, item_id);

assert!(tx_context::sender(ctx) == owner, ENotOwner);

let item = ofield::remove(&mut id, true);
object::delete(id);
item
}

/// Call [`delist`] and transfer item to the sender.
public entry fun delist_and_take<T: key + store, C>(
marketplace: &mut Marketplace,
public entry fun delist_and_take<T: key + store, COIN>(
marketplace: &mut Marketplace<COIN>,
item_id: ID,
ctx: &mut TxContext
) {
let item = delist<T, C>(marketplace, item_id, ctx);
let item = delist<T, COIN>(marketplace, item_id, ctx);
transfer::transfer(item, tx_context::sender(ctx));
}

/// Purchase an item using a known Listing. Payment is done in Coin<C>.
/// Amount paid must match the requested amount. If conditions are met,
/// owner of the item gets the payment and buyer receives their item.
public fun buy<T: key + store, C>(
marketplace: &mut Marketplace,
public fun buy<T: key + store, COIN>(
marketplace: &mut Marketplace<COIN>,
item_id: ID,
paid: Coin<C>,
paid: Coin<COIN>,
): T {
let Listing<T, C> { item, ask, owner } =
dynamic_field::remove(&mut marketplace.id, item_id);
let Listing {
id,
ask,
owner
} = ofield::remove(&mut marketplace.id, item_id);

assert!(ask == coin::value(&paid), EAmountIncorrect);

transfer::transfer(paid, owner);
// Check if there's already a Coin hanging and merge `paid` with it.
// Otherwise attach `paid` to the `Marketplace` under owner's `address`.
if (ofield::exists_<address>(&marketplace.id, owner)) {
coin::join(
ofield::borrow_mut<address, Coin<COIN>>(&mut marketplace.id, owner),
paid
)
} else {
ofield::add(&mut marketplace.id, owner, paid)
};

let item = ofield::remove(&mut id, true);
object::delete(id);
item
}

/// Call [`buy`] and transfer item to the sender.
public entry fun buy_and_take<T: key + store, C>(
marketplace: &mut Marketplace,
public entry fun buy_and_take<T: key + store, COIN>(
marketplace: &mut Marketplace<COIN>,
item_id: ID,
paid: Coin<C>,
paid: Coin<COIN>,
ctx: &mut TxContext
) {
transfer::transfer(
buy<T, COIN>(marketplace, item_id, paid),
tx_context::sender(ctx)
)
}

/// Take profits from selling items on the `Marketplace`.
public fun take_profits<COIN>(
marketplace: &mut Marketplace<COIN>,
ctx: &mut TxContext
): Coin<COIN> {
ofield::remove<address, Coin<COIN>>(&mut marketplace.id, tx_context::sender(ctx))
}

/// Call [`take_profits`] and transfer Coin to the sender.
public entry fun take_profits_and_keep<COIN>(
marketplace: &mut Marketplace<COIN>,
ctx: &mut TxContext
) {
transfer::transfer(buy<T, C>(marketplace, item_id, paid), tx_context::sender(ctx))
transfer::transfer(
take_profits(marketplace, ctx),
tx_context::sender(ctx)
)
}
}

Expand All @@ -107,7 +173,6 @@ module nfts::marketplaceTests {
use sui::coin;
use sui::sui::SUI;
use sui::test_scenario::{Self, Scenario};
// use nfts::bag::{Self, Bag};
use nfts::marketplace;

// Simple Kitty-NFT data structure.
Expand All @@ -123,7 +188,7 @@ module nfts::marketplaceTests {
/// Create a shared [`Marketplace`].
fun create_marketplace(scenario: &mut Scenario) {
test_scenario::next_tx(scenario, ADMIN);
marketplace::create(test_scenario::ctx(scenario));
marketplace::create<SUI>(test_scenario::ctx(scenario));
}

/// Mint SUI and send it to BUYER.
Expand Down

0 comments on commit f4b58ae

Please sign in to comment.