跳至主要内容
此页面是从英文翻译而来的。请注意,与原始页面相比,可能会出现错误或差异。真实的文档来源应始终是英文版本。

链接操作模式

有时,你需要确保与你的合约互动的用户使用了另一个合同 在他们使用你的之前,先签订合同。例如,你可能需要确保他们有 代币在使用您的合约之前已将代币转移到您的合约账户。

当与代币有关时,这通常被称为 “存款模式”,但存入代币 这不是你唯一一次可能想使用这种模式,因此被称为 “Linked-Actions Pattern”。

以存款模式为例,让我们来看看这笔交易可能是什么样子:

- Transaction
1. eosio.token::transfer (Token Transfer)
-[inline] mycontract::on_transfer (Notifiable Action Receiver)
2. mycontract::record (Regular Action)

上表显示了事务中操作的执行顺序。

交易中只有代币转账和 “记录” 操作,但有 也是由您的代币转移触发的事件接收器函数 合约将在代币转移和记录操作之间进行捕获和放置。

这种模式解决的常见问题是,你要确保 在您允许记录操作发生之前,已进行令牌转移。

让我们来看看一些不带模式的代码:

代币转移操作

ACTION transfer(name from, name to, asset quantity, string memo){
// ...
require_recipient( from );
require_recipient( to );
// ...
}

事件接收器和记录动作

#include <eosio/asset.hpp>

[[eosio::on_notify("eosio.token::transfer")]]
void on_transfer(name from, name to, asset quantity, string memo){
// ...
}

ACTION record(name from, uint64_t internal_id, uint8_t status){
// ...
}

你可以在上面看到,我们想添加一些关于用户转移资金的额外信息 在我们的合约中,但我们无法在代币转移操作中这样做,因为我们所拥有的只是 这 memo 字段,这是一个字符串。

性能注意事项

你可能已经猜到你可以做一些字符串操作和转换来获取数据 你需要进入 memo 字段,但不建议这样做。那个 memo 字段不仅限于 256 大多数代币合约中的字符,但是智能合约中的字符串操作是其中之一 你能做的最昂贵的操作。

取而代之的是,我们可以使用 linked-actions 模式来确保代币转移已经发生 在我们允许之前 record 要采取行动,我们也可以传递我们需要的其他信息 到 record 行动。

让我们更新一下 on_transfer 事件接收器和 record 在它们之间建立联系的操作 使用链接操作模式。

首先,我们要添加一个 multi_index 表格到我们的合同中,用于存储我们需要传递的信息 在两个动作之间。

TABLE transfer_info {
name from;
asset quantity;

uint64_t primary_key() const { return from.value; }
};

using _transfers = multi_index<"transfers"_n, transfer_info>;

[[eosio:: on_notify (“eosio.token:: transfer”)]]
void on_transfer(name from, name to, asset quantity, string memo){
_transfers transfers( get_self(), get_self().value );
transfers.emplace( get_self(), [&]( auto& row ) {
row.from = from;
row.quantity = quantity;
});
}

警告

你应该有更多的支票 on_transfer 比我们在这个例子中看到的要多。本指南不是 关于安全性,所以为了清楚起见,我们省略了这些检查,但你不应该部署令牌事件接收器 在生产中是这样的。

然后,在我们的 record action 我们可以检查转账是否存在,如果有,我们可以 将其从表中删除以释放 RAM 并执行我们的逻辑。

如果没有,我们可以简单地出错然后告诉用户他们需要转移代币 在他们使用合同之前先签订合同。


ACTION record(name from, uint64_t internal_id, uint8_t status){
// ...
_transfers transfers( get_self(), get_self().value );
auto transfer = transfers.find( from.value );
check( transfer != transfers.end(), "Must transfer tokens to contract before using it" );
transfers.erase( transfer );

// Do your logic here
}

RAM 滥用问题

上面的模式效果很好,但确实有问题。如果用户将代币转移到您的合约中 但从来没有打电话给 record 操作,用于存储传输信息的 RAM 永远不会 被释放。

由于您的合同是支付RAM的合约,这意味着账户可以发送少量代币 根据您的合同,消耗您的RAM并使您的合同过于昂贵。

我们可以通过添加一个来解决这个问题 checkon_transfer 事件接收器,以确保数量 在我们存储转账信息之前,已经超过了某个阈值。

[[eosio::on_notify("eosio.token::transfer")]]
void on_transfer(name from, name to, asset quantity, string memo){
check(quantity.amount > 100, "Must transfer more than 100 tokens");

...
}

或者,我们可以消耗这些成本,然后定期清理表以释放 RAM 这已经不用了。

ACTION cleanup(){
_transfers transfers( get_self(), get_self().value );
auto transfer = transfers.begin();

uint8_t count = 0;
while( transfer != transfers.end() && count < 100 ) {
transfer = transfers.erase( transfer );
count++;
}
}

请注意,您必须定期自己调用此操作以降低内存使用率, 但是,对于与货币价值无关的关联行动模式,这是一个不错的选择 降低内存使用率的方法。

挑战

你怎么能修改上面的代码来捕获 NFT 转账并关联操作,这样 只有所有者和正确的 NFT 才能触发 record 行动?