EOS智能合约安全终极指南

EOS智能合约安全终极指南。当世界上最大的ICO,EOS于2018年6月推出时,加密社区变得持怀疑态度,并且由于软件错误而被冻结了2天。但快进4个月,EOS今天占了以太网今天所做交易的两倍以上。通过免费和更快交易的承诺,EOS最顶级的Dapp每日活跃用户约为13,000,而以太网最顶级的Dapp只有2,000。

EOS智能合约安全

一些常见的智能合约漏洞几乎适用于所有平台。与以太坊一样,在EOS上编写的智能合约需要在主网上上线之前进行审核。合同中的致命错误可以在合同没有经过足够的战斗时被利用。在本指南中,我们将帮助您避免在EOS上制作下一个杀手dApp的过程中常见的陷阱。

在阅读本指南之前,了解有关EOS开发的一些先决条件信息非常重要,这些信息在您阅读本指南时会很方便。了解C ++是必须的。开始智能合约开发的最佳位置是EOSIO自己的文档

处理ABI调度员

extern “C” {
void apply(uint64_t receiver,uint64_t code,uint64_t action){
class_name thiscontract(receiver);

如果((代码== N(eosio 。令牌))&&(动作== N(转移))){
execute_action(& thiscontract,& class_name :: transfer);
回归 ;
}

if(code != receiver)返回 ;

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转发源代码中的错误而发生在dAppEOSBet Casino上的黑客攻击。

如果(代码==  自 ||代码== N(eosio 。令牌)){
TYPE thiscontract(self);
开关(动作){
EOSIO_API(类型,成员)
}
}

上面检查ABI转发源代码的apply动作处理程序允许攻击者完全绕过eosio.token :: transfer()函数,并在放置之前直接调用contract :: transfer()函数而不将EOS转移到合同中。打赌。对于损失,他没有得到任何报酬,但一无所获。然而,对于胜利,他从合同中支付了真正的EOS。

他们通过在传入的操作请求合同之前添加eosio.token合同转移操作的检查来修复上述错误。

如果(代码==  自 ||代码== N(eosio 。令牌)){
 如果(动作== N(转移)){
eosio_assert(代码== N(eosio 。令牌),“必须转移EOS”);
}
TYPE thiscontract(self);
开关(动作){
EOSIO_API(类型,成员)
}
}

使用语句require_auth(account)非常重要;进入您只想要授权帐户执行的操作。require_auth(_self);用于仅授权合同的所有者签署交易

行动中的授权

void token :: transfer(account_name from,account_name to,asset quantity)
{
auto sym =数量。符号。名称();
require_recipient(from);
require_recipient(to);
auto payer = has_auth(to)?来自
sub_balance(from,quantity);
add_balance(to,quantity,payer);
}

上面的示例代码允许任何人调用该操作。要解决它,请使用require_auth(from);声明授权付款人致电该行动​​。

尽量避免修改eosio.token合同

由于在eosio.token合同中经过严格测试的方法调用,最近一位白帽黑客成功获得了10亿令牌。DappSe7ens(现在处于非活动状态)在eosio.token合同中声明了一种新方法,用于将其令牌空投到用户帐户中。合同没叫问题或transfe的eosio.token合约以反映更改,因此奇迹般地出现在用户的账户资金- [R行动。其次,他们忘记在转移之前验证方法中的金额,这允许黑客在此过程中索取10亿个令牌。

除了更改最大供应量和令牌符号外,建议不要为自定义函数修改它,因为eosio.token合同中的错误可能是致命的。为了便于安全地进行空投,将空投标记转移到一个单独的帐户并从那里分发。

修改多索引表属性

EOS当前将数据存储在共享内存数据库中,以便跨操作共享。

struct [[eosio :: table]] person {
account_name键;
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(人),person > address_index;

上面的示例代码创建了一个名为people的multi_index表,该表基于使用structperson的该表的单行的数据结构。部署后,EOS目前不允许修改表属性。eosio_assert_message断言失败将是将被抛出的错误。因此,在部署表之前需要完全考虑属性。否则,需要创建具有不同名称的新表,并且在从旧表迁移到新表时需要特别小心。如果不这样做可能会导致数据丢失。

数值溢流检查

在进行算术运算时,如果没有足够负责地检查边界条件,则值可能会溢出,从而导致用户资产丢失。

void transfer(symbol_name symbol,account_name from,account_names to,uint64_t balance){
require_auth(从);
帐户fromaccount;
eosio_assert(is_balance_within_range(余额),“无效余额”);
eosio_assert(余额>  0,“必须转移正余额”); uint64_t amount = balance *  4 ; //乘法溢出
}

在上面的示例代码中,使用uint64_t表示用户余额可能会在值乘以时导致溢出。因此,请尽量避免使用uint64_t来表示余额并对其执行算术运算。使用eosiolib中定义的资产结构进行操作,而不是用于处理溢出条件的精确平衡。

照顾合同中的假设

在执行合同时会有假设需要断言。如果断言失败,使用eosio_assert将事先处理条件并停止执行特定操作。举个例子 -

void assert_roll_under(const uint8_t & roll_under){
eosio_assert(roll_under > =  2  && roll_under <=  96,
 “在溢出下滚动,必须大于2且小于96”);
}

上面的断言语句假设roll_under整数大于2且小于96.但如果不是,则抛出上述消息并停止执行。没有发现像上面这样的角落案件可能会成为制定规则的房子的灾难。

生成真随机数

如果没有准确完成,在EOS区块链上生成真正的随机数仍然存在风险。如果不能正确地做到这一点将导致对手预测结果,在整个过程中游戏整个系统。Oracalize.it等服务的存在是为了提供来自外部源的随机数,但它们价格昂贵且单点故障。人们过去曾使用Blockchain的上下文变量(块编号,块戳等)来生成以太坊智能合约中的随机数,但它之前已被游戏过。为了正确地进行生成,程序必须提供一种单一方无法单独控制的组合随机性。目前最好的方法之一是Dan Larimar自己在两方之间生成随机数时建议的方法。

串sha256_to_hex(常量checksum256 & SHA256){
 返回 to_hex((字符*)SHA256 。散列,的sizeof(SHA256 。散列));
}

串sha1_to_hex(常量checksum160 & SHA1){
 返回 to_hex((字符*)SHA1 。散列,的sizeof(SHA1 。散列));
}
template < class  T > 
内联void hash_combine(std :: size_t & seed,const T & v){
std :: hash < T > hasher;
种子^ = hasher(v)+  0x9e3779b9  +(种子<<  6)+(种子>>  2);
}

上面的示例代码给出了1到100之间的优化随机数生成.seed1是house种子,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,000 EOS的攻击,当一个对手欺骗他们的eosio.token合同时,只要他在自己的钱包之间进行交易,就将EOS送到他的钱包。eosio.token合同代码通知EOS令牌的发送方和接收方都有传入令牌。为了模仿行为并促进黑客行为,对手创建了两个帐户,让我们假设A和B.A与智能合约的动作有声明require_recipient(N(eosbetdice11))。当A通过动作调用促进了从A到B的交易时,它通知了转移功能在合同中,好像电话来自eosio.token合同。由于EOS没有真正转移到合同中,每当黑客输掉赌注时,他什么都没有丢失,但是在赢得赌注时他获得了奖励。因此,仅检查合同名称和操作名称是不够的。


检查合同中的通知

为了缓解这个问题,该函数应检查合同是否确实是令牌的接收者。

eosio_assert(transfer_data 。来自 == _self || transfer_data 。到== _self,“必须是传入或传出传输”);

在EOS上制定智能合约时应遵循的最佳做法是什么?

错误是任何软件不可避免的一部分。它的后果在分散的环境中被放大,特别是如果它涉及价值交易。除了上面讨论的EOS特定保护措施之外,以下是新智能合约开发人员应该记住的一些一般预防措施和最佳实践 -

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

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

struct st_frozen {
uint64_t冻结;
};

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

//公共行动
void freeze(){
require_auth(_self);
setFreezeFlag(1);
}

//公共行动
void unfreeze(){
require_auth(_self);
setFreezeFlag(0);
}
//  任何公共行动
void action(...){
eosio_assert(getFreezeFlag()。 frozen ==  1,“合同被冻结!”);
...
}
随时了解库中的安全性增强或平台上的漏洞披露。必要时立即更新库。 至少开源合同代码,以便在游戏中保持公平性,独立开发人员可以更快地发现错误。

EOS智能合约安全:结论

自EOS推出仅仅5个月,它已经超出了预期。它所做出的权衡取舍 - DPOS,可变智能合约,21个采矿节点等,当然受到权力下放极端主义者的严厉批评。尽管如此,考虑到平台今天提供的可扩展性,它还没有停止基于以太坊的dApp转向EOS。无论是EOS还是以太坊赢得战争还有待确定,但EOS肯定赢得了这场战斗。它将保持不变,直到以太坊能够达到运行“世界计算机”所需的世界所需的可扩展性。

郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。

郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。

留言与评论(共有 0 条评论)
   
验证码:
微信号已复制,请打开微信添加咨询详情!