本文介绍了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 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!