EOS智能合约安全终极指南

36次阅读

共计 6741 个字符,预计需要花费 17 分钟才能阅读完成。

EOS 智能合约安全终极指南。当世界上最大的 ICO,EOS 于 2018 年 6 月推出时,加密社区变得持怀疑态度,并且由于软件错误而被冻结了 2 天。但快进 4 个月,EOS 今天占了以太网今天所做交易的两倍以上。通过免费和更快速交易的承诺,EOS 最顶级的 Dapp 拥有大约 13,000 个每日活跃用户,而以太网的最顶级 Dapp 只有 2,000 个。
一些常见的智能合约漏洞几乎适用于所有平台。与以太坊一样,在 EOS 上编写的智能合约需要在主网上上线之前进行审核。合约中的致命错误可以在合约没有经过足够的测试时被利用。在本指南中,我们将帮助你避免在 EOS 上制作下一个杀手 dApp 的过程中常见的陷阱。
在阅读本指南之前,了解有关 EOS 开发的一些先决条件信息非常重要,这些信息在你阅读本指南时会很方便。了解 C ++ 是必须的。开始智能合约开发的最佳位置是 EOSIO 自己的文档。
ABI 调用处理
extern “C” {
void apply(uint64_t receiver, uint64_t code, uint64_t action) {
class_name thiscontract(receiver);

if ((code == N(eosio.token)) && (action == N(transfer))) {
execute_action(&thiscontract, &class_name::transfer);
return;
}

if (code != receiver) return;

switch (action) {EOSIO_API(class_name, (action_1)(action_n))};
eosio_exit(0);
}
}
上面是修改后的 ABI 调用程序的示例代码。如下所示的更简单的 ABI 调用程序用于简化合约的操作处理。
EOSIO_ABI(class_name, (action_1)(action_n) );
ABI 调用程序 / 交易处理程序允许合约收听传入的 eosio.token 交易时间,以及与智能合约的正常交互。为了避免异常和非法调用,绑定每个键操作和代码以满足要求是很重要的。
一个例子是由于他们的 ABI 转发源代码中的错误而发生在 dApp EOSBet Casino 上的黑客攻击。
if(code == self || code == N(eosio.token) ) {
TYPE thiscontract(self);
switch(action) {
EOSIO_API(TYPE, MEMBERS)
}
}
上面检查 ABI 转发源代码的 apply 动作处理程序允许攻击者完全绕过 eosio.token::transfer() 函数,并在放置之前直接调用 contract::transfer() 函数而不将 EOS 转移到合约中。打赌。对于损失,他没有得到任何报酬,但一无所获。然而,对于胜利,他从合约中支付了真正的 EOS。
他们通过在传入操作请求合约之前添加 eosio.token 合约转移操作检查来修复上述错误。
if(code == self || code == N(eosio.token) ) {
if(action == N(transfer) ){
eosio_assert(code == N(eosio.token), “Must transfer EOS”);
}
TYPE thiscontract(self);
switch(action) {
EOSIO_API(TYPE, MEMBERS)
}
}
使用语句 require_auth(account) 非常重要;进入只需要授权帐户才能执行的操作。require_auth(_self);用于仅授权合约的所有者签署交易。
操作中的授权
void token::transfer(account_name from, account_name to, asset quantity)
{
auto sym = quantity.symbol.name();
require_recipient(from);
require_recipient(to);
auto payer = has_auth(to) ? to : from;
sub_balance(from, quantity);
add_balance(to, quantity, payer);
}
上面的示例代码允许任何人调用该操作。要解决它,请使用 require_auth(from),声明授权付款人调用该行动​​。
尽量避免修改 eosio.token 合约
最近一位白帽黑客因其 eosio.token 合约中的方法调用问题而设法获得了 10 亿美元的 dapp 代币。Dapp Se7ens(现在处于非活动状态)在 eosio.token 合约中声明了一种新方法,用于将其代币空投到用户帐户中。合约没有反映 eosio.token 合约的问题或转移操作的变化,因此资金神奇地出现在用户的帐户上。其次,他们忘记在转移之前验证方法中的金额,这允许黑客在此过程中索取 10 亿个代币。
除了更改最大供应和代币符号之外,建议避免为自定义函数修改它,因为 eosio.token 合约中的错误可能是致命的。为了便于安全地进行空投,将空投代币转移到一个单独的帐户并从那里分发。
修改多索引表属性
EOS 当前将数据存储在共享内存数据库中,以便跨操作共享。
struct [[eosio::table]] person {
account_name key;
std::string first_name;
std::string last_name;
std::string street;
std::string city;
std::string state;

uint64_t primary_key() const { return key;}
};
typedef eosio::multi_index<N(people), person> address_index;
上面的示例代码创建了一个名为 people 的 multi_index 表,该表基于使用 struct person 的该表的单行的数据结构。部署后,EOS 目前不允许修改表属性。eosio_assert_message 断言失败将是将被抛出的错误。因此,在部署表之前需要完全考虑属性。否则,需要创建具有不同名称的新表,并且在从旧表迁移到新表时需要特别小心。如果不这样做可能会导致数据丢失。
数值外溢检查
在进行算术运算时,如果没有足够负责地检查边界条件,则值可能会溢出,从而导致用户资产丢失。
void transfer(symbol_name symbol, account_name from, account_names to, uint64_t balance) {
require_auth(from);
account fromaccount;
eosio_assert(is_balance_within_range(balance), “invalid balance”);
eosio_assert(balance > 0, “must transfer positive balance”); uint64_t amount = balance * 4; //Multiplication overflow
}
在上面的示例代码中,使用 uint64_t 表示用户余额可能会在值乘以时导致溢出。因此,尽量避免使用 uint64_t 来表示余额并对其执行算术运算。使用 eosiolib 中定义 的 asset 结构进行操作,而不是处理溢出条件的精确余额。
考虑合约中的假设
在执行合约时会有假设需要断言。如果断言失败,使用 eosio_assert 将事先处理条件并停止执行特定操作。举个例子:
void assert_roll_under(const uint8_t& roll_under) {
eosio_assert(roll_under >= 2 && roll_under <= 96,
“roll under overflow, must be greater than 2 and less than 96”);
}
上面的断言语句假设 roll_under 整数大于 2 且小于 96. 但如果不是,则抛出上述消息并停止执行。没有发现像上面这样的局部问题可能会成为规则制定的全局灾难。
生成正确随机数
如果没有准确完成,在 EOS 区块链上生成正确的随机数仍然存在风险。如果没有正确地做到这一点,将导致对手预测结果,在整个过程中对整个系统进行游戏。像 Oracalize.it 这样的服务可以提供来自外部源的随机数,但它们很昂贵并且可能出现单点故障。人们过去曾使用 Blockchain 的上下文变量(块编号,块时间戳等)来生成以太坊智能合约中的随机数,这只是在游戏中使用。为了正确地进行生成,程序必须提供一种单一方无法单独控制的组合随机性。目前最好的方法之一是 Dan Larimar 自己生成随机数时建议的方法。
string sha256_to_hex(const checksum256& sha256) {
return to_hex((char*)sha256.hash, sizeof(sha256.hash));
}

string sha1_to_hex(const checksum160& sha1) {
return to_hex((char*)sha1.hash, sizeof(sha1.hash));
}
template <class T>
Inline void hash_combine(std::size_t& seed, const T& v) {
std::hash<T> hasher;
seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
上面的示例代码给出了 1 到 100 之间的优化随机数生成.seed1 是种子,seed2 是上面的用户种子。作为参考,Dappub 和 EOSBetCasino 开放了他们的完整合约,随机数发生器实现了玩家和开发商之间的公平骰子游戏。
uint8_t compute_random_roll(const checksum256& seed1, const checksum160& seed2) {
size_t hash = 0;
hash_combine(hash, sha256_to_hex(seed1));
hash_combine(hash, sha1_to_hex(seed2));
return hash % 100 + 1;
}
EOSBet 最近再次遭到 65,000EOS 的攻击,当一个对手欺骗他们的 eosio.token 合约时,只要他在自己的钱包之间进行交易,就将 EOS 送到他的钱包。eosio.token 合约代码 通知 EOS 代币的发送者和接收者有传入代币。为了模仿这种行为并促进黑客攻击,对手创建了两个帐户,让我们假设 A 和 B。A 与智能合约的操作有声明 require_recipient(N(eosbetdice11))。当 A 通过操作调用促进了从 A 到 B 的交易时,它通知合约中的转移功能,就好像该呼叫来自 eosio.token 合约一样。由于 EOS 没有真正转移到合约中,每当黑客输掉赌注时,他什么都没有丢失,但是在赢得赌注时他获得了奖励。因此,仅检查合约名称和操作名称是不够的。
检查合约中的通知
为了缓解这个问题,该函数应检查合约是否确实是代币的接收者。
eosio_assert(transfer_data.from == _self || transfer_data.to == _self, “Must be incoming or outgoing transfer”);
在 EOS 上制定智能合约时应遵循的最佳做法是什么?
错误是任何软件不可避免的一部分。它的后果在去中心化的环境中被放大,特别是如果它涉及价值交易。除了上面讨论的 EOS 特定保护措施之外,以下是新智能合约开发人员应该记住的一些一般预防措施和最佳实践:

在主网上发布之前,始终独立于第三方智能合约审计公司审计合约。
在发布到 testnet 之前,进行必要的 Caveman 调试(当前调试合约的唯一方法)。EOSIO 文档有一个很好的指南。
设置提款限额转移率,避免主网启动初期出现过多损失。有白帽黑客负责任披露的 bug 赏金计划。
当检测到错误时,有一个 killswitch 来冻结合约。

为了实现它,我们在 multi_index 表中保留一个标志。我们使用只能由合约所有者调用的操作来设置标志。然后我们检查每个公共行动是否将标志设置为冻结。下面给出了该函数的示例实现。
struct st_frozen {
uint64_t frozen;
};

typedef singleton<N(freeze), st_frozen> tb_frozen;
tb_frozen _frozen;

uint64_t getFreezeFlag() {
st_frozen frozen_st{.frozen = 0};
return _frozen.get_or_create(_self, frozen_st);
}

void setFreezeFlag(const uint64_t& pFrozen) {
st_frozen frozen_st = getFreezeFlag();
frozen_st.frozen = pFrozen;
_frozen.set(frozen_st, _self);
}

// public Action
void freeze() {
require_auth(_self);
setFreezeFlag(1);
}

// public Action
void unfreeze() {
require_auth(_self);
setFreezeFlag(0);
}
// any public action
void action(…) {
eosio_assert(getFreezeFlag().frozen == 1, “Contract is frozen!”);

}
随时了解库中的安全性增强或平台上的漏洞披露。必要时立即更新库。至少开源合约代码,以便在项目中保持公平性,独立开发人员可以更快地发现错误。
EOS 智能合约安全:结论
自 EOS 推出仅仅 5 个月,它已经超出了预期。它所取得的 DPOS,可变智能合约,21 个采矿节点等,当然受到权力下放极端主义者的严厉批评。然而,考虑到平台今天提供的可扩展性,它并没有阻止基于以太坊的 dApp 转向 EOS。无论是 EOS 还是以太坊赢得战争还有待确定,但 EOS 肯定赢得了这场战斗。它将保持不变,直到以太坊设法达到运行“世界计算机”所需的世界所需的可扩展性。
======================================================================
分享一些以太坊、EOS、比特币等区块链相关的交互式在线编程实战教程:

EOS 教程,本课程帮助你快速入门 EOS 区块链去中心化应用的开发,内容涵盖 EOS 工具链、账户与钱包、发行代币、智能合约开发与部署、使用代码与智能合约交互等核心知识点,最后综合运用各知识点完成一个便签 DApp 的开发。

java 以太坊开发教程,主要是针对 java 和 android 程序员进行区块链以太坊开发的 web3j 详解。

python 以太坊,主要是针对 python 工程师使用 web3.py 进行区块链以太坊开发的详解。

php 以太坊,主要是介绍使用 php 进行智能合约开发交互,进行账号创建、交易、转账、代币开发以及过滤器和交易等内容。

以太坊入门教程,主要介绍智能合约与 dapp 应用开发,适合入门。

以太坊开发进阶教程,主要是介绍使用 node.js、mongodb、区块链、ipfs 实现去中心化电商 DApp 实战,适合进阶。

C#以太坊,主要讲解如何使用 C# 开发基于.Net 的以太坊应用,包括账户管理、状态与交易、智能合约开发与交互、过滤器和交易等。

java 比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与 UTXO 等,同时也详细讲解如何在 Java 代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是 Java 工程师不可多得的比特币开发学习课程。

php 比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与 UTXO 等,同时也详细讲解如何在 Php 代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是 Php 工程师不可多得的比特币开发学习课程。

tendermint 区块链开发详解,本课程适合希望使用 tendermint 进行区块链开发的工程师,课程内容即包括 tendermint 应用开发模型中的核心概念,例如 ABCI 接口、默克尔树、多版本状态库等,也包括代币发行等丰富的实操代码,是 go 语言工程师快速入门区块链开发的最佳选择。

汇智网原创翻译,转载请标明出处。这里是原文 EOS 智能合约安全终极指南

正文完
 0