Liquid Staking

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

Fee receivers can individually update strategy rewards to potentially steal users' rewards

Summary

The updateStrategyRewards(uint256[] memory _strategyIdxs, bytes memory _data) function can be used to update strategy rewards. However, if many strategies can be updated simultaneously, fee receivers could update them one by one to potentially steal users' rewards, resulting in fee receivers gaining more tokens and users receiving fewer tokens.

Vulnerability Details

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

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

When updateStrategyRewards(uint256[] memory _strategyIdxs, bytes memory _data) is called, the function invokes strategy.updateDeposits(_data) for each strategy in _strategyIdxs and subsequently updates the totalRewards and totalStaked variables. This process is instrumental in calculating the fees paid on the rewards. Notably, the fee distribution varies when updating all strategies simultaneously versus updating them one by one. Updating strategies individually allows fee receivers to potentially receive more rewards while users may receive fewer rewards in comparison.

Impact

  • Fee receivers can potentially receive more rewards than intended.

  • Users may receive fewer rewards than intended.

PoC

The rewards are different in the following PoC.

it('update all strategy rewards at once', async () => {
const { accounts, adrs, stakingPool, token, strategy2, erc677Receiver, stake } =
await loadFixture(deployFixture)
await stake(1, 2000)
await stake(2, 1000)
await stake(3, 2000)
await token.transfer(adrs.strategy1, toEther(1100))
await token.transfer(adrs.strategy3, toEther(500))
await strategy2.simulateSlash(toEther(400))
await stakingPool.updateStrategyRewards([0, 1, 2], '0x')
assert.equal(
fromEther(await stakingPool.balanceOf(accounts[1])),
2336,
'Account-1 balance incorrect'
)
assert.equal(
fromEther(await stakingPool.balanceOf(accounts[2])),
1168,
'Account-2 balance incorrect'
)
assert.equal(
fromEther(await stakingPool.balanceOf(accounts[3])),
2336,
'Account-3 balance incorrect'
)
assert.equal(
Number(fromEther(await stakingPool.balanceOf(accounts[4]))),
120,
'Owners rewards balance incorrect'
)
assert.equal(
Number(fromEther(await stakingPool.balanceOf(adrs.erc677Receiver))),
240,
'Delegator pool balance incorrect'
)
assert.equal(
Number(fromEther(await erc677Receiver.totalRewards()).toFixed(2)),
240,
'Delegator pool rewards incorrect'
)
assert.equal(fromEther(await stakingPool.totalSupply()), 6200, 'totalSupply incorrect')
})
it('update strategy rewards one by one', async () => {
const { accounts, adrs, stakingPool, token, strategy2, erc677Receiver, stake } =
await loadFixture(deployFixture)
await stake(1, 2000)
await stake(2, 1000)
await stake(3, 2000)
await token.transfer(adrs.strategy1, toEther(1100))
await token.transfer(adrs.strategy3, toEther(500))
await strategy2.simulateSlash(toEther(400))
await stakingPool.updateStrategyRewards([0], '0x')
await stakingPool.updateStrategyRewards([1], '0x')
await stakingPool.updateStrategyRewards([2], '0x')
assert.equal(
fromEther(await stakingPool.balanceOf(accounts[1])),
2289.0819672131147,
'Account-1 balance incorrect'
)
assert.equal(
fromEther(await stakingPool.balanceOf(accounts[2])),
1144.5409836065573,
'Account-2 balance incorrect'
)
assert.equal(
fromEther(await stakingPool.balanceOf(accounts[3])),
2289.0819672131147,
'Account-3 balance incorrect'
)
assert.equal(
Number(fromEther(await stakingPool.balanceOf(accounts[4]))),
159.0983606557377,
'Owners rewards balance incorrect'
)
assert.equal(
Number(fromEther(await stakingPool.balanceOf(adrs.erc677Receiver))),
318.1967213114754,
'Delegator pool balance incorrect'
)
assert.equal(
Number(fromEther(await erc677Receiver.totalRewards()).toFixed(2)),
320,
'Delegator pool rewards incorrect'
)
assert.equal(fromEther(await stakingPool.totalSupply()), 6200, 'totalSupply incorrect')
})

Tools Used

Manual code review

Recommendations

Consider updating all strategy rewards simultaneously to prevent discrepancies in calculations.

updateAllStrategiesRewards(bytes memory _data)
Updates

Lead Judging Commences

inallhonesty Lead Judge
10 months ago
inallhonesty Lead Judge
10 months ago
inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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