从技术角度解析:为什么通缩机制的代币易受攻击

通常,具有燃烧机制的代币将在其 _transfer 函数中实现燃烧的逻辑。有时候会存在发送者承担手续费的情况。 在这种情况下,接收方收到的代币数量不会发生变化,但发送方需要支付更多代币,因为其需要承担手续费。

概述

在区块链上具有通缩机制的代币最近经常受到攻击。本文将讨论并分析代币令牌受到攻击的原因,并给出相应的防御方案。

在代币中实现通缩机制通常有两种方式,一种是燃烧机制,另一种是反射机制。下面我们将分析这两种实现方式可能存在的问题。

燃烧机制

通常,具有燃烧机制的代币将在其 _transfer 函数中实现燃烧的逻辑。有时候会存在发送者承担手续费的情况。 在这种情况下,接收方收到的代币数量不会发生变化,但发送方需要支付更多代币,因为其需要承担手续费。 下面是一个简单的例子:

function _transfer(address sender, address recipient, uint256 amount) internal virtual returns (bool) {

require(_balances[sender] >= amount, "ERC20: transfer amount exceeds balance");

require(sender != address(0), "ERC20: transfer from the zero address");

require(recipient != address(0), "ERC20: transfer to the zero address");

burnFee = amount * burnFeeRate;

_balances[sender] -= amount;

_burn(sender, burnFee);

_balances[recipient] += amount;

}

然后我们讨论这种情况下可能存在的风险。

如果单看代币合约,我们会发现这种写法其实没有什么问题,但是区块链中有很多复杂的情况,需要我们考虑很多方面。

通常,为了让代币有价格,项目方会在 Uniswap、Pancakeswap 等去中心化交易所为代币添加流动性。

其中,在 Uniswap 中,有一个函数 skim,它会将流动性池中两种代币的余额和储备金的差值转移给调用方,以平衡余额和储备金:

function skim(address to) external lock {

address _token0 = token0; // gas savings

address _token1 = token1; // gas savings

_safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)).sub(reserve0));

_safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)).sub(reserve1));

}

此时发送方变成了流动性池,当调用 _transfer 时,流动性池中的代币将被部分销毁,导致代币价格部分上涨。

攻击者利用此特性将代币直接转入流动性池中,然后调用 skim 函数转出,然后多次重复此操作,导致流动性池中大量代币被燃烧,价格也随之飙升, 最后卖出代币获利。

一个真实的攻击案例,winner doge (WDOGE) :

function _transfer(address sender, address recipient, uint256 amount) internal virtual returns (bool) {

require(_balances[sender].amount >= amount, "ERC20: transfer amount exceeds balance");

require(sender != address(0), "ERC20: transfer from the zero address");

require(recipient != address(0), "ERC20: transfer to the zero address");

?

if(block.timestamp >= openingTime && block.timestamp <= closingTime)

{

_balances[sender].amount -= amount;

_balances[recipient].amount += amount;

emit Transfer(sender, recipient, amount);

}

else

{

uint256 onePercent = findOnePercent(amount);

uint256 tokensToBurn = onePercent *4;

uint256 tokensToRedistribute = onePercent * 4;

uint256 toFeeWallet = onePercent*1;

uint256 todev = onePercent* 1;

uint256 tokensToTransfer = amount - tokensToBurn - tokensToRedistribute - toFeeWallet-todev;

?

_balances[sender].amount -= amount;

_balances[recipient].amount += tokensToTransfer;

_balances[feeWallet].amount += toFeeWallet;

_balances[dev].amount += todev;

if (!_balances[recipient].exists){

_balanceOwners.push(recipient);

_balances[recipient].exists = true;

}

?

redistribute(sender, tokensToRedistribute);

_burn(sender, tokensToBurn);

emit Transfer(sender, recipient, tokensToTransfer);

}

return true;

}

在 WDOGE 合约的_transfer 函数中,当 block.timestamp > closingTime 时,进入 else 循环。 在代码第 21 行中,转账金额从发送方的余额中扣除,在代码第 31 行中,发送方又被燃烧了 tokensToBurn 数量的代币。 攻击者利用这种手续费的机制,通过上述的攻击方式窃取流动性池中的所有价值代币 (WBNB)。

从技术角度解析:为什么通缩机制的代币易受攻击

反射机制

在反射机制中,用户每次交易都会收取手续费,用于奖励持有代币的用户,但不会触发转账,只是单纯修改一个系数。

在这个机制中,用户有两种类型的代币数量,tAmount 和 rAmount。 tAmount 为实际代币数量,rAmount 为反映后的代币数量,比率为 tTotal / rTotal,一般的代码实现如下:

function balanceOf(address account) public view override returns (uint256) {

if (_isExcluded[account]) return _tOwned[account];

return tokenFromReflection(_rOwned[account]);

}

function tokenFromReflection(uint256 rAmount) public view returns(uint256) {

require(rAmount <= _rTotal, "Amount must be less than total reflections");

uint256 currentRate = _getRate();

return rAmount.div(currentRate);

}

function _getRate() private view returns(uint256) {

(uint256 rSupply, uint256 tSupply) = _getCurrentSupply();

return rSupply.div(tSupply);

}

