stake.link

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

The `SDLPoolSecondary::withdraw` function will be reverted if the chains are not synchronized with each other

Summary

The withdraw function in the secondary chain will be reverted while the system is not synchronized with the primary chain

Vulnerability Details

The user can stake sdl tokens on the secondary chain and these tokens will be queued using the sdlpoolSecondary::_queuewlock function, then the secondary chain sends the update of the new supply of tokens to the primary chain with the help of the function SDLPoolCCIPControllerSecondary::performUpkeep, finally the user can now execute the queued functions. On the other hand, the user can use the function SDLPoolSecondary::withdraw to be able to obtain the deposited tokens if they have not been locked.

The problem is that as long as the system is not updated (secondary chain <-> primary chain) the withdraw function will be reverted in certain circumstances. Please see the following test:

  1. The user deposits 100 tokens and the secondary chain sends the new information to the primary chain.

  2. The user deposits another 20 tokens however at this point the secondary chain does NOT send the new information to the primary chain.

  3. The user tries to withdraw his 120 tokens however the withdraw function will be reverted due to an Arithmetic operation underflowed or overflowed error.

// File: test/core/sdlPool/sdl-pool-secondary.test.ts
// $ yarn test --grep "codehawks queue and withdraw reverts"
//
it('codehawks queue and withdraw reverts', async () => {
//
// 1. Mint 100 tokens and lock false. Now balance is 100 and the effective balance is 100
await mintLock(false)
assert.equal(fromEther(await sdlToken.balanceOf(sdlPool.address)), 100)
assert.equal(fromEther(await sdlPool.totalEffectiveBalance()), 100)
assert.equal(fromEther(await sdlPool.effectiveBalanceOf(accounts[0])), 100)
assert.deepEqual(
(await sdlPool.getLockIdsByOwner(accounts[0])).map((v) => v.toNumber()),
[1]
)
//
// 2. User add 20 more tokens to his lockId (0)
await sdlToken
.connect(signers[0])
.transferAndCall(
sdlPool.address,
toEther(20),
ethers.utils.defaultAbiCoder.encode(['uint256', 'uint64'], [1, 0])
)
assert.equal(fromEther(await sdlToken.balanceOf(sdlPool.address)), 120)
assert.equal(fromEther(await sdlPool.totalEffectiveBalance()), 100)
assert.equal(fromEther(await sdlPool.effectiveBalanceOf(accounts[0])), 100)
//
// 3. The user withdrawal will be reverted by overflow
await expect(sdlPool.withdraw(1, toEther(120))).to.be.revertedWith("Arithmetic operation underflowed or overflowed outside of an unchecked block");
})

Impact

The user cannot obtain his deposited tokens in the secondary chain while the system is not updated between secondary chain and primary chain. There is no specification that the user CANNOT withdraw their tokens if the system is not updated, the user technically did not lock their tokens therefore they should be able to obtain all their tokens.

Tools used

Manual review

Recommendations

Allow the user to get all their deposited tokens even if the chains are not synchronized.

Updates

Lead Judging Commences

0kage Lead Judge almost 2 years ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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