Skip to content

Latest commit

 

History

History
77 lines (62 loc) · 2.76 KB

File metadata and controls

77 lines (62 loc) · 2.76 KB

Fast Khaki Raccoon

Medium

DecentralizedIndex::_update() runs in an infinite loop in some cases

Summary

DecentralizedIndex::_update() runs in an infinite loop in some cases

Root Cause

DecentralizedIndex::_update() handles the ERC20 update to handle fees. The issue is that in some cases, it will run an infinite recursion. Let's imagine the simplest scenario:

function _update(address _from, address _to, uint256 _amount) internal override {
        require(!_blacklist[_to], "BK");
        bool _buy = _from == V2_POOL && _to != V2_ROUTER;
        bool _sell = _to == V2_POOL;
        uint256 _fee;
        if (_swapping == 0 && _swapAndFeeOn == 1) {
            if (_from != V2_POOL) {
                _processPreSwapFeesAndSwap();
            }
            if (_buy && _fees.buy > 0) {
                _fee = (_amount * _fees.buy) / DEN;
                super._update(_from, address(this), _fee);
            } else if (_sell && _fees.sell > 0) {
                _fee = (_amount * _fees.sell) / DEN;
                super._update(_from, address(this), _fee);
            } else if (!_buy && !_sell && _config.hasTransferTax) {
                _fee = _amount / 10000; // 0.01%
                _fee = _fee == 0 && _amount > 0 ? 1 : _fee;
                super._update(_from, address(this), _fee);
            }
        }
        _processBurnFee(_fee);
        super._update(_from, _to, _amount - _fee);
    }

Tokens are sent from address(bob) to address(alice). _buy and _sell will both be false, we are not swapping and swap and fee are on, we will go in the third block (assuming transfer tax is on). We will get 0.01% of the amount, give it to address(this), then we go out of the block and process the burn fee:

function _processBurnFee(uint256 _amtToProcess) internal {
        if (_amtToProcess == 0 || _fees.burn == 0) {
            return;
        }
        uint256 _burnAmt = (_amtToProcess * _fees.burn) / DEN;
        _totalSupply -= _burnAmt;
        _burn(address(this), _burnAmt);
    }

There, we call _burn() again which calls _update() once again where _from and _to are address(this) and address(0) respectively which will bring us in the same third block, process a fee again and process the burn fees again. As seen, this will keep running in an infinite loop, eventually running out of gas.

Internal Pre-conditions

No response

External Pre-conditions

No response

Attack Path

  1. User flash mints when the transfer tax is on
  2. We have the following line there:
_burn(_msgSender(), _fee == 0 ? 1 : _fee);
  1. This will run infinitely

Impact

Some functionalities don't work properly, especially when the transfer tax is on

PoC

No response

Mitigation

Refactor the _update() function