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
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
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