Move “Hot Potato” Pattern

Boris Povod
4 min readJul 29, 2022

--

Move language is excellent and flexible, yet some approaches could not be evident for incoming developers. Indeed one of such approaches i want to discuss today. It’s a pattern we recently implemented in one of our projects called “Hot Potato”. I didn’t invent this name, which was born from my colleague who called it so lol. Maybe “Loan Patter” would be more suitable, yet what’s done is done.

One of the problems the pattern is solving: what if we need to extract some resource from a module, but we need that resource to return to the module at the end of our payload execution? Let’s define that extracted resource can’t be copied or dropped but can be stored.

If you are unfamiliar with the topic I’m talking about — read about abilities in Move Book.

Also, look at Github repository contains code for the current article.

Let’s start coding. I’ve written a resource that will store balance for users:

struct Wallet has key, store {
balance: u64,
}

I’m not adding frameworks or other imports to make it simple. Let’s allow to create a new resource with some initial balances:

public fun create(account: &signer) {
move_to(account, Wallet {
balance: 100,
});
}

The function above creates a wallet resource with an initial balance of 100.

We must allow loaning from the wallet resource by creating another wallet and the amount the loaner asked. The loan must be returned at the end of the transaction. For example, let’s agree that at the end of each transaction, the wallet balance should still be equal to 100.

How can we do it? By creating another resource that can’t be dropped, stored, or cloned. The loan function will return our loan and the new resource we just defined. Let’s name that resource “HotPotato” and implement the loan function:

/// Our HotPotato struct can't be dropped, cloned,
/// copied, or stored as it doesn't have any abilities
struct HotPotato {
}
.../// The loan function returns both loaned amounts as Wallet and
/// HotPotato resources.
public fun loan_from_wallet(
addr: address,
amount: u64
): (Wallet, HotPotato) acquires Wallet {
let wallet = borrow_global_mut<Wallet>(addr);
wallet.balance = wallet.balance - amount;
let loan = Wallet {
balance: amount
};
let hot_potato = HotPotato {}; (loan, hot_potato)
}

So what prevents us from never returning the loan? I can explain, as HotPotato resource can’t be stored, cloned, or dropped, and doesn’t have any abilities, only what we can do with it — destruct, you can’t just leave it in scope.

You can try it yourself, create a new module and try to call the loan function, and it wouldn’t compile at all because we need a function inside the hot potato module that will take resource and destruct it.

Look at “thief” module i did:

module 0x1::thief {
use 0x1::hot_potato;
struct MyWallet has key {
wallet: hot_potato::Wallet,
}
public entry fun try_to_steal(
my_account: &signer,
addr: address
) {
let (loan, _hot_potato) = hot_potato::loan_from_wallet(
addr,
50
);

move_to(my_account, MyWallet {
wallet: loan
});
}
}

If you try to compile it, you will get the following errors:

> The local variable 'loan' still contains a value. The value does not have the 'drop' ability and must be consumed before the function returnsAND> To satisfy the constraint, the 'drop' ability would need to be added here

To make it work, we have to implement destruction. But destruction works only inside the same module where the resource is defined. So next, we make a function to pay loan where we accept both loan and hot potato resource and destruct it.

/// Pay loan: pass both loan (Wallet) and hot potato resources. public fun pay_loan(
addr: address,
loan: Wallet,
hot_potato: HotPotato
) acquires Wallet {
let wallet = borrow_global_mut<Wallet>(addr);
wallet.balance = wallet.balance + loan.balance;
assert(wallet.balance == 100, 0);
// destruct
let Wallet {balance: _} = loan;
let HotPotato {} = hot_potato;
}

Look at this, we got our loan back and destroyed hot potato resources. If you update our “thief” module and call “pay_loan”, you will see how it perfects compiles and do what we indeed wanted we initially planned.

Using Move features we just created a way to loan something from module with pay back guarantees.

It’s not all. We can improve the provided example: we can store some critical data in hot potato resources, like the amount of loan, and do additional checks, etc.

The pattern can solve other kinds of issues. For example, another approach i through could be a way to initialize some unsafe states (by returning hot potato resource), call some unsafe functions using resource, and in the end finish unsafe condition with a function that accepts hot potato resource and checks that state can be switched back to safe mode and will destruct resource.

The source code for the current article is placed in Github repository.

Enjoy!

--

--

Boris Povod

Blockchain developer, R&D lead and Co-Founder of Pontem Network