Open hats-bug-reporter[bot] opened 9 months ago
Thank you for the in-depth report.
However, this is an invalid issue. You seem to have overlooked the second condition in the convert() function that checks if the total supply of bTokens is above zero. If everyone has burned their bTokens, no additional reward will accrue.
/// @dev Update the funding reward pool balance and the tracker of collected rewards
if (bToken.totalSupply() > 0 && fundingRewardsCollected < fundingMaxRewards) {
uint256 newRewards = (FUNDING_REWARD_SHARE * AMOUNT_TO_CONVERT) / 100;
fundingRewardPool += newRewards;
fundingRewardsCollected += newRewards;
}
It is also not possible that everyone burns their bTokens and a rest of rewards remains in the Portal because it´s a proportional redeeming at all times. Therefore, by definition the last person redeeming has 100% of the total supply of bTokens and therefore receives 100% of the rewardPool.
Github username: @0xmuxyz Twitter username: -- Submission hash (on-chain): 0xe9927d9b43daab555b6cfd3863985bdb608f357a2cbbe758e0228d4b66cd3999 Severity: medium
Description:
Description
Within the Portal, the
fundingRewardPool
would be defined to store the amount of PSM available for redemption against bTokens like this: \ https://github.com/hats-finance/Possum-Labs--Portals--0xed8965d49b8aeca763447d56e6da7f4e0506b2d3/blob/5e1855411121ccd883f15c0d3c8d2fd9fc1d8e4c/contracts/Portal.sol#L119The
fundingRewardPool
would be increased until reach thefundingMaxRewards
(bToken.totalSupply()
) when theconvert()
would be called by an arbitrager like this: \ https://github.com/hats-finance/Possum-Labs--Portals--0xed8965d49b8aeca763447d56e6da7f4e0506b2d3/blob/5e1855411121ccd883f15c0d3c8d2fd9fc1d8e4c/contracts/Portal.sol#L638-L640When a funder would like to redeem their $bTokens for $PSM, the
burnBtokens()
would be called. Within theburnBtokens()
, the amount of $PSM that a funder can receive (amountToReceive
) would be determined based on the returned-value of thegetBurnValuePSM()
. \ https://github.com/hats-finance/Possum-Labs--Portals--0xed8965d49b8aeca763447d56e6da7f4e0506b2d3/blob/5e1855411121ccd883f15c0d3c8d2fd9fc1d8e4c/contracts/Portal.sol#L701 \ Then, the amount of $PSM that a funder can receive (amountToReceive
) would be subtract from thefundingRewardPool
like this: \ https://github.com/hats-finance/Possum-Labs--Portals--0xed8965d49b8aeca763447d56e6da7f4e0506b2d3/blob/5e1855411121ccd883f15c0d3c8d2fd9fc1d8e4c/contracts/Portal.sol#L707Within the
getBurnValuePSM()
, theburnValue
, which is theamountToReceive
of $PSM in theburnBtokens()
above, would be calculated and returned like this: \ https://github.com/hats-finance/Possum-Labs--Portals--0xed8965d49b8aeca763447d56e6da7f4e0506b2d3/blob/5e1855411121ccd883f15c0d3c8d2fd9fc1d8e4c/contracts/Portal.sol#L684Since the funders can redeem their $bToken for $PSM anytime and the $PSM balance in the fundingRewardPool
is gradually increased via the
convert(), there will be the **remained-$PSM balance** of the
fundingRewardPoolafter all funders redeem their $bTokens for $PSM if some funders redeem their $bTokens for $PSM **before** the $PSM balance of the
fundingRewardPoolreach the
fundingMaxRewards`.Since the $PSM balance of
fundingRewardPool
would be excluded from thereserve0
in both thebuyPortalEnergy()
and thesellPortalEnergy()
, the remained-$PSM balance of thefundingRewardPool
would not be used for thereserve0
of the internal LP like this: \ https://github.com/hats-finance/Possum-Labs--Portals--0xed8965d49b8aeca763447d56e6da7f4e0506b2d3/blob/5e1855411121ccd883f15c0d3c8d2fd9fc1d8e4c/contracts/Portal.sol#L504 https://github.com/hats-finance/Possum-Labs--Portals--0xed8965d49b8aeca763447d56e6da7f4e0506b2d3/blob/5e1855411121ccd883f15c0d3c8d2fd9fc1d8e4c/contracts/Portal.sol#L555So, this lead to that the remained-$PSM balance of the
fundingRewardPool
would be stuck in thefundingRewardPool
forever.POC (Scenario)
Assuming that:
There were 5 funders in total who contribute to funding when the bootstrapping phase.
Each funder receive 100 $bToken via the
contributeFunding()
in exchange for depositing 10 $PSM respectively (NOTE:rewardRate is 1000%) like this:Now, the
fundingMaxRewards
(bToken.totalSupply()
) is 500 (100 + 100 + 100 + 100 + 100).Example scenario:
1/ Funder(A) would call the
burnBtokens()
with 100 $bToken when the $PSM balance of thefundingRewardPool
is 100. In this case, Funder(A) will receive 20 $PSM ((100 * 100) / 500
)2/ Funder(B) would call the
burnBtokens()
with 100 $bToken when the $PSM balance of thefundingRewardPool
is 200. In this case, Funder(B) will receive 40 $PSM ((200 * 100) / 500
)4/ The $PSM balance of the
fundingRewardPool
reach thefundingMaxRewards
(500).5/ Funder(C) would call the
burnBtokens()
with 100 $bToken when the $PSM balance of thefundingRewardPool
is 500. In this case, Funder(C) will receive 100 $PSM ((500 * 100) / 500
)6/ Funder(D) would call the
burnBtokens()
with 100 $bToken when the $PSM balance of thefundingRewardPool
is 500. In this case, Funder(D) will receive 100 $PSM ((500 * 100) / 500
)7/ Funder(E) would call the
burnBtokens()
with 100 $bToken when the $PSM balance of thefundingRewardPool
is 500. In this case, Funder(E) will receive 100 $PSM ((500 * 100) / 500
)8/ Now, every funders finish to redeem all of their $bTokens balance (500 $bTokens in total) and they receive 360 $PSM in total (20 + 40 + 100 + 100 + 100).
fundingRewardPool
is140
(500 - 360
)9/ However, there is no way for the owner to rescue the remained-$PSM balance of the
fundingRewardPool
(140
). And the remained-$PSM balance of thefundingRewardPool
(140
) would not be used for thereserve0
of the internal LP.10/ As a result, the remained-$PSM balance of the
fundingRewardPool
(140
) would be stuck in thefundingRewardPool
forever.Recommendation
Consider adding a function that enable the owner to send the remained-$PSM balance of the
fundingRewardPool
to thereserve0
of the internal LP - after all funders redeemed all $bToken balance of them.