You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository has been archived by the owner on Aug 18, 2024. It is now read-only.
sherlock-admin opened this issue
Feb 16, 2024
· 1 comment
Labels
DuplicateA valid issue that is a duplicate of an issue with `Has Duplicates` labelMediumA valid Medium severity issueRewardA payout will be made for this issue
AbstractStakingAM.sol::_getRewardBalances() will fail and lead to loss of rewards and DOS
Summary
In the current implementation of AbstractStakingAM.sol it is assumed that in underlying staking contract the rewards are claimed only when _withdraw() or _claimReward() are called, which is not true for StakedStargateAM.sol. This will lead to constant reverts of _getRewardBalances(), as _getCurrentReward() will return zero, while assetState_.lastRewardGlobal will be non-zero, so uint256 deltaReward = currentRewardGlobal - assetState_.lastRewardGlobal will underflow and revert.
Vulnerability Detail
In AbstractStakingAM.sol::mint() we see that assetState[asset] is updated with value returned from _getRewardBalances() and after that _stake() is called. _getRewardBalances() calls StakedStargateAM.sol::_getCurrentReward(), which then calls Stargate's LPStakingTime.sol::pendingEmissionToken(). Let's take a look at it.
We see that in deposit function pending rewards are calculated, transferred to the user and user.rewardDent is updated. So, if we call deposit() and then call pendingEmissionToken(), we will get zero as pending rewards, as all of them were claimed in deposit().
Now let's check the following scenario:
Some funds are deposited to the contract via StakedStargateAM.sol::mint(). Now the position in Stargate's contract is non-zero, pending rewards are also non-zero. Now let's assume some time has passed and StakedStargateAM.sol::_getCurrentReward() it returns X, which is more than 0.
StakedStargateAM.sol::mint() is called again, then AbstractStakingAM.sol::_getRewardBalances(), currentRewardGlobal is X, assetState_.lastRewardGlobal is zero, so assetState_.lastRewardGlobal is updated to X.
Now, assetState.lastRewardGlobal is X, which is more than zero.
Then, in mint() function _stake() is called, which, as mentioned previously, claims all pending rewards.
Now, if AbstractStakingAM.sol::_getRewardBalances() is called, currentRewardGlobal will be less than X for a certain period of time (while rewards are accumulating), while assetState_.lastRewardGlobal is equal to X > 0. So,
Steps (2) and (3) of this scenario can be repeated many times, given that current pending rewards are non-zero. More than that, calls to increaseLiquidity(), instead of mint(), will lead to similar results.
Impact
This bug has several severe impacts:
As AbstractStakingAM.sol::_getRewardBalances() is used in calculating current value of staking asset module, for some period of time the value of many accounts` collaterals will be impossible to calculate due to reverts. (All accounts that have staked Stargate LPs in their collateral).
AbstractStakingAM.sol::_getRewardBalances() is used during withdrawal of funds from the contract, so it will also revert and users will not be able to get their staked assets or claim rewards. This DOS may last for more than a week, depending on the value of X and the value of staked assets.
As mentioned earlier, during calls to StakedStargateAM.sol::_stake() rewards are claimed, but they are not counted towards the users in AbstractStakingAM.sol::mint() and AbstractStakingAM.sol::increaseLiquidity(). This funds will be forever locked on the contract's balance. So, users lose their rewards from staking Stargate LPs, and rewards are the whole point of staking. As these rewards are from an outside integration, they can be counted as user's funds.
As mint() and increaseLiquidity() are expected to be often called by users, this problem will occur again and again, even if no malicious user try to specifically cause it.
So, regular DOS and loss of funds are main impacts of this vulnerability.
Account for claimed rewards before calls to _stake() as it is done before calls to _withdraw() in, for example, AbstractStakingAM.sol::decreaseLiquidity().
sherlock-admin2
changed the title
Shiny Emerald Hornet - AbstractStakingAM.sol::_getRewardBalances() will fail and lead to loss of rewards and DOS
pash0k - AbstractStakingAM.sol::_getRewardBalances() will fail and lead to loss of rewards and DOS
Feb 28, 2024
Sign up for freeto subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Labels
DuplicateA valid issue that is a duplicate of an issue with `Has Duplicates` labelMediumA valid Medium severity issueRewardA payout will be made for this issue
pash0k
high
AbstractStakingAM.sol::_getRewardBalances()
will fail and lead to loss of rewards and DOSSummary
In the current implementation of
AbstractStakingAM.sol
it is assumed that in underlying staking contract the rewards are claimed only when_withdraw()
or_claimReward()
are called, which is not true forStakedStargateAM.sol
. This will lead to constant reverts of_getRewardBalances()
, as_getCurrentReward()
will return zero, whileassetState_.lastRewardGlobal
will be non-zero, souint256 deltaReward = currentRewardGlobal - assetState_.lastRewardGlobal
will underflow and revert.Vulnerability Detail
In
AbstractStakingAM.sol::mint()
we see thatassetState[asset]
is updated with value returned from_getRewardBalances()
and after that_stake()
is called._getRewardBalances()
callsStakedStargateAM.sol::_getCurrentReward()
, which then calls Stargate'sLPStakingTime.sol::pendingEmissionToken()
. Let's take a look at it.When
StakedStargateAM.sol::_stake()
is called, it then calls Stargate'sLPStakingTime.sol::deposit()
. Let's take a look at it.We see that in deposit function pending rewards are calculated, transferred to the user and
user.rewardDent
is updated. So, if we calldeposit()
and then callpendingEmissionToken()
, we will get zero as pending rewards, as all of them were claimed in deposit().Now let's check the following scenario:
Some funds are deposited to the contract via
StakedStargateAM.sol::mint()
. Now the position in Stargate's contract is non-zero, pending rewards are also non-zero. Now let's assume some time has passed andStakedStargateAM.sol::_getCurrentReward()
it returns X, which is more than 0.StakedStargateAM.sol::mint()
is called again, thenAbstractStakingAM.sol::_getRewardBalances()
,currentRewardGlobal
is X,assetState_.lastRewardGlobal
is zero, soassetState_.lastRewardGlobal
is updated to X.Later in
mint()
function:assetState[asset] = assetState_;
Now,
assetState.lastRewardGlobal
is X, which is more than zero.Then, in
mint()
function_stake()
is called, which, as mentioned previously, claims all pending rewards.AbstractStakingAM.sol::_getRewardBalances()
is called,currentRewardGlobal
will be less than X for a certain period of time (while rewards are accumulating), whileassetState_.lastRewardGlobal
is equal to X > 0. So,reverts due to underflow.
Steps (2) and (3) of this scenario can be repeated many times, given that current pending rewards are non-zero. More than that, calls to
increaseLiquidity()
, instead ofmint()
, will lead to similar results.Impact
This bug has several severe impacts:
As
AbstractStakingAM.sol::_getRewardBalances()
is used in calculating current value of staking asset module, for some period of time the value of many accounts` collaterals will be impossible to calculate due to reverts. (All accounts that have staked Stargate LPs in their collateral).AbstractStakingAM.sol::_getRewardBalances()
is used during withdrawal of funds from the contract, so it will also revert and users will not be able to get their staked assets or claim rewards. This DOS may last for more than a week, depending on the value of X and the value of staked assets.As mentioned earlier, during calls to
StakedStargateAM.sol::_stake()
rewards are claimed, but they are not counted towards the users inAbstractStakingAM.sol::mint()
andAbstractStakingAM.sol::increaseLiquidity()
. This funds will be forever locked on the contract's balance. So, users lose their rewards from staking Stargate LPs, and rewards are the whole point of staking. As these rewards are from an outside integration, they can be counted as user's funds.As
mint()
andincreaseLiquidity()
are expected to be often called by users, this problem will occur again and again, even if no malicious user try to specifically cause it.So, regular DOS and loss of funds are main impacts of this vulnerability.
Code Snippet
AbstractStakingAM.sol::mint()
AbstractStakingAM.sol::_getRewardBalances()
Stargate's LPStakingTime.sol
Tool used
Manual Review
Recommendation
Account for claimed rewards before calls to
_stake()
as it is done before calls to_withdraw()
in, for example,AbstractStakingAM.sol::decreaseLiquidity()
.Duplicate of #38
The text was updated successfully, but these errors were encountered: