Skip to main content

Hot Potato

The "hot potato" pattern in Move refers to a struct that lacks capabilities, meaning it can only be created and used within its defining module.

This pattern requires that function B must be called immediately after function A, when function A returns a hot potato and function B consumes it. This approach is particularly useful in scenarios like implementing flash loans.

module examples::trade_in {
use iota::iota::IOTA;
use iota::coin::{Self, Coin};

/// Price for the first phone model in the series
const MODEL_ONE_PRICE: u64 = 10000;

/// Price for the second phone model
const MODEL_TWO_PRICE: u64 = 20000;

/// Error when attempting to purchase a non-existent model
const EWrongModel: u64 = 1;

/// Error when the paid amount does not match the required price
const EIncorrectAmount: u64 = 2;

/// Struct representing a phone, which can be purchased or traded in for a newer model
public struct Phone has key, store { id: UID, model: u8 }

/// Struct representing a payable receipt.
/// This receipt must be used immediately in one of the payment functions:
/// either `trade_in` or `pay_full`. It cannot be stored, owned, or dropped.
public struct Receipt { price: u64 }

/// Function to initiate the purchase of a phone, with the payment deferred.
/// The `Receipt` returned must be passed
/// to either the `pay_full` or `trade_in` functions to complete the transaction.
public fun buy_phone(model: u8, ctx: &mut TxContext): (Phone, Receipt) {
assert!(model == 1 || model == 2, EWrongModel);

let price = if (model == 1) MODEL_ONE_PRICE else MODEL_TWO_PRICE;

(
Phone { id: object::new(ctx), model },
Receipt { price }
)
}

/// Function to pay the full price for the phone and finalize the transaction by consuming the `Receipt`.
public fun pay_full(receipt: Receipt, payment: Coin<IOTA>) {
let Receipt { price } = receipt;
assert!(coin::value(&payment) == price, EIncorrectAmount);

// For simplicity, transfer directly to the @examples account
transfer::public_transfer(payment, @examples);
}

/// Function to trade in an old phone and receive a 50% discount on the new phone.
/// The `Receipt` is consumed as part of this transaction.
public fun trade_in(receipt: Receipt, old_phone: Phone, payment: Coin<IOTA>) {
let Receipt { price } = receipt;
let tradein_price = if (old_phone.model == 1) MODEL_ONE_PRICE else MODEL_TWO_PRICE;
let to_pay = price - (tradein_price / 2);

assert!(coin::value(&payment) == to_pay, EIncorrectAmount);

transfer::public_transfer(old_phone, @examples);
transfer::public_transfer(payment, @examples);
}
}

The Flash Loan example specifically illustrates the hot potato pattern described in this topic.

Question 1/2

What does the 'hot potato' pattern in Move refer to?