Liquid Staking

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

Donations can be stolen by providing just-in-time stake

Summary

The StakingPool::donateTokens() function allows users to donate tokens to liquidity providers in a staking pool. However, a vulnerability exists that enables malicious users to exploit this feature, allowing them to steal donations intended for other users.

Vulnerability Details

StakingPool::donateTokens()

The vulnerability arises from how donations are distributed among users in the staking pool. The StakingPool::donateTokens() function distributes the donated tokens proportionally to users based on the amount they have staked. A malicious user can exploit this mechanism by following these steps:

  1. Monitor the mempool to detect incoming donation transactions.

  2. Intercept the transaction by making a deposit into the staking pool just before the donation is processed.

  3. When the donation is distributed, the malicious user receives a portion of the funds despite not contributing anything meaningful.

  4. The malicious user can then withdraw their funds, having effectively stolen part of the donation intended for others.

The larger the deposit made by the malicious user, the more of donated funds they will steal.

Proof of Concept:

Add the following test in test/core/staking-pool.test.ts :

it('donation can be stolen', async () => {
const { accounts, stakingPool, stake } = await loadFixture(deployFixture)
await stake(1, 1000)//regular user
await stake(2, 9000)//A malicious user detects the donation and, before the donation transaction is made, performs a deposit
assert.equal(fromEther(await stakingPool.balanceOf(accounts[1])), 1000)
assert.equal(fromEther(await stakingPool.balanceOf(accounts[2])), 9000)
assert.equal(fromEther(await stakingPool.totalStaked()), 10000)
await stakingPool.donateTokens(toEther(1000))
assert.equal(fromEther(await stakingPool.balanceOf(accounts[1])), 1100)
assert.equal(fromEther(await stakingPool.balanceOf(accounts[2])), 9900) //The malicious user also took a part of the reward
assert.equal(fromEther(await stakingPool.totalStaked()), 11000)
})

Impact

The donation can be stolen.

Tools Used

Manual code review / Hardhat test

Recommendations

  1. A potential solution is to set a precondition that a user must have been in the pool for a certain period to receive a donation (which is complicated in the current protocol implementation and requires careful modifications).

  2. An alternative solution would be to remove the donateTokens() function and replace it with a standard staking reward approach for distributing donation assets through time.

Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Appeal created

elprofesor Submitter
about 1 year ago
inallhonesty Lead Judge
about 1 year ago
inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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