Move语言“烫手山芋”模式

本文介绍了Move语言中的"Hot Potato"设计模式,该模式用于从模块中安全地借用资源,确保在交易结束时资源必须归还。通过创建一个不可复制、不可丢弃的HotPotato资源,强制借款者必须通过特定的pay_loan函数归还借款和销毁HotPotato资源,从而保证了资源的安全。

Move 语言非常出色且灵活,但对于新来的开发者来说,某些方法可能并不明显。 事实上,我想在今天讨论的其中一种方法。 这是我们最近在一个名为“Hot Potato”(烫手山芋)的项目中实现的一种模式。 我没有发明这个名字,它来自我的同事,他这样称呼它,哈哈。 也许“Loan Pattern”(贷款模式)更合适,但事已至此。

该模式解决的问题之一是:如果我们需要从一个模块中提取一些资源,但我们需要该资源在我们的 payload 执行结束时返回到该模块,该怎么办? 让我们定义提取的资源不能被复制或丢弃,但可以被存储。

如果你不熟悉我正在谈论的主题,请阅读 Move Book 中的关于 abilities 的内容。

另外,请查看 Github repository,其中包含本文的代码。

让我们开始编写代码。 我已经编写了一个 resource,它将存储用户的余额:

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

为了简单起见,我没有添加框架或其他导入。 让我们允许使用一些初始余额创建一个新的 resource:

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

上面的函数创建一个初始余额为 100 的 wallet resource。

我们必须允许从 wallet resource 中贷款,方法是创建另一个 wallet 和贷款人要求的金额。贷款必须在交易结束时返回。例如,让我们约定在每次交易结束时,wallet 余额仍应等于 100。

我们该怎么做呢? 通过创建另一个不能被丢弃、存储或克隆的 resource。 loan 函数将返回我们的贷款和我们刚刚定义的新的 resource。 让我们将该 resource 命名为“HotPotato”,并实现 loan 函数:

/// 我们的 HotPotato struct 不能被丢弃、克隆、
/// 复制或存储,因为它没有任何 abilities
struct HotPotato {
}.../// loan 函数返回贷款金额作为 Wallet 和
/// 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)
}

那么是什么阻止我们永远不归还贷款呢? 我可以解释一下,因为 HotPotato resource 不能被存储、克隆或丢弃,并且没有任何 abilities,我们唯一能做的就是销毁它,你不能只是将它留在作用域中。

你可以自己尝试一下,创建一个新模块并尝试调用 loan 函数,它根本无法编译,因为我们需要 hot potato 模块中的一个函数来获取 resource 并销毁它。

看看我做的“thief”模块:

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
        });
    }
}

如果你尝试编译它,你将收到以下错误:

> 局部变量 'loan' 仍然包含一个值。该值没有 'drop' ability,必须在函数返回之前被消耗
> 为了满足约束,需要在此处添加 'drop' ability

为了使其工作,我们必须实现 destruction。 但是 destruction 仅在定义 resource 的同一模块内有效。 因此,接下来,我们创建一个函数来偿还贷款,在该函数中,我们接受贷款和 hot potato resource 并销毁它。

/// 偿还贷款:传递贷款 (Wallet) 和 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;
}

看看这个,我们拿回了贷款并销毁了 hot potato resources。 如果你更新我们的“thief”模块并调用“pay_loan”,你将看到它是如何完美编译并执行我们最初计划的操作的。

使用 Move features,我们刚刚创建了一种从模块贷款并保证偿还的方式。

但这还不是全部。 我们可以改进提供的示例:我们可以在 hot potato resources 中存储一些关键数据,例如贷款金额,并进行额外的检查等。

该模式可以解决其他类型的问题。 例如,我考虑的另一种方法可能是初始化一些不安全状态(通过返回 hot potato resource),使用 resource 调用一些不安全函数,最后使用接受 hot potato resource 并检查状态是否可以切换回安全模式的函数来结束不安全状态,并将销毁 resource。

本文的源代码位于 Github repository

请享用!

  • 原文链接: medium.com/@borispovod/m...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
borispovod
borispovod
江湖只有他的大名,没有他的介绍。