-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Accumulated rewards would be frozen when the token's index is 0
#16
Accumulated rewards would be frozen when the token's index is 0
#16
Comments
@amarcu Sponsors are not allowed to close, reopen, or assign issues or pull requests. |
@Bauchibred can you clarify this part? Is this applicable here? In PJQA please.
|
koolexcrypto marked the issue as satisfactory |
koolexcrypto marked the issue as selected for report |
koolexcrypto changed the severity to 2 (Med Risk) |
This is valid in pendle because So in the scope of Loopfi, this issue shouldn't be valid. function _updateRewardIndex()
internal
virtual
override
returns (address[] memory tokens, uint256[] memory indexes)
{
tokens = market.getRewardTokens();
indexes = new uint256[](tokens.length);
if (tokens.length == 0) return (tokens, indexes);
if (lastRewardBlock != block.number) {
// if we have not yet update the index for this block
lastRewardBlock = block.number;
uint256 totalShares = _rewardSharesTotal();
// Claim external rewards on Market
market.redeemRewards(address(vault));
for (uint256 i = 0; i < tokens.length; ++i) {
address token = tokens[i];
// the entire token balance of the contract must be the rewards of the contract
RewardState memory _state = rewardState[token];
(uint256 lastBalance, uint256 index) = (_state.lastBalance, _state.index);
uint256 accrued = IERC20(tokens[i]).balanceOf(vault) - lastBalance;
@> if (index == 0) index = INITIAL_REWARD_INDEX;
if (totalShares != 0) index += accrued.divDown(totalShares);
rewardState[token] = RewardState({
index: index.Uint128(),
lastBalance: (lastBalance + accrued).Uint128()
});
indexes[i] = index;
}
} else {
for (uint256 i = 0; i < tokens.length; i++) {
indexes[i] = rewardState[tokens[i]].index;
}
}
} |
Correct. Indexs won't have index zero |
koolexcrypto marked the issue as unsatisfactory: |
Lines of code
https://github.com/code-423n4/2024-10-loopfi/blob/d219f0132005b00a68f505edc22b34f9a8b49766/src/pendle-rewards/RewardManagerAbstract.sol#L18-L19
https://github.com/code-423n4/2024-10-loopfi/blob/d219f0132005b00a68f505edc22b34f9a8b49766/src/pendle-rewards/RewardManagerAbstract.sol#L52-L79
https://github.com/code-423n4/2024-10-loopfi/blob/d219f0132005b00a68f505edc22b34f9a8b49766/src/pendle-rewards/RewardManager.sol#L118-L133
Vulnerability details
Background
The contracts for taking care of the Pendle rewards was added to the scope for this update audit, after conversations with the sponsors it was understood that this implementation (of
RewardManagerAbstract
&RewardManager
) was inspired/forked by/from the Pendle implementation found here, problem however is that Loopfi's implementation is incomplete as it leaves out a core check which was included in the native Pendle implementation atRewardManagerAbstract.sol#L58
to ensure accumulated rewards are never blocked nor frozen.After further discussions with the sponsors it was concluded that leaving out the check (i.e using
if (userIndex == index) continue;
instead ofif (userIndex == index || index == 0) continue;
) makes the implementation vulnerable to the very same case from the Q3 Spearbit Audit of Pendle where accumulated rewards could be frozen for some tokens linked here which they had not known about prior to their audit with Code4rena.Proof of Concept
First note how the initial reward index has been set as
1
: https://github.com/code-423n4/2024-10-loopfi/blob/d219f0132005b00a68f505edc22b34f9a8b49766/src/pendle-rewards/RewardManagerAbstract.sol#L18-L19Now take a look at https://github.com/code-423n4/2024-10-loopfi/blob/d219f0132005b00a68f505edc22b34f9a8b49766/src/pendle-rewards/RewardManagerAbstract.sol#L52-L79
This function is the end method used to distribute the rewards, issue however is that in the case where the index of the token is
0
the execution attempts to deductuserIndex
from it, howeveruserIndex
can never be lower than1
, considering even if it's zero, it gets set toINITIAL_REWARD_INDEX
, which has been initialised as1
, and as such the attempt to subtractuserIndex
fromindex
, would revert here, due to having0 - x
where in this case the lowest value ofx
is1
as it would be in our case when(userIndex == 0)
is true.Now the case for having an
index == 0
is possible, this is because in some cases it is allowed to expandSY
reward tokens list which can happen after_setPostExpiryData()
has fixed the expiry state in one of theYTs
linked to thatSY
. i.e this could be due to say expanding the reward token list right after linkedYT
expiration on Pendle, as is used to actualise the same bug case here (see 5.1.1), That's to say if a new reward token was added to the Nitro pool for e.g, but not yet added to rewardTokens of anSY
, let's useCamelotV1Volatile's SY
, an attacker can run anyYT
operation involving_setPostExpiryData()
in the first block with itsexpiry <= block.timestamp
and then run publicupdateRewardTokensList()
of SY:https://github.com/pendle-finance/pendle-core-v2-public/blob/7e451be619353f57a8ab722234b8f9ebe0632836/contracts/core/StandardizedYield/implementations/CamelotV1Volatile/PendleCamelotV1VolatileSY.sol#L95C1-L104C6
The above allows anyone to add new rewardTokens to this SY if a new rewardToken is added to the Nitro pool, which would then cause for our attempt on handling rewards on withdrawal or deposits fail, cause the call inherently falls on
_updateAndDistributeRewards()
that reverts when attempting to distribute the rewards here.Impact
Handling rewards on withdrawal or deposits would fail for that specific
YT
, cause the call inherently falls on_updateAndDistributeRewards()
that reverts when attempting to distribute the rewards via_distributeRewardsPrivate()
here.Resources:
5.1.1
from Spearbit's Q3 2024 Pendle audit.Recommended Mitigation Steps
As hinted in Spearbit's Q3 2024 Pendle audit consider ignoring indices that are not initialised, this way we never have the accumulated rewards being stuck, i.e
Which has also been implemented by Pendle here.
Assessed type
Token-Transfer
The text was updated successfully, but these errors were encountered: