Open howlbot-integration[bot] opened 3 months ago
This lack of a slippage protection mechanism can result in users receiving fewer stRSR tokens than anticipated.
The exchangeRate()
function is a view function that can be useful from off-chain or from other contracts. It has nothing to do with how much StRSR tokens stakers get during staking. In that case, _payoutRewards()
is called correctly: https://github.com/reserve-protocol/protocol/blob/72fc1f6e41da01e733c0a7e96cdb8ebb45bf1065/contracts/p1/StRSR.sol#L230
Warden's point is that exchangeRate() returns stale exchange rates. But I think this is intentional, as exchangeRate() is a view function and cannot call _payoutRewards() to always get the latest exchange rate. So adding slippage protection would be a better approach, but it is OK without it because the exchange rate fluctuations are mild by design. Regarding severity, unlike the findings cited by warden, the exchange rate here does not rise or fall significantly, and users can unstake to withdraw RSR. Will consider downgrading to Low
thereksfour changed the severity to QA (Quality Assurance)
thereksfour marked the issue as grade-b
Lines of code
https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/StRSR.sol#L502 https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/StRSR.sol#L621 https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/StRSR.sol#L230
Vulnerability details
Impact
The
stake()
function allowsRSR
holders to stake theirRSR
token forstRSR
token at an exchange rate of that current time. However, the lack of slippage protection can result in users receiving less than expected when stakingRSR
tokens. This occurs because thestakeRate
(i.e.exchangeRate
) is updated within thestake
functions by the_payoutRewards
function. However, users typically check theexchangeRate
using theexchangeRate()
function before staking to understand the expected amount they will receive. Since theexchangeRate()
function does not trigger_payoutRewards
, it returns a value that may change if a newstakeRate
is claculated (based on new added rewards), which leads to changes in the exchange rate when the actual staking occurs. This lack of a slippage protection mechanism can result in users receiving fewerstRSR
tokens than anticipated.Proof of Concept
Payout Reward Function: This function payouts rewards (adds to
stakeRSR
) and updates thestakeRate
. https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/StRSR.sol#L593-L627exchangeRate()
function to calculate the expectedstRSR
:https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/StRSR.sol#L502
However, the
exchangeRate
function does not call the_payoutRewards
function, which updates thestakeRate
to a new value (in case of new addedRSR
rewards).RSR
tokens, thestake()
function calls_payoutRewards
before minting newstRSR
tokens:https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/StRSR.sol#L227
This can change the
stakeRate
after the user has checked theexchangeRate
, leading to them receiving fewerstRSR
tokens than expected.stakeRate
is set asFIX_ONE
(i.e.1e18 - 1:1
),totalStakes
= 500 andstakeRSR
= 500RSR
, callsexchangeRate()
function to get the exchange rate betweenRSR
andStRSR
(note:exchangeRate()
returnsFIX_ONE
i.e.1e18
therefore, 50RSR
= 50StRSR
).stake()
function. Assume there are new reward to distribute in the contract (200RSR
), the_payoutRewards()
function re-calculates a newstakeRate
. newstakeRate
=(totalStakes(500) * FIX_ONE_256(1e18) + (stakeRSR(700) - 1)) / stakeRSR(700)
=7.14285714285714E+17
(note: now less than initialstakeRate
ofFIX_ONE
(1e18
))stakeRate
, Alice now getsStRSR
amount of =stakeRate(7.14285714285714E+17) * stakeAmount(50) = ~ 35 StRSR
(i.e. 15StRSR
less than expected)Tools Used
Manual, Foundry
Recommended Mitigation Steps
Either implement a slippage protection mechanism in the
stake
function or update theexchangeRate
function to call_payoutRewards
Reference
https://github.com/code-423n4/2023-12-revolutionprotocol-findings/issues/397#issue-2051784043
Assessed type
Other