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 Mar 3, 2024. It is now read-only.
sherlock-admin opened this issue
Aug 29, 2023
· 0 comments
Labels
DuplicateA valid issue that is a duplicate of an issue with `Has Duplicates` labelHighA valid High severity issueRewardA payout will be made for this issue
Users can potentially get an unlimited amount of reward tokens from LMPVault and from DestinationVaults
Summary
Whenever a user receives vault tokens of LMPVault or of a DestinationVault (i.e. via minting from depositting assets to the vault or via transferring the vault tokens to another user), they will instantly receive reward tokens which should not happen.
Vulnerability Detail
When LMPVault._afterTokenTransfer() or DestinationVault._afterTokenTransfer() gets called, MainRewarder.stake() is being called (line 351 DestinationVault.sol, line 863 LMPVault.sol), which subsequently calls AbstractRewarder._updateReward() (line 87 MainRewarder.sol) and then AbstractRewarder.earned() (line 134 AbstractRewarder.sol)) to determine the earned rewards for the user and assign the value to rewards[account] (line 135 AbstractRewarder.sol).
Inside the AbstractRewarder.earned() method the new rewards are determined based on the balance of the user (balanceOf(account)). The AbstractRewarder.balanceOf() method returns the balance of the user in the vault (stakeTracker.balanceOf(account), line 156 AbstractRewarder.sol). At this moment the balance of the user in the vault is already the updated balance which includes the new tokens that were minted for the user or transferred to the user, since the call stack starts from LMPVault._afterTokenTransfer() or DestinationVault._afterTokenTransfer(). This means that the user will instantly receive reward tokens from the protocol directly after vault tokens from LMPVault or from a DestinationVault were minted or transferred to them, which should not happen.
AbstractRewarder.earned() returns the currently earned reward as described above and this value gets assigned to rewards[account] (line 135 AbstractRewarder.sol). If AbstractRewarder.earned() would then be called again for example to withdraw the rewards, the return value includes the accounted value from rewards[account] plus the new rewards (balance * (rewardPerToken() - userRewardPerTokenPaid[account])) that were realized meanwhile:
Alice never received any reward tokens from LMPVault or from a DestinationVault.
Alice deposits into LMPVault the first time, immediately receiving reward tokens. Because AbstractRewarder.earned() gets called subsequently from LMPVault._afterTokenTransfer(), and there userRewardPerTokenPaid[account] will be equal to 0, since it is the first time that Alice deposits into LMPVault. This means that Alice instantly receives the full amount of reward tokens calculated from AbstractRewarder.earned() based on her balance of vault tokens AFTER she depositted.
Alice transfers her LMPVault tokens to Bob, which triggers LMPVault._afterTokenTransfer() for Bob so that Bob will instantly receive reward tokens based on their new balance AFTER Alice sent them their LMPVault tokens. Bob also never received reward tokens before so that they now also receive a lot of reward tokens from their LMPVault tokens that were transferred to them.
This can be repeated by transferring the vault tokens to other wallets so that reward tokens are being generated again and again to create a potentially unlimited amount of reward tokens, by abusing this issue.
In the same way as described above, a potentially unlimited amount of reward tokens from the DesinationVaults can be gotten, since the destination vaults suffer from the same issue.
Impact
By abusing this issue a user can potentially receive an unlimited amount of reward tokens from the LMPVault and from the DestinationVaults.
Consider calling MainRewarder.getReward(to, true) inside LMPVault._beforeTokenTransfer() and DestinationVault._beforeTokenTransfer() in order to make sure that userRewardPerTokenPaid[account] (line 136 AbstractRewarder.sol) gets updated so that the user will not receive rewards for the new vault tokens that are being minted or transferred to them.
sherlock-admin2
changed the title
Perfect Dijon Leopard - Users can potentially get an unlimited amount of reward tokens from LMPVault and from DestinationVaults
lemonmon - Users can potentially get an unlimited amount of reward tokens from LMPVault and from DestinationVaultsOct 3, 2023
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` labelHighA valid High severity issueRewardA payout will be made for this issue
lemonmon
high
Users can potentially get an unlimited amount of reward tokens from
LMPVault
and fromDestinationVaults
Summary
Whenever a user receives vault tokens of
LMPVault
or of aDestinationVault
(i.e. via minting from depositting assets to the vault or via transferring the vault tokens to another user), they will instantly receive reward tokens which should not happen.Vulnerability Detail
When
LMPVault._afterTokenTransfer()
orDestinationVault._afterTokenTransfer()
gets called,MainRewarder.stake()
is being called (line 351 DestinationVault.sol, line 863 LMPVault.sol), which subsequently callsAbstractRewarder._updateReward()
(line 87 MainRewarder.sol) and thenAbstractRewarder.earned()
(line 134 AbstractRewarder.sol)) to determine the earned rewards for the user and assign the value torewards[account]
(line 135 AbstractRewarder.sol).Inside the
AbstractRewarder.earned()
method the new rewards are determined based on the balance of the user (balanceOf(account)
). TheAbstractRewarder.balanceOf()
method returns the balance of the user in the vault (stakeTracker.balanceOf(account)
, line 156 AbstractRewarder.sol). At this moment the balance of the user in the vault is already the updated balance which includes the new tokens that were minted for the user or transferred to the user, since the call stack starts fromLMPVault._afterTokenTransfer()
orDestinationVault._afterTokenTransfer()
. This means that the user will instantly receive reward tokens from the protocol directly after vault tokens fromLMPVault
or from aDestinationVault
were minted or transferred to them, which should not happen.AbstractRewarder.earned()
returns the currently earned reward as described above and this value gets assigned torewards[account]
(line 135 AbstractRewarder.sol). IfAbstractRewarder.earned()
would then be called again for example to withdraw the rewards, the return value includes the accounted value fromrewards[account]
plus the new rewards (balance * (rewardPerToken() - userRewardPerTokenPaid[account])
) that were realized meanwhile:Example:
LMPVault
or from aDestinationVault
.LMPVault
the first time, immediately receiving reward tokens. BecauseAbstractRewarder.earned()
gets called subsequently fromLMPVault._afterTokenTransfer()
, and thereuserRewardPerTokenPaid[account]
will be equal to 0, since it is the first time that Alice deposits intoLMPVault
. This means that Alice instantly receives the full amount of reward tokens calculated fromAbstractRewarder.earned()
based on her balance of vault tokens AFTER she depositted.LMPVault
tokens to Bob, which triggersLMPVault._afterTokenTransfer()
for Bob so that Bob will instantly receive reward tokens based on their new balance AFTER Alice sent them theirLMPVault
tokens. Bob also never received reward tokens before so that they now also receive a lot of reward tokens from theirLMPVault
tokens that were transferred to them.In the same way as described above, a potentially unlimited amount of reward tokens from the
DesinationVaults
can be gotten, since the destination vaults suffer from the same issue.Impact
By abusing this issue a user can potentially receive an unlimited amount of reward tokens from the
LMPVault
and from theDestinationVaults
.Code Snippet
https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/vault/LMPVault.sol#L854-L865
https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/vault/DestinationVault.sol#L345-L353
https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/rewarders/MainRewarder.sol#L86-L93
https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/rewarders/AbstractRewarder.sol#L128-L140
https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/rewarders/AbstractRewarder.sol#L204-L206
https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/rewarders/AbstractRewarder.sol#L155-L157
Tool used
Manual Review
Recommendation
Consider calling
MainRewarder.getReward(to, true)
insideLMPVault._beforeTokenTransfer()
andDestinationVault._beforeTokenTransfer()
in order to make sure thatuserRewardPerTokenPaid[account]
(line 136 AbstractRewarder.sol) gets updated so that the user will not receive rewards for the new vault tokens that are being minted or transferred to them.Duplicate of #603
The text was updated successfully, but these errors were encountered: