stake.link

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

Lost of`boostAmount` when updating a Lock.

Summary

When a staker calls the extend extendLockDuration function to extend their lock duration, the initial boostAmount get overwritten by the new boostAmount, causing the user to lose boostAmount for the initial period that his stakes were locked.

This also happens when they increase lock amount, the boostAmount with the previous amount and duration get overwritten.

The root cause of this Bug comes from the _updateLock function.

Vulnerability Details

SDLPool.sol: calcutates boostAmount as

Lock memory lock = Lock(_lock.amount, _lock.boostAmount, _lock.startTime, _lock.duration, _lock.expiry);
uint256 baseAmount = _lock.amount + _amount;
uint256 boostAmount = boostController.getBoostAmount(baseAmount, _lockingDuration);

This cause a problem as the boostAmount for the initial lockperiod get overwritten with the new boostAmount for Example.

  • Suppost initial lock duration was 90days

  • Then after 60days, the staker decides to extend lock duration by 120days, making the total lock duration to be 60days + 120day = 180days.

  • The protocol calculates the boostAmount for 120days and ignores the 60days that have been already elapsed making the staker to lose 60days worth of boostAmount

Impact

Updating Lock leads to lose in profit for staker.

POC

Add this test to this file and run the following command

yarn test ./test/core/sdlPool/sdl-pool-primary.test.ts --grep "POC"
describe("POC",async () => {
it('Updating Lock Durations', async () => {
await sdlToken.transferAndCall(
sdlPool.address,
toEther(100),
ethers.utils.defaultAbiCoder.encode(['uint256', 'uint64'], [0, 0])
)
await sdlPool.extendLockDuration(1, 90 * DAY)
let ts = (await ethers.provider.getBlock(await ethers.provider.getBlockNumber())).timestamp
const maxLockDuration = (365 * 4) * DAY
const boostAmountInitial = Number((100 * 90 * 4 * DAY / maxLockDuration).toFixed(4))
assert.equal(fromEther(await sdlPool.totalEffectiveBalance()), 124.65753424657534)
assert.equal(fromEther(await sdlPool.totalStaked()), 124.65753424657534)
assert.equal(fromEther(await sdlPool.effectiveBalanceOf(accounts[0])), 124.65753424657534)
assert.equal(fromEther(await sdlPool.staked(accounts[0])), 124.65753424657534)
assert.deepEqual(parseLocks(await sdlPool.getLocks([1])), [
{ amount: 100, boostAmount: boostAmountInitial, startTime: ts, duration: 90 * DAY, expiry: 0 },
])
await time.increase(60 * DAY)
const boostAmountFinal = Number((100 * 180 * 4 * DAY / maxLockDuration).toFixed(4))
await sdlPool.extendLockDuration(1, 120 * DAY)
ts = (await ethers.provider.getBlock(await ethers.provider.getBlockNumber())).timestamp
assert.deepEqual(parseLocks(await sdlPool.getLocks([1])), [
{ amount: 100, boostAmount: boostAmountFinal , startTime: ts, duration: 120 * DAY, expiry: 0 },
])
})
})

Tools Used

Manual Analysis and Hardhat

Recommendations

Added the following codes snippet to the _updateLock function

function _updateLock(
Lock memory _lock,
uint256 _amount,
uint64 _lockingDuration
) internal view returns (Lock memory) {
if ((_lock.expiry == 0 || _lock.expiry > block.timestamp) && _lockingDuration < _lock.duration) {
revert InvalidLockingDuration();
}
Lock memory lock = Lock(_lock.amount, _lock.boostAmount, _lock.startTime, _lock.duration, _lock.expiry);
uint256 baseAmount = _lock.amount + _amount;
uint256 boostAmount = boostController.getBoostAmount(baseAmount, _lockingDuration);
+ if (lock.duration > 0) {
+ boostAmount = boostAmount + (lock.boostAmount * (uint64(block.timestamp) - lock.startTime) / lock.duration);
+ }
if (_lockingDuration != 0) {
lock.startTime = uint64(block.timestamp);
} else {
delete lock.startTime;
}
lock.amount = baseAmount;
lock.boostAmount = boostAmount;
lock.duration = _lockingDuration;
lock.expiry = 0;
return lock;
}
Updates

Lead Judging Commences

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.