アビリティシステムにおけるケース - アビリティを持たない構造体 - はホットポテトと呼ばれます。これは保存できず(オブジェクトとしても、他の構造体のフィールドとしても)、コピーや破棄もできません。したがって、一度構築されると、そのモジュールによって適切に解体されなければならず、さもなければトランザクションはdropのない未使用の値によって中断されます。
コールバックをサポートする言語に精通している場合、ホットポテトをコールバック関数を呼び出す義務と考えることができます。呼び出さないと、トランザクションは中断されます。
この名前は、子供たちのゲームに由来しています。そこでは、ボールがプレイヤー間で素早く渡され、音楽が止まったときに最後にボールを持っていたプレイヤーがゲームから外れます。これがこのパターンの最良の例示です - ホットポテト構造体のインスタンスは呼び出し間で渡され、どのモジュールもそれを保持することはできません。
public struct Request {}
Request
にはアビリティがなく、保存や無視ができないため、モジュールはそれを解体する関数を提供する必要があります。例えば:
/// 新しい`Request`を構築します
public fun new_request(): Request { Request {} }
/// `Request`を解体します。ホットポテトの性質上、トランザクションの中断を
/// 避けるためにこの関数を呼び出す必要があります。
public fun confirm_request(request: Request) {
let Request {} = request;
}
以下の例では、Promise
ホットポテトを使用して、コンテナから借用された値が確実に元に戻されるようにしています。Promise
構造体には借用されたオブジェクトのIDとコンテナのIDが含まれており、借用された値が別のものと交換されていないこと、そして正しいコンテナに返されることを保証します。
/// `key + store`を持つ任意のObjectのための汎用コンテナ。Optionタイプは
/// 値の取り出しと戻しを可能にするために使用されます。
public struct Container<T: key + store> has key {
id: UID,
value: Option<T>,
}
/// 借用された値が確実に返されるようにするためのホットポテト構造体。
public struct Promise {
/// 借用されたオブジェクトのID。値の交換がなかったことを保証します。
id: ID,
/// コンテナのID。借用された値が正しいコンテナに返されることを保証します。
container_id: ID,
}
/// コンテナから値を借用するモジュール。
public fun borrow_val<T: key + store>(container: &mut Container<T>): (T, Promise) {
assert!(container.value.is_some());
let value = container.value.extract();
let id = object::id(&value);
(value, Promise { id, container_id: object::id(container) })
}
/// 取り出したアイテムをコンテナに戻します。
public fun return_val<T: key + store>(
container: &mut Container<T>, value: T, promise: Promise
) {
let Promise { id, container_id } = promise;
assert!(object::id(container) == container_id);
assert!(object::id(&value) == id);
container.value.fill(value);
}
以下に、ホットポテトパターンの一般的な使用例をいくつか挙げます。
上記の例で示したように、ホットポテトは借用された値が確実に正しいコンテナに返されることを保証するのに非常に効果的です。この例ではOption
内に保存された値に焦点を当てていますが、同じパターンを他の任意のストレージタイプ、例えばダイナミックフィールドにも適用できます。
ホットポテトパターンの典型的な例はフラッシュローンです。フラッシュローンは同一トランザクション内で借り入れと返済が行われるローンです。借り入れた資金は何らかの操作を行うために使用され、返済された資金は貸し手に返されます。ホットポテトパターンは、借り入れた資金が確実に貸し手に返されることを保証します。
// 貸し手から資金を借り入れます。
let (asset_a, potato) = lender.borrow(amount);
// 借り入れた資金で何らかの操作を行います。
let asset_b = dex.trade(loan);
let proceeds = another_contract::do_something(asset_b);
// 手数料を保持し、残りを貸し手に返します。
let pay_back = proceeds.split(amount, ctx);
lender.repay(pay_back, potato);
transfer::public_transfer(proceeds, ctx.sender());
ホットポテトパターンを使用して、実行パスに変化を導入することができます。例えば、「ボーナスポイント」またはUSDでPhone
を購入できるモジュールがある場合、ホットポテトを使用して購入と支払いを分離できます。このアプローチは、一部の店舗の仕組みと非常によく似ています - 商品を棚から取り、そしてレジに行って支払いをします。
/// `Phone`。店舗で購入可能です。
public struct Phone has key, store { id: UID }
/// `Phone`を購入するために支払わなければならないチケット。
public struct Ticket { amount: u64 }
/// `Phone`と、それを購入するために支払わなければならない`Ticket`を返します。
public fun purchase_phone(ctx: &mut TxContext): (Phone, Ticket) {
(
Phone { id: object::new(ctx) },
Ticket { amount: 100 }
)
}
/// 顧客は`BonusPoints`または`SUI`で`Phone`の支払いができます。
public fun pay_in_bonus_points(ticket: Ticket, payment: Coin<BONUS>) {
let Ticket { amount } = ticket;
assert!(payment.value() == amount);
abort 0 // 関数の残りの部分は省略
}
/// 顧客は`USD`で`Phone`の支払いができます。
public fun pay_in_usd(ticket: Ticket, payment: Coin<USD>) {
let Ticket { amount } = ticket;
assert!(payment.value() == amount);
abort 0 // 関数の残りの部分は省略
}