首页 > 世链号 > 技术分析 Lendf.me 被攻击,ERC777 到底该不该用?
币小葱  

技术分析 Lendf.me 被攻击,ERC777 到底该不该用?

摘要:Imtoken 使用 ERC777 发行 imbtc 其实是非常值得称赞的,典型的反面是 USDT (transfer 不返回值)坑了多少项目。

周末两天 Uniswap 和 Lendf.me 都发生了黑客攻击事件,都是 Defi 应用与 ERC777 组合应用导致可重入漏洞,其中导致 Lendf.me 损失抵押资产千万美元。

发生这样的事情,相信是所有从业者不愿意看到的,本文也无意针对 Lendf.me,你们也是受害者,只是看到有人甩锅给 ERC777 ,不忍从技术角度说几句公道话。要把锅全甩给 ERC777 ,是特朗普坏(甩锅给你,只因你太优秀)。

ERC777 是一个好的 Token 标准, 可以极大的提高 Defi 应用的用户体验,通过使用的 Hook 回调机制,在 ERC20 中需要二笔或多笔完成的交易(当然还有其他的特性),而使用 ERC777 单笔交易就可以完成。

对行业的发展我一直是乐观派, 如果因为本次攻击,拒绝使用 ERC777,那一定在开历史倒车。这次事件挫败了大家对 Defi 的信心, 从长远看,我相信会让行业更健康。

可重入攻击是怎么发生的?

下面我用一段简洁的代码说明可重入攻击是如何发生的(警告,以下是代码请勿使用),下面是 Defi 应用最常见的逻辑,deposit 函数用来存款,存款时会记录下用户的存款金额,withdraw 函数用来取款,取款在余额的基础上加上一个利率。

 interface IToken { function transfer(address recipient, uint256 amount) external returns (bool); function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);} contract Defi { IToken token; mapping(address => uint) balances; function deposit(uint256 amount) external { uint balance = balances[msg.sender] + amount; if(token.transferFrom(msg.sender, this, amount)){ balances[msg.sender] = balance; } } function withdraw() external { if(token.transfer(msg.sender, balances[msg.sender] + 利息)) { // 取回后余额设置为 0 balances[msg.sender] = 0; } } } 

在交互过程中,存在 3 个角色,用户、Defi 合约、Token 合约, 用户存款和取款的时序图是这样的:

技术分析 Lendf.me 被攻击,ERC777 到底该不该用?

此时一切运行正常,(经过测试后)用户在一段时间之后可以赎回 110 个 token,开开心心发布上线了。

后来上线了一个 ERC777 代币, ERC777 定义了以下两个 hook 接口:

 interface ERC777TokensSender { function tokensToSend( address operator, address from, address to, uint256 amount, bytes calldata userData, bytes calldata operatorData ) external;} 
 interface ERC777TokensRecipient { function tokensReceived( address operator, address from, address to, uint256 amount, bytes calldata data, bytes calldata operatorData ) external;} 

用来同时发送者和接收者进行相应的响应,当然发送者和接收者也可以选择不响应(不实现接口)。

ERC777 的转账实现一般类似下面这样:(transfer 和 transferFrom 实现差不多,下面用 transfer 举例)

 * 
 function transfer(address to, uint256 amount) public returns (bool) { if (有发送者接口实现) { 发送者 .tokensToSend(operator, from, to, amount, userData, operatorData); } _move(from, from, to, amount, "", ""); if (有接收者接口实现) { 接收者 .tokensReceived(operator, from, to, amount, userData, operatorData); } return true;} 

简单来说,就是在更改 发送者 和 接收者余额的前后查看是否需要通知发送者和接收者,大部分情况下,普通账号对普通账号的转账(因为普通一般不会实现接口)和 ERC20 效果上一样的。

如果发送者和接收者实现了 ERC777 的转账接口, 上面的存款调用时序图就是这样的:

技术分析 Lendf.me 被攻击,ERC777 到底该不该用?

在 Defi 合约调用 Token 的 transferFrom 时,Token 合约会调用 tokensToSend 和 tokenReceived 以便发送者和接收者进行相应的相应。注意这里 tokensToSend 由用户实现,tokenReceived 由 Defi 合约实现。

这个回调能力做很多有趣的事情,比如:可以把授权和存款合并为一笔交易,用户直接调用 token 合约的转账,Defi 合约收到转账后,在 tokenReceived 中完成用户的存款操作。

ERC777 协议没有对用户如何实现 tokensToSend 及 tokenReceived 做出规定,Defi 合约开发者也不应该对参与方的实现进行任何的假定。在 Lendf.me 的攻击案例中,黑客用户就是在 tokensToSend 的实现中,调用了 Defi 合约的 withdraw ,黑客用户合约的代码大概是这样的:

 * 
 contract Hacker { IToken token; IDefi defi; function hack() external { token.approve(defi, 100); defi.deposit(100) } function tokensToSend() external { defi.withdraw() } } 

黑客攻击的时序图如下:

技术分析 Lendf.me 被攻击,ERC777 到底该不该用?

注意 tokensToSend() 、 withdraw() 和 tokensReceived() 函数都是在 transferFrom() 中执行的,根据 deposit 的代码:

 function deposit(uint256 amount) external { uint balance = balances[msg.sender] + amount; if(token.transferFrom(msg.sender, this, amount)){ balances[msg.sender] = balance; } } 

只要前面 3 个函数没有出错,transferFrom 执行成功之后,就重置用户余额(黑客合约)为 100 (存款金额)。而实际上黑客已经把所有存款全部取出,从而实现了一次对 Defi 合约的攻击。

大家都没方法控制合约的实现,但是甩锅到 ERC777 对吗?那么对于 Defi 开发者,如何避免攻击呢?

避免 ERC777 重入攻击

其实可重入攻击一直都存在,OpenZeppelin 也给过解决方案,给 Defi 合约加上重入限制即可。

 * 
 contract Defi { bool private_notEntered; IToken token; mapping(address => uint) balances; modifier nonReentrant() { require(_notEntered, "ReentrancyGuard: reentrant call"); _notEntered = false; _; _notEntered = true; } function deposit(uint256 amount) external nonReentrant { if(token.transferFrom(msg.sender, this, amount)){ balances[msg.sender] = balances[msg.sender] + amount; } } function withdraw() external nonReentrant { if(token.transfer(msg.sender, balances[msg.sender] + 利息)) { // 取回后余额设置为 0 balances[msg.sender] = 0; } } } 

给 deposit 和 withdraw 函数加入重入限制后,此时如果在 tokensToSend 中调用 withdraw 就会败而回退交易。很明显在 Defi 合约中可以避免重入攻击。

最后希望 Lendf.me 度过难关。

来源链接:mp.weixin.qq.com
来源:Vitalik Buterin

免责声明
世链财经作为开放的信息发布平台,所有资讯仅代表作者个人观点,与世链财经无关。如文章、图片、音频或视频出现侵权、违规及其他不当言论,请提供相关材料,发送到:2785592653@qq.com。
风险提示:本站所提供的资讯不代表任何投资暗示。投资有风险,入市须谨慎。
世链粉丝群:提供最新热点新闻,空投糖果、红包等福利,微信:juu3644。