Skip to content

Instantly share code, notes, and snippets.

@0xcuriousapple
Last active June 27, 2023 23:44
Show Gist options
  • Save 0xcuriousapple/5b48f9d8072b15cd5b0c5371398df0f3 to your computer and use it in GitHub Desktop.
Save 0xcuriousapple/5b48f9d8072b15cd5b0c5371398df0f3 to your computer and use it in GitHub Desktop.

One can freeze the COIL staking/unstaking using underflow

Description

Coil Staking 
Contract at 0x6701E792b7CD344BaE763F27099eEb314A4b4943

  function stake(uint256 amount_) external {
        rebase(); 
        Coil.safeTransferFrom(msg.sender, address(this), amount_);
        Spiral.mint( msg.sender, (amount_*initialIndex) / index );
        lastStake[msg.sender] = epoch.number;
    }

    function unstake(uint256 amount_) external {
        rebase();
        Spiral.safeBurnFrom(msg.sender, amount_);
        Coil.safeTransfer( msg.sender, (amount_ * index) / initialIndex );
    }

SpiralDAO has a COIL staking contract where they allow stakers to stake their COIL in return for SPR tokens.
COIL token is inflationary, and SPR represents, the ownership of COIL and COIL inflation from the staking contract.

Coil Staking 
Contract at 0x6701E792b7CD344BaE763F27099eEb314A4b4943

 function rebase() public {
        while( epoch.endBlock <= block.number ) { // #FX: SGN-01M
            uint256 totalSpiral = Spiral.totalSupply();
            ---
            if (totalSpiral > 0 && Coil.balanceOf(address(this)) > 0 && epoch.apr > 0){
                Coil.mint(address(this), (totalSpiral * index / initialIndex) - Coil.balanceOf(address(this))); // @audit underflow
            }
            ----
        }
    }

For both of stake and unstake, rebase is done before to calculate the current index.
However there is a bug in rebase, allowing anyone with enough COIL funds to block/freeze stake/unstake.
If you check the mint action of COIL inside rebase, total tokens minted are the difference between (totalSpiral * index / initialIndex) and Coil.balanceOf(address(this).
As per the Spiral Team's response in audit report by pessimistic for ISSUE M04, spiral tokens could not be minted externally, hence its total supply can not be manipulated.
However what if instead of staking COIL through stake, user directly transfers COIL to COIL staking contract ?
This would inflate the balance of COIL for the staking contract without impacting the total spiral.
Hence by transffering enough amount of COIL to the contract, one can cause underflow, and thereby block rebase, stake and unstake.
To block given epoch, the amount attacker needs to transfer = the reward inflation for that epoch.
To block n number of epochs it needs to be n * reward inflation per epoch.

It is true that after some time, once index calculation increases with subquecent epochs, the staking contract can become solvent again, however it could again be blocked using direct transfer of COIL again.

Doing so attacker can block all COIL inflationary rewards and can 🛑 freeze 🛑 the assets of existing stakers since they cant do unstake

In terms of numbers, for freezing unstake,
At the time of writing, 3755999_637576305018593060 COIL are staked inside staking contract, all of which could be frozen.
COIL Token address: 0x823E1B82cE1Dc147Bbdb25a203f046aFab1CE918 image At the time of the writing, the total supply of COIL is 4476886776527413148671334.

Let me know your github handles, if you would like POC, will share access to private repo

Recommedation

Consider skipping COIL mint if Coil.balanceOf(address(this))) > (totalSpiral * index / initialIndex)
Doing so would consider the direct transfer as donation, and wont impact any other users

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment