The stake.link protocol miscalculates the amount of LINK a user can withdraw when redeeming their stLINK tokens due to incorrect handling of the stLINK to LINK conversion in the executeQueuedWithdrawals function of the PriorityPool contract. Specifically, the protocol fails to account for the rebasing nature of stLINK, leading to inaccurate withdrawals when the pool value has fluctuated. The core of the issue lies in the use of _amount as if stLINK were equivalent to LINK, resulting in incorrect amounts being transferred, as well as incorrect burning of tokens. This issue can lead to financial losses for users in positive scenarios (where the pool’s value has increased) and financial loss for the protocol in negative scenarios (where the pool’s value has decreased).
The issue occurs when the protocol processes queued withdrawals via the performUpkeep function in the WithdrawalPool contract. When a user’s withdrawal is queued, the protocol will later execute these withdrawals using the executeQueuedWithdrawals function in PriorityPool. However, the conversion between stLINK and LINK is incorrectly handled during this process. Here's the detailed flow of the issue:
Upkeep Execution in WithdrawalPool:
When upkeep is performed, the performUpkeep function in the WithdrawalPool contract is triggered. It calculates how much LINK can be withdrawn and attempts to withdraw that amount from the PriorityPool:
Incorrect Withdrawal Execution in PriorityPool:
The executeQueuedWithdrawals function in the PriorityPool contract is responsible for executing the queued withdrawals. This function calls stakingPool.withdraw() to process the withdrawals:
The issue is that _amount represents an amount of stLINK, but the protocol treats it as if it were LINK. This results in incorrect transfers and conversions, especially when the ratio of stLINK to LINK has changed due to rebasing (rewards or penalties).
Incorrect Handling of stLINK in StakingPool's withdraw:
Inside the stakingPool.withdraw() function, the amount of stLINK provided is processed without correctly converting it to the corresponding amount of LINK:
The _burn() function is then called with the incorrect _amount, treating the stLINK value as if it were LINK. This leads to an incorrect burning of shares:
The conversion function getSharesByStake is incorrectly used here, resulting in burning the wrong amount of stLINK. The correct function, getStakeByShares, must be used to calculate the correct amount of LINK that corresponds to the stLINK provided.
Impact of Incorrect Conversion:
Because of the improper conversion and handling of stLINK as if it were LINK, users receive incorrect amounts during withdrawal. In positive scenarios (when the pool value has increased), users withdraw less than they should, resulting in financial loss for users. In negative scenarios (when the pool value has decreased), users withdraw more than they should, causing financial loss for the protocol.
Let’s walk through an example of a positive scenario where the pool’s value has increased due to rewards:
The pool initially contains 1000 LINK and 1000 stLINK (1 stLINK = 1 LINK).
Due to rewards, the pool’s value increases to 1100 LINK, but the stLINK amount remains at 1000 (1 stLINK = 1.1 LINK).
A user attempts to withdraw 100 stLINK. Based on the updated ratio, they should receive 110 LINK.
Incorrect Calculation:
The protocol treats the 100 stLINK as if it were 100 LINK and transfers only 90 LINK to the user, instead of the correct 110 LINK. This results in a financial loss for the user.
Positive Scenario: When the pool value increases, users lose LINK due to incorrect conversion, as they receive fewer LINK tokens than they should.
Negative Scenario: When the pool value decreases, users withdraw more LINK than they should, resulting in financial loss for the protocol.
Manual code review
Correct Function Usage: Replace the use of getSharesByStake with getStakeByShares in both the withdraw function and the _burn function to ensure that the proper conversion between stLINK and LINK is maintained.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.