玩DeFi 项目如何避开“无限授权”的坑?
发布于 3 年前 作者 linbingfa 1451 次浏览 来自

今天早上看到新闻 DeFi 协议工具 Furucombo 因为用户无限授权,被盗 1500 万美金资产。正好星球用户也在提问关于合约漏洞以及用户无限授权的问题,所以写本篇文章把无限授权的前因后果给大家做一个普及。这也切身关系到每一个 DeFi 用户的资产安全。

什么是无限授权? 无限授权(Unlimited Approve)是指你授权某个合约可以转移(transfer)你地址中的某个资产(token),并且授权的上限是 Unlimited,即 uint256 (0xfff…fff)。 为什么要授权? 在 DeFi 活动中往往涉及到代币交换(Swap),例如:Uniswap 中,你将 USDT 交换成 UNI。再如:在 Comound 中,存入 USDT 资产即将你的 USDT 交换成 cUSDT。 以上例子中的行为都发生一次合约调用中,例如 Uniswap 交换 USDT 和 UNI 的行为发生在函数:swapTokensForExactTokens 中。在该函数中,Uniswap 将从你的地址中取走一定数量的 USDT,并同时将一定数量的 UNI 转给你的地址。为了能让 Uniswap 合约取走你地址中的 USDT,你需要提前向它进行授权。 也就是说,授权是让别的合约有权利取走你地址中的某个 token。这是让 DeFi 功能得以运转所必要的条件。

如何进行授权? 授权是由用户主动发送一条交易,调用被授权 token 合约的 approve 方法,填入授权目标地址,及授权金额。如下的方法:

function approve(address _spender, uint256 _value) public returns (bool success)

授权多少合适? 如果你一次性想兑换 3000 USDT,那么授权 3000 即可。那么为什么往往要授权无限呢?其中原因有二:

  1. 如果单次授权特定金额,那么再次兑换时则需要再次授权。每次授权都是一个单独的交易,都需要花费矿工费。因此无限授权可以降低矿工费成本;
  2. 不可以累加授权。即我刚授权 3000 USDT,突然临时起意,想再授权 2000 USDT,让授权总额达到 5000 USDT,是不可以的。无限授权没有上限,因此无需再累加授权。 为什么不能累加授权?

以 USDT 为例,打开它的合约源码,我们可以清楚地看到在授权的方法(approve)中,是不允许累加授权的。也就是说,当你试图授权某地址时,当前对该地址的授权数量必须是0。如下: function approve(address _spender, uint _value) public onlyPayloadSize(2 * 32) { // To change the approve amount you first have to reduce the addresses// allowance to zero by callingapprove(_spender, 0)` if it is not// already 0 to mitigate the race condition described here:// https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729require(!((_value != 0) && (allowed[msg.sender][_spender] != 0))); allowed[msg.sender][_spender] = _value;Approval(msg.sender, _spender, _value);} USDT 合约源码中require 要求 allowed[msg.sender][_spender] == 0。也就是对授权地址的当前授权量必须为0。有这样的要求,是因为一个常见的 approve 攻击引起的。详见文章《ERC20 API: An Attack Vector on Approve/TransferFrom Methods》,这里不做展开。 网址:https://docs.google.com/document/d/1YLPtQxZu1UAvO9cZ1O2RPXBbT0mooh4DYKjA_jp-RLM/ 授权的危险性 DeFi 项目方、钱包开发者、普通用户,都应该意识到授权是个非常危险的事情。这就好比,你打开了家里的保险柜,任由他人来取走你的钱财。无限授权则更是危险,这就好比你说,往后的几十年,你赚取的钱财,别人依然可以毫无障碍的取走。 而你安心这么做的原因是,你相信别人是善良的,你相信社会是路不拾遗的。也就是你相信别人的代码是善意的(你没有遇到恶意的项目)、你相信别人代码是没有漏洞(bug)的。

而很不幸,事实往往并非如此。不少用户会受到了钓鱼项目的攻击,它们是恶意的、诈骗类的项目。只要你对它们进行了无限授权,你当前的资产、以及未来还存放在该地址中的资产都将被洗劫一空。而因程序漏洞引发的安全问题,正如刚刚发生的 Furucombo 被盗事件,正在周而复始地重复发生。

解决之道 最直观的解决之道,是让用户主动取消授权。为了你账户的安全,在使用完 DeFi 后,你需要手动的发送交易取消授权。这种解决方法简单直接,但是可行性并不高。这提升了对用户的要求,而且主动取消要付出额外的矿工费,实际施行的效果较差。

第二个思路是让 DeFi 项目方在进行 approve 操作时更加审慎,从用户安全角度出发,能够减少无限授权的操作,而变成按需授权。为这个问题,我和 Compound 项目方有过沟通,但最终他们还是以过于麻烦为由不予支持。

第三种思路是对 ERC20 协议下手。于是我和 ETH 核心开发有一些交流,并提出将 approve 函数从: function approve(address _spender, uint256 _value) public returns (bool success) 改进为:

function approve(address _spender, uint256 _value, uint256 deadline) public returns (bool success) 其中 deadline 是个时间参数(可以是 timestamp 或 blockNumber),即表示该授权达到 deadline 后自动取消。这样用户再不多花手续费的情况下,至少保证 deadline 后的账户是安全的。也是一个有益的提升。 在 Github 上,我提出过相关思路,并准备在有空的时候把它整理成一条 EIP 草案: https://github.com/ethereum/EIPs/issues/3197 用户端方案 目前 DeFi 的安全性相对较差,各种无限授权横飞。从用户端出发解决以上问题,较为困难。但是最简单有效的一条经验是,使用单独的地址参与 DeFi 项目。并尽量不在该地址上存储大额 token。

DeFi 的安全提升任重而道远,保持警惕之心,科学使用钱包,不参与过小的 DeFi 项目,并且学习必要的安全知识,是保护资产安全的有效方法。

欢迎使用OKX交易所
1 回复
回到顶部