The stake.link protocol incorrectly calculates the amount of stLINK a user should receive when depositing LINK into the WithdrawalPool. Specifically, the issue lies in the deposit function of the WithdrawalPool contract, where the protocol uses a fixed 1:1 ratio instead of using the correct conversion mechanism based on _getSharesByStake. This can lead to improper conversion between LINK and stLINK, resulting in financial losses for users when the pool's value increases or decreases.
When a user attempts to deposit LINK into the PriorityPool, the protocol checks for available space in the Chainlink staking contracts. If there is insufficient space, the excess tokens are either queued or allocated to satisfy queued withdrawals, triggering a call to the deposit function in the WithdrawalPool:
The user initiates a deposit via the deposit function in PriorityPool:
The _deposit function tries to fulfill queued withdrawals from the WithdrawalPool. If tokens are required to finalize withdrawals, the protocol transfers LINK to the WithdrawalPool and calls its deposit function:
The issue arises in the WithdrawalPool::deposit function where a fixed 1:1 ratio is used to transfer stLINK tokens in exchange for deposited LINK:
The protocol transfers stLINK to the PriorityPool and afterwards to the user using a fixed 1:1 ratio, which is incorrect. Instead of calculating the correct number of stLINK tokens based on the pool’s current ratio of LINK to stLINK (which fluctuates due to rewards and slashing events), the function simply transfers an equal amount of stLINK to LINK deposited. The correct approach would involve using the _getSharesByStake function to calculate how many stLINK shares should be transferred based on the current pool ratio.
Consider a scenario where the pool has earned rewards and its value has increased:
Initially, the pool contains 1000 LINK and 1000 stLINK, so 1 stLINK = 1 LINK.
After rewards are distributed, the pool contains 1100 LINK but still has only 1000 stLINK, meaning 1 stLINK now equals 1.1 LINK.
A user deposits 100 LINK to the pool.
Incorrect Calculation:
The protocol transfers 100 stLINK to the user based on the fixed 1:1 ratio.
However, due to the rewards, 100 LINK should entitle the user to only ~90.91 stLINK (since 1 stLINK = 1.1 LINK).
The user receives more stLINK than they should, leading to over-allocation and diluting other stakers' shares in the pool.
Alternatively, in a negative scenario where slashing occurs and the pool’s value decreases, the user may receive less stLINK than they should, leading to financial loss.
Positive Scenario (Pool Value Increases): Users receive more stLINK than they should, leading to dilution of other stakers’ shares.
Negative Scenario (Pool Value Decreases): Users receive less stLINK than they should, resulting in financial losses for the depositor.
Manual code review
To prevent improper conversion between LINK and stLINK, replace the fixed 1:1 ratio in the WithdrawalPool::deposit function with the _getSharesByStake function. This will ensure that the correct amount of stLINK is transferred to the user based on the current pool ratio, maintaining the integrity of the staking pool and preventing financial losses.
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.