反射机制的代币中一般有一个叫做 deliver 的函数,会销毁调用者的代币,降低 rTotal 的值,所以比率会增加,其他用户反射后的代币数量也会增加:

function deliver(uint256 tAmount) public {

address sender = _msgSender();

require(!_isExcluded[sender], "Excluded addresses cannot call this function");

(uint256 rAmount,,,,,) = _getValues(tAmount);

_rOwned[sender] = _rOwned[sender].sub(rAmount);

_rTotal = _rTotal.sub(rAmount);

_tFeeTotal = _tFeeTotal.add(tAmount);

}

攻击者注意到这个函数,并用它来攻击相应的 Uniswap 的流动性池。

那他该如何进行利用呢? 同样从 Uniswap 的 skim 函数开始:

function skim(address to) external lock {

address _token0 = token0; // gas savings

address _token1 = token1; // gas savings

_safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)).sub(reserve0));

_safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)).sub(reserve1));

}

Uniswap 中 reserve 是储备金,与 token.balanceOf(address(this)) 不同。

攻击者先调用 deliver 函数销毁自己的代币,导致 rTotal 的值减少,比率随之增加,所以反射后的代币的值也会增加,token.balanceOf(address(this)) 也会相应变大,与 reserve 的值出现了差距。

因此,攻击者可以通过调用 skim 函数转出数量为两者之间差值的代币从而进行获利。

Attacker: token.deliver

rtotal: decrease

rate: increase

tokenFromReflection: increase

balanceOf: increase -> token.balanceOf(address(this)) > reserve

Attacker: pair.skim

token.balanceOf(address(this)) > reserve

token.transfer

一个真实的攻击案例,BEVO NFT Art Token (BEVO):

从技术角度解析:为什么通缩机制的代币易受攻击

而当代币合约中存在 burn 函数时,存在了另外一种相似的攻击手法:

function burn(uint256 _value) public{

_burn(msg.sender, _value);

}

?

function _burn(address _who, uint256 _value) internal {

require(_value <= _rOwned[_who]);

_rOwned[_who] = _rOwned[_who].sub(_value);

_tTotal = _tTotal.sub(_value);

emit Transfer(_who, address(0), _value);

}

当用户调用 burn 函数时,自己的代币会被销毁,同时 tTotal 的值会减少,所以比率会降低,对应的反射后的代币数量也会减少,所以在此时流动性池的代币的数量也会减少,从而代币的价格会上涨。

攻击者利用这个特性通过多次调用 burn 函数来减少 tTotal 的值,然后调用流动性池的 sync 函数同步 reserve 和 balances。最后,流动性池中的代币大幅减少,价格飙升。 然后攻击者出售代币以获取利润。

Attacker: token.burn

tTotal: decrease

rate: decrease

tokenFromReflection: decrease

balanceOf: decrease

Attacker: pair.sync

token.balanceOf(address(this)) > reserve

token.transfer

一个真实的攻击案例,Sheep Token (SHEEP):

从技术角度解析:为什么通缩机制的代币易受攻击

防御方案

通过解读针对燃烧机制和反射机制代币的攻击手法,不难发现攻击者攻击的核心点是操纵流动性池的价格,因此将流动性池的地址加入白名单 ,不涉及代币的销毁,不参与代币的反射机制,可以避免此类攻击。

总结

本文分析了通缩机制代币的两种实现机制以及针对这两种机制的攻击手段,最后给出了相应的解决方案。 在编写合约时,项目方必须考虑代币与去中心化交易所结合的情况,以避免此类攻击。

(声明:请读者严格遵守所在地法律法规,本文不代表任何投资建议)

(1)
上一篇 2023年3月9日 上午11:44
下一篇 2023年3月9日 下午12:36

相关推荐

  • Unibot遭遇黑客入侵,Telegram用户该如何保障资产安全?

    Unibot 承认在 10 月 31 日遭受了攻击,原因是在新路由器中出现了代币批准的漏洞。Unibot 官方发布公告称:「在新路由器中出现了代币批准的漏洞,Unibot 已暂停了新路由器以解决这个问题。由于新路由器的错误而造成的任何资金损失都将得到补偿;用户的密钥和钱包是安全的,将在调查结束后发布详细回应。」

    2023年11月3日
    327
  • 加密市场反思:回归常识,以理性眼光审视市场乱象

    大多数时候,市场并不关心精确的基本面计算,因为我们谈论的是一个几乎没有任何现有消费行为的新兴链上原生经济(以太坊)。大多数 ETH 上的项目存在的目的是通过投机活动燃烧 Gas,并且是以太坊的网络效应的衍生物,对其并没有真正的净增值。

    2023年8月19日
    464
  • 空投爱好者必备的开发知识有哪些?

    此前我们的文章中阐述了区块链的属性定义,区块链不只是一个总帐本,今天存在的所有公链的设计都是为了表层的应用生长。所以理解链的开发知识,和本身互联网开发应用一致,在架构理解上,只是后端都变成了链,原本在数据库中的数据状态变成了链上的数据状态。

    2024年3月21日
    0

发表回复

登录后才能评论
微信

联系我们
邮箱:whylweb3@163.com
微信:gaoshuang613