高级:处理多种代币类型

在前一课中,我们实现了一个简单的代币市场,卖家列出他们想要出售的代币,买家可以使用 SUI 自动从列表中购买,而无需卖家参与或签署交易。

public struct Listing<phantom CoinType> has key {
    id: UID,
    seller: address,
    listed_coins: Balance<CoinType>,
    amount_asked: u64,
}

public fun buy_coins<CoinType>(listing: Listing<CoinType>, payment: Coin<SUI>): Balance<CoinType> {
    let Listing<CoinType> { id, seller, listed_coins, amount_asked } = listing;
    object::delete(id);
    assert!(coin::value(&payment) == amount_asked, EINSUFFICIENT_PAYMENT);
    transfer::public_transfer(payment, seller);
    listed_coins
}

如果我们希望卖家指定他们接受哪种代币作为支付,而不是总是要求使用 Sui,该怎么办? 这意味着我们必须将两种不同类型的代币作为类型参数用于 Listing 对象和 buy_coins 函数:

public struct Listing<phantom ListedCoin, phantom PaymentCoin> has key {
    id: UID,
    seller: address,
    listed_coins: Balance<ListedCoin>,
    amount_asked: u64,
}

public fun buy_coins<ListedCoin, PaymentCoin>(listing: Listing<ListedCoin, PaymentCoin>, payment: Coin<PaymentCoin>): Balance<CoinType> {
    let Listing<ListedCoin, PaymentCoin> { id, seller, listed_coins, amount_asked } = listing;
    object::delete(id);
    assert!(coin::value(&payment) == amount_asked, EINSUFFICIENT_PAYMENT);
    transfer::public_transfer(payment, seller);
    listed_coins
}

我们做了以下更改:

  1. Listing 现在有两种单独的(虚拟)类型。我们可以为类型命名,这里我们使名称非常明确,以免混淆类型——ListedCoin 和 PaymentCoin。
  2. buy_coins 现在也接受两种代币类型。支付现在必须是 Coin<PaymentCoin> 类型,而不是 Coin<SUI>
  3. buy_coins 内部的逻辑变化不大。我们不需要明确验证支付是否为正确的类型,因为支付代币的类型已经在 Listing<ListedCoin, PaymentCoin> 中直接指定。买家必须提供正确的支付代币,否则他们根本无法调用此函数!
  4. 类型正确性由 Move 编译器和 Sui VM 在执行交易时保证。

在非常复杂的情况下,例如允许在多种不同代币类型之间进行 3 方或 4 方交易的交易所,您可能会看到非常长的函数,例如:

public fun buy_coins<Coin1, Coin2, Coin3, Coin4>(...) {
}

CoinTypes 必须始终显式声明为类型参数。如果我们希望使用上面的函数来支持 1 种、2 种、3 种或 4 种类型的交易,这可能会导致一些复杂性,因为用户在调用此函数时不能将缺失的类型留空。为了解决这个问题,可以定义一个“Null”代币类型,用户在不需要时可以传递这个类型:

use std::type_name;

public struct Null has drop {}

public fun buy_coins<Coin1, Coin2, Coin3, Coin4>(...) {
    if (type_name::into_string<Coin3>() == type_name::into_string<Null>()) {
        // Coin3 is not specified and should be ignored
    }
}

如上所示,开发人员可以使用 type_name::into_string 来比较类型,以确定哪些类型未被指定。