主页 > imtoken用什么id下载 > 数千个Ethereum Token合约不兼容问题浮出水面,或将严重影响DAPP生态

数千个Ethereum Token合约不兼容问题浮出水面,或将严重影响DAPP生态

imtoken用什么id下载 2023-03-21 05:42:50

如今,以太坊上已经部署了超过 70,000 个 ERC20 Token 智能合约,基于 ERC20 合约的 DAPP 发展迅速,包括去中心化交易所、资产托管、钱包等应用。 然而,SECBIT(Ambie)实验室不断发现大量ERC20 Token合约不符合EIP20规范,这些不规范的合约将对DAPP生态造成严重影响。 特别是今年4月17日以太坊智能合约语言编译器Solidity升级到0.4.22版本后,编译后的合约代码将无法兼容一些非标准的智能合约,给DAPP的开发带来困难。 大麻烦。 这个问题首当其冲的是去中心化交易所(DEX)。 部分非标准Token可能无法正常完成交易/转账,部分ERC20 Token的DAPP也会受到影响。 据SECBIT(安比)实验室不完全统计,至少有2603个ERC20合约存在这种不兼容问题。 但到目前为止,这个问题并没有引起各方的足够重视。

5 月 10 日,以太坊 Solidity 社区收到以下问题(对调用的返回数据执行 ABI 长度检查可能会中断),其中提到了编译器升级对非标准令牌合约的影响。

以太坊智能合约教程_以太坊部署合约_以太坊智能合约编写

问题解决直接:

问题描述

根据Issue中的描述信息,该问题是由于ERC20 Token合约中transfer()函数的实现没有严格遵循EIP20规范导致的。 在以太坊官方的EIP20 Token合约规范文档中以太坊部署合约,明确规定了各个接口和Event的实现。

以太坊智能合约教程_以太坊智能合约编写_以太坊部署合约

每个函数都应该包含一个返回值,其中 transfer() 函数应该返回一个 bool 值。 但实际部署的大量Token合约并没有严格按照EIP20规范执行。 如下一段知名Token合约(市值:630亿美元)的代码所示,transfer function没有返回值。

以太坊智能合约编写_以太坊部署合约_以太坊智能合约教程

Token合约中没有返回值的transfer()函数不符合EIP20合约规范。 对于普通账户直接调用transfer()函数转账的场景不会有任何影响。 但如果外部合约根据EIP20规范的ABI分析调用transfer()函数,在solidity编译器升级到0.4.22版本前合约调用不会异常。 但当合约升级到 0.4.22 时,转账函数调用将恢复。

以太坊智能合约编写_以太坊部署合约_以太坊智能合约教程

这种不符合EIP规范的写法,为什么在之前的版本中可以正常调用,编译器升级到0.4.22版本后却无法兼容?

在solidity中,一个函数调用通过签名调用合约找到对应的函数,签名是通过函数名和参数类型的哈希得到的,与返回值无关。 因此,无论是否存在同名、同参数类型返回值的函数,其签名都是相同的。

以太坊部署合约_以太坊智能合约编写_以太坊智能合约教程

以太坊智能合约编写_以太坊部署合约_以太坊智能合约教程

在call(g, a, v, in, insize, out, outsize)函数中,in和out分别是函数的输入和输出地址。 由于用户只需要为第一次使用的内存支付gas费用,通常in和out设置为同一个地址,即函数的输入输出值存放在内存中的同一个位置。 在有返回值的情况下,函数执行后,函数输出值会覆盖输入值。 如果你试图读取一个没有返回值的函数,输入的值将不会被修改,所以读取的就是输入的。

在0.4.22之前的版本中,当外部合约调用没有返回值的transfer()函数时,外部合约仍然会在内存中查询该函数的返回值。 由于没有真正的返回值,外部调用合约的返回值应该在内存中存储的相应位置查找,查找到的数据将作为返回值。 实际上,查询到的返回值并不是transfer()函数的返回值,通常是大于0的值,外部调用合约认为是true。 这个错误的返回值不会影响外部合约对 transfer() 函数的调用。 这样一来,即使没有返回值,外部合约调用transfer函数的时候也不会有问题,自然就掩盖了这个问题。

在 2017 年 10 月的拜占庭硬分叉中,以太坊采用了一系列 EIP 修改提案,包括在 EVM 层面增加 revert 和 RETURNDATASIZE 等指令操作符。 在solidity编译器0.4.22版本中,正式引入了对RETURNDATASIZE的支持。 该运算符的作用是获取被调用合约函数返回值的大小。

因此以太坊部署合约,Solidity 0.4.22编译的代码在调用外部合约时,会对函数的返回值进行校验。 如果返回值的长度小于RETURNDATASIZE中存储的长度,函数将不会被正常调用,会触发revert。 该操作符使得外部合约调用操作更加安全,但同时也导致上述无返回值的transfer()函数无法正常调用。

这个不兼容问题是什么意思?

智能合约语言Solidity编译器升级后,未来大量ERC20 Token合约将无法兼容DAPP(使用0.4.22以上Solidity编译器编译)。 这种不兼容不仅限于 transfer() 函数,还包括 transferFrom() 和 approve() 函数。

Solidity 团队的核心开发者 Christian Reitwiessner 认为,此次编译器升级非常必要。 虽然此次升级导致部分Token兼容性问题暴露,但此次升级是正确的做法。 因为根据EVM的内存布局,函数调用数据(call data)和函数返回数据(return data)共享一个内存区域。 如果transfer()函数没有调用RETURN指令返回任何值,那么如果调用者使用RETURNDATACOPY来取返回值时,内存中的脏数据就会被取回。 脏数据的值很大概率是非零值,在EVM中代表“真”。 但这里请注意,脏数据也有可能为零,代表“假”。 这意味着即使代币合约正常完成转账,也会返回“false”,导致外部DAPP误认为转账不成功,从而可能导致安全漏洞。

社区中也有人提到了这一点:

以太坊智能合约教程_以太坊部署合约_以太坊智能合约编写

假设1:如果transfer()函数没有返回值,那么调用方合约在获取RETURNDATA时,通常获取到的脏数据是函数签名的值,即该值是签名的SHA-3传输()函数。 Greek("0xa9059cbb"),也就是说,在正常情况下,这个值是非零的,相当于return true。

以太坊智能合约编写_以太坊智能合约教程_以太坊部署合约

如果以上假设成立,那么至少这个不兼容问题不会造成DAPP逻辑混乱,以后也不会有安全漏洞。 但也有不少人指出,依赖这种假设是非常危险的,会埋下安全隐患。

那么有的读者可能会问,EIP20规范中并没有提到transfer()函数的返回值为false的含义,而EIP20规范中明确指出,如果转账不成功,应该直接revert。 如下所示:

以太坊智能合约教程_以太坊部署合约_以太坊智能合约编写

但是,实际部署的合约中存在大量不符合EIP20规范的新旧合约。 这些合约在某些情况下会返回“false”,表示转账不成功。 但我们也看到 EIP20 规范对返回值含糊不清。

对于 DAPP(solidity > 0.4.22),它将面临三种不同的代币合约转移语义:

合约的transfer()函数在转账不成功时返回false

合约的transfer()函数在转账不成功时执行revert

合约的transfer()函数可以转账成功,但是由于缺少返回值,合约调用revert导致转账失败

不规范的合约会给DAPP开发带来很大的麻烦。 可以预见,随着很多DAPP的升级,将会有越来越多的ERC20 Token API调用失败(transfer()函数会因为EVM执行revert而失败)。 不兼容的Token合约数量现已达到2603个,TOP100 Token榜单中的不兼容Token数量为11个。

为什么会出现大量不符合规范的合同?

安比实验室通过对问题合约的深入分析,发现大量问题合约是参考了一些权威模板执行的,但这些权威模板也存在不兼容的问题。 其中,在业界知名的openzeppelin-solidity的早期合约中,ERC20Basic合约中的transfer()函数一直没有返回值。 版本52120a8c42(2017年3月21日),StandardToken合约改为无返回值,直到6331dd125d(2017年7月13日),修正所有合约的tansfer()函数。 所以参考这个阶段(2017年3月-2017年7月),所有参照openzeppelin合约实现的Token合约都可能存在这个问题。

以太坊智能合约教程_以太坊部署合约_以太坊智能合约编写

另外,以太坊官网提供的Token合约模板也属于问题合约模板:

以太坊智能合约编写_以太坊智能合约教程_以太坊部署合约

以下为权威模板影响的Token合约数量统计:

参考以太坊官网提供的问题合约模板(),已知有1703份

参考Open-Zeppelin的问题合约,已知合约有990个

我们如何回应?

Token方重新发布合约

DAPP开发者需要通过安全调用码接入各种不兼容ERC20的Token合约

以太坊硬分叉升级

我们认为第一种方案可以彻底解决这种不兼容问题带来的安全风险,但是重新发行如此大量的Token合约的成本极高。 第二种方案需要DAPP开发团队修改代码来处理各种不兼容问题,这对很多DAPP开发团队来说是一个很大的负担。 第三种方案可以解决假设1,去除所有Token安全风险,但硬分叉方案需要深入分析讨论,短期内难以得到妥善解决。

对于方案二,DAPP可以使用如下代码片段作为调用ERC20合约transfer()的中间层代码:

以太坊智能合约编写_以太坊智能合约教程_以太坊部署合约

以太坊智能合约教程_以太坊智能合约编写_以太坊部署合约

这段代码使用call方法直接手动调用transfer()函数,使用内联汇编代码手动获取returndatasize()进行判断。 如果为0,则表示被调用的ERC20合约已经正常执行,但没有返回值,即转账成功; 如果是32,说明ERC20合约符合标准,直接执行returndatacopy()操作,调用mload()获取返回值进行处理。 判断就够了; 如果是其他值,则还原。 封装成函数而不是传递。

完整的中间层代码还需要支持transferFrom()和approve()函数,完整代码请参考安比实验室github仓库。

总结

这种不兼容问题的根源非常复杂,涉及到很多方面,包括以太坊智能合约虚拟机EVM架构、智能合约语言Solidity编译器、Token合约EIP20规范、ERC20合约代码模板等。 任何一方都很难全面了解这个问题,这个问题是一个月前在 Solidity 社区中提出的,随后是 Christian Reitwiessner 的一篇博文和 Lukas Cremer 的几个解决方案。 Brendan Chou首先在github上提出了一个解决方案。

但经过近一个月的时间,大部分DAPP开发团队对此知之甚少,缺乏安全警惕意识。

SECBIT实验再次呼吁以太坊社区加强交流和技术推广。 同时,强烈建议项目发行方在项目启动前咨询专业的智能合约技术团队,以防后患。

跟进

发现问题后,安比实验室团队第一时间联系了多家知名去中心化交易所(DEX.top、loopring、ddex.io)。 这些团队在第一时间确认问题并更新了代码,并反复确认已经部署运行的合约不会影响交易所的功能。 团队也将这一情况反馈给了多家知名Token发行项目方,均得到及时反馈。 同时,团队在第一时间向以太坊官网提交了多个补丁修复Token问题合约模板,并得到了快速响应。 安比特实验室感谢社区的积极响应,希望更多的Token发行方和DAPP开发团队意识到这一兼容性问题,以保障DAPP生态的健康发展。

参考

[1] @chris_77367/explaining-unexpected-reverts-starting-with-solidity-0-4-22-3ada6e82308c解释从 Solidity 0.4.22 开始的意外恢复

以太坊智能合约教程_以太坊智能合约编写_以太坊部署合约

[2] 缺少返回值错误——至少有 130 个代币受到影响

[3] eip-20

[4] #L85token 进阶

[5] openzeppelin-solidity

[6] 对调用的返回数据强制执行 ABI 长度检查可能会中断

[7] #returndatasize 返回数据大小

[8] Brendan对ERC20SafeTransfer的实现

以上数据由安比实验室提供。

安比(Ambie)实验室是谁?

SECBIT(安比)实验室专注于智能合约安全问题,全面监测智能合约安全漏洞,提供专业的合约安全审计服务,对智能合约安全技术进行深入研究,致力于参与构建共识、公信力和秩序的区块链经济。

郭宇,安比(Ambie)实验室创始人,博士。 中国科学技术大学博士,耶鲁大学访问学者,毕业后在中国科学技术大学任教九年,曾任副教授,后任知名金融科技公司副总裁. 专注形式化证明和系统软件研究十余年,在金融安全行业有丰富的产品开发经验。 是国内较早关注和参与比特币和区块链技术的研究人员之一,拥有20多个项目。 区块链专利核心发明人。 研究专长:区块链技术、形式化验证、编程语言理论、操作系统内核。

安比特技术社区

info@secbit.io

以太坊智能合约编写_以太坊部署合约_以太坊智能合约教程