市场合约
现在我们对各种类型的集合和动态字段的工作原理有了深入的了解,我们可以开始为链上市场编写合约,它可以支持以下功能:
- 列出任意项目类型和数量
- 接受自定义或本地可替代令牌类型的付款
- 可以同时允许多个卖家列出他们的物品并安全地接收付款
类型定义
首先,我们定义整体的“Marketplace”结构:
/// 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.
public struct Marketplace<phantom COIN> has key {
id: UID,
items: Bag,
payments: Table<address, Coin<COIN>>
}
Marketplace
将是一个共享对象,任何人都可以访问和更改。 它接受一个 COIN
通用类型参数,该参数定义了接受付款的 同质化代币 类型。
items
字段将保存项目列表,它可以是不同的类型,因此我们在这里使用异构的 Bag
集合。
payments
字段将保存每个卖家收到的付款。 这可以用一个键值对来表示,其中卖家的地址作为键,接受的硬币类型作为值。 因为这里的key和value的类型是同构的,固定的,所以我们可以对这个字段使用Table
集合类型。
测验:您将如何修改此结构以接受多种可替代令牌类型?
接下来,我们定义一个 Listing
类型:
/// A single listing which contains the listed item and its
/// price in [`Coin<COIN>`].
public struct Listing has key, store {
id: UID,
ask: u64,
owner: address,
}
该结构仅包含我们需要的与项目列表相关的信息。 我们将直接将要交易的实际项目作为动态对象字段附加到 Listing 对象,因此我们不需要在此处显式定义任何项目字段或集合。
注意 Listing
具有 key
能力,这是因为当我们将它放入集合中时,我们希望能够使用它的对象 ID 作为键。
Listing and Delisting
接下来,我们编写列出和删除项目的逻辑。 首先,列出一个项目:
/// List an item at the 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 {
ask,
id: object::new(ctx),
owner: tx_context::sender(ctx),
};
ofield::add(&mut listing.id, true, item);
bag::add(&mut marketplace.items, item_id, listing)
}
如前所述,我们将简单地使用动态对象字段接口附加任意类型的待售商品,然后我们将 Listing
对象添加到 listings 的 Bag
中,使用该商品的对象 id 作为 key 和实际的 Listing 对象作为值(这就是为什么 Listing 也有 store 的能力)。
对于下架,我们定义了以下方法:
/// Internal function to remove listing and get an item back. Only owner can do that.
fun delist<T: key + store, COIN>(
marketplace: &mut Marketplace<COIN>,
item_id: ID,
ctx: &mut TxContext
): T {
let Listing {
id,
owner,
ask: _,
} = bag::remove(&mut marketplace.items, 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, COIN>(
marketplace: &mut Marketplace<COIN>,
item_id: ID,
ctx: &mut TxContext
) {
let item = delist<T, COIN>(marketplace, item_id, ctx);
transfer::transfer(item, tx_context::sender(ctx));
}
注意下架的 Listing
对象是如何解包和删除的,以及通过 ofield::remove请记住,Sui 资产不能在其定义模块之外销毁,因此我们必须将项目转移到 delister。
采购和付款
购买商品类似于下架,但具有处理付款的额外逻辑。
/// Internal function to 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.
fun buy<T: key + store, COIN>(
marketplace: &mut Marketplace<COIN>,
item_id: ID,
paid: Coin<COIN>,
): T {
let Listing {
id,
ask,
owner
} = bag::remove(&mut marketplace.items, item_id);
assert!(ask == coin::value(&paid), EAmountIncorrect);
// Check if there's already a Coin hanging and merge `paid` with it.
// Otherwise attach `paid` to the `Marketplace` under owner's `address`.
if (table::contains<address, Coin<COIN>>(&marketplace.payments, owner)) {
coin::join(
table::borrow_mut<address, Coin<COIN>>(&mut marketplace.payments, owner),
paid
)
} else {
table::add(&mut marketplace.payments, 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, COIN>(
marketplace: &mut Marketplace<COIN>,
item_id: ID,
paid: Coin<COIN>,
ctx: &mut TxContext
) {
transfer::transfer(
buy<T, COIN>(marketplace, item_id, paid),
tx_context::sender(ctx)
)
}
第一部分与从列表中删除项目相同,但我们还会检查发送的付款金额是否正确。
第二部分将支付硬币对象插入到我们的payments
Table
中,并且根据卖家是否已经有一些余额,它将执行一个简单的table::add
或table::borrow_mut
和 coin::join
将付款合并到现有余额中。
入口函数 buy_and_take
简单地调用 buy
并将购买的物品转移给买家。
收取利润
最后,我们为卖家定义了从市场中收取余额的方法。
/// Internal function to take profits from selling items on the `Marketplace`.
fun take_profits<COIN>(
marketplace: &mut Marketplace<COIN>,
ctx: &mut TxContext
): Coin<COIN> {
table::remove<address, Coin<COIN>>(&mut marketplace.payments, tx_context::sender(ctx))
}
/// Call [`take_profits`] and transfer Coin object to the sender.
public entry fun take_profits_and_keep<COIN>(
marketplace: &mut Marketplace<COIN>,
ctx: &mut TxContext
) {
transfer::transfer(
take_profits(marketplace, ctx),
tx_context::sender(ctx)
)
}
Quiz:为什么我们不需要在这种市场设计下使用基于[Capability](/tutorial/sui-move-intro-course-zh/unit-two/lessons/capability设计模式) 的访问控制? 我们可以在这里实现能力设计模式吗? 这会给市场带来什么特性?_
完整合同
您可以在 example_projects/marketplace
文件夹下找到我们实现的通用市场完整的智能合约。