Skip to content
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

boostedValue should be added to permanentTotalSupply for permanently locked tokens #11

Open
c4-bot-7 opened this issue Sep 25, 2024 · 17 comments
Labels
2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value bug Something isn't working downgraded by judge Judge downgraded the risk level of this issue M-04 primary issue Highest quality submission among a set of duplicates 🤖_06_group AI based duplicate group recommendation satisfactory satisfies C4 submission criteria; eligible for awards selected for report This submission will be included/highlighted in the audit report sponsor confirmed Sponsor agrees this is a problem and intends to fix it (OK to use w/ "disagree with severity") upgraded by judge Original issue severity upgraded from QA/Gas by judge

Comments

@c4-bot-7
Copy link
Contributor

Lines of code

https://github.com/code-423n4/2024-09-fenix-finance/blob/main/contracts/core/VotingEscrowUpgradeableV2.sol#L470

Vulnerability details

Impact

Unlocking tokens from the permanent locking can be DoSed.

Proof of Concept

In the VotingEscrowUpgradeableV2._processLockChange function, boostedValue is added to the token's locked amount at L465. However, boostedValue is not added to permanentTotalSupply for permanently locked tokens at L470.

File: contracts\core\VotingEscrowUpgradeableV2.sol
465:@>       newLocked.amount += LibVotingEscrowUtils.toInt128(boostedValue);
466:         uint256 diff = LibVotingEscrowUtils.toUint256(newLocked.amount - oldLocked_.amount);
467:         uint256 supplyBefore = supply;
468:         supply += diff;
469:         if (newLocked.isPermanentLocked) {
470:@>           permanentTotalSupply += amount_;
471:         }

In the unlockPermanent function, the token's locked amount is subtracted from permanentTotalSupply at L219.

File: contracts\core\VotingEscrowUpgradeableV2.sol
219:         permanentTotalSupply -= LibVotingEscrowUtils.toUint256(state.locked.amount);

As a result, calling this function may be reverted by the underflow.

Let's consider the following scenario:

  1. Alice and Bob each create locks with 100 FNX for a 1-week duration (< veBoostCached.getMinLockedTimeForBoost).
  2. Alice and Bob lock their tokens permanently: permanentTotalSupply = 100 + 100 = 200.
  3. Alice deposits 1000 FNX (>= veBoostCached.getMinFNXAmountForBoost), and _boostFNXPercentage is 1000 (10%):
    • boostedValue: 100
    • total locked amount: 100 + 1000 + 100 = 1200
    • permanentTotalSupply: 200 + 1000 = 1200
  4. Alice unlocks her token from the permanent lock: permanentTotalSupply = 1200 - 1200 = 0.
  5. Bob tries to unlock his token from the permanent lock, but it is reverted because permanentTotalSupply is 0.

Tools Used

Manual Review

Recommended Mitigation Steps

It is recommended to change the code in the VotingEscrowUpgradeableV2._processLockChange function as following:

Let me know if you need further assistance!

        if (newLocked.isPermanentLocked) {
-           permanentTotalSupply += amount_;
+           permanentTotalSupply = permanentTotalSupply + amount_ + boostedValue;
        }

Assessed type

DoS

@c4-bot-7 c4-bot-7 added 3 (High Risk) Assets can be stolen/lost/compromised directly bug Something isn't working labels Sep 25, 2024
c4-bot-6 added a commit that referenced this issue Sep 25, 2024
@c4-bot-12 c4-bot-12 added the 🤖_06_group AI based duplicate group recommendation label Sep 25, 2024
@c4-judge c4-judge added the primary issue Highest quality submission among a set of duplicates label Sep 26, 2024
@c4-judge
Copy link

alcueca marked the issue as primary issue

@b-hrytsak
Copy link

Hi, thanks for your submission.

Indeed, after refactoring and changes, this point, although known, was missed in the new code

duplicate #6 , or #6 is a duplicate of #11

It is difficult to understand the severity of this issue, it seems to be Overseverity. If we go with the worst-case scenario, the last user/users will not be able to unlock the permanent lock on their veNFTs, which will lead them to some additional temporary lock until the problem is resolved, as they would have to wait 182 days for full unlocking anyway

Thank you for your participation

@b-hrytsak b-hrytsak added the sponsor confirmed Sponsor agrees this is a problem and intends to fix it (OK to use w/ "disagree with severity") label Sep 30, 2024
@c4-judge
Copy link

alcueca changed the severity to 2 (Med Risk)

@c4-judge c4-judge added 2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value downgraded by judge Judge downgraded the risk level of this issue and removed 3 (High Risk) Assets can be stolen/lost/compromised directly labels Sep 30, 2024
@c4-judge
Copy link

alcueca marked the issue as duplicate of #6

@c4-judge c4-judge added duplicate-6 and removed primary issue Highest quality submission among a set of duplicates labels Sep 30, 2024
@c4-judge
Copy link

alcueca marked the issue as satisfactory

@c4-judge c4-judge added satisfactory satisfies C4 submission criteria; eligible for awards and removed duplicate-6 labels Sep 30, 2024
@c4-judge c4-judge reopened this Sep 30, 2024
@c4-judge c4-judge added primary issue Highest quality submission among a set of duplicates selected for report This submission will be included/highlighted in the audit report labels Sep 30, 2024
@c4-judge
Copy link

alcueca marked the issue as selected for report

@KupiaSecAdmin
Copy link

@alcueca Thanks for your judging.
I think this is high severity.
This vulnerability leads not only to a DoS but also to an incorrect calculation of voting power in the _balanceOfNFT function due to the incorrect accumulation of permanentTotalSupply.

File: contracts\core\VotingEscrowUpgradeableV2.sol
532:     function _checkpoint(uint256 tokenId_, LockedBalance memory oldLocked_, LockedBalance memory newLocked_) internal {
             [...]
616:@>       last_point.permanent = LibVotingEscrowUtils.toInt128(permanentTotalSupply);
File: contracts\core\VotingEscrowUpgradeableV2.sol
647:     function _balanceOfNFT(uint256 tokenId_, uint256 timestamp_) internal view returns (uint256 balance) {
649:         if (pointEpoch > 0) {
650:             Point memory lastPoint = nftPointHistory[tokenId_][pointEpoch];
651:             if (lastPoint.permanent > 0) {
652:@>               return LibVotingEscrowUtils.toUint256(lastPoint.permanent);

@alcueca
Copy link

alcueca commented Oct 3, 2024

This vulnerability leads ... also to an incorrect calculation of voting power

This was not pointed out in the original submission, but it is right.

@c4-judge c4-judge added 3 (High Risk) Assets can be stolen/lost/compromised directly and removed 2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value downgraded by judge Judge downgraded the risk level of this issue labels Oct 3, 2024
@c4-judge c4-judge added the upgraded by judge Original issue severity upgraded from QA/Gas by judge label Oct 3, 2024
@c4-judge
Copy link

c4-judge commented Oct 3, 2024

alcueca changed the severity to 3 (High Risk)

@b-hrytsak
Copy link

@KupiaSecAdmin @alcueca

In the balanceOfNFT calculation, data regarding the permanentTotalSupply is not used, and the impact of not accounting boostedValue in permanentTotalSupply is limited only to the calculation of the total voting power. This does not affect on votes proccesing, but only the outcome (votingPowerTotalSupply()). The voting power for a user's veNFT will still be calculated correctly.

This statement most likely arose because similar structures and pieces of code are used for the general voting power calculation and for the user. However, last_point in _checkpoint is from supplyPointsHistory, whereas in balanceOfNFT, nftPointHistory is used.

@Ch-301
Copy link

Ch-301 commented Oct 3, 2024

@alcueca Thanks for your judging. I think this is high severity. This vulnerability leads not only to a DoS but also to an incorrect calculation of voting power in the _balanceOfNFT function due to the incorrect accumulation of permanentTotalSupply.

File: contracts\core\VotingEscrowUpgradeableV2.sol
532:     function _checkpoint(uint256 tokenId_, LockedBalance memory oldLocked_, LockedBalance memory newLocked_) internal {
             [...]
616:@>       last_point.permanent = LibVotingEscrowUtils.toInt128(permanentTotalSupply);
File: contracts\core\VotingEscrowUpgradeableV2.sol
647:     function _balanceOfNFT(uint256 tokenId_, uint256 timestamp_) internal view returns (uint256 balance) {
649:         if (pointEpoch > 0) {
650:             Point memory lastPoint = nftPointHistory[tokenId_][pointEpoch];
651:             if (lastPoint.permanent > 0) {
652:@>               return LibVotingEscrowUtils.toUint256(lastPoint.permanent);

hey @c4-judge

I believe there is some wrong assumption in this issue

tl;dr
the last_point.permanent and lastPoint.permanent are not the same thing in this logic.

last_point.permanent is related to supplyPointsHistory[] this mapping which is tracking the total supply changes.

However, lastPoint.permanent that used in _balanceOfNFT() is from nftPointHistory[][] this mapping which is recording the changes over time for every veNFT.

and the value of lastPoint.permanent is updated here

            u_new.permanent = permanent;
            nftPointHistory[tokenId_][nftStates[tokenId_].pointEpoch] = u_new;
        }

Which is acutely only this amount here

The impact is more like this QA (nb: not dupl) last user can't call unlockPermanent() successfully

@KupiaSecAdmin
Copy link

There is a confunsion for two variables and I agree there is no incorrect calculation of voting power by the permanentTotalSupply.
But there still exists DoS vulnerability.

@Ch-301
Copy link

Ch-301 commented Oct 3, 2024

There is a confunsion for two variables and I agree there is no incorrect calculation of voting power by the permanentTotalSupply. But there still exists DoS vulnerability.

I'm not sure if we can call this denial-of-service, because only the last permanent-lock is affected by losing his locked FNX tokens!

@alcueca
Copy link

alcueca commented Oct 3, 2024

I think the last permanent lock being affected reasonably often merits a medium severity. Thanks @KupiaSecAdmin for retracting your previous statement about permanentTotalSupply .

@C4-Staff C4-Staff added the H-01 label Oct 4, 2024
@nnez
Copy link

nnez commented Oct 4, 2024

Sorry if my input might be too late for this but maybe this could be helpful regarding the acutal impact of this.

I actually described the impact @Ch-301 mentioned in my original finding here: #6

Although, it's not always the last withdrawal that's gonna have a problem.
For instance, let's say there are 2 existing permanent locks:

  • Lock A = 10
  • Lock B = 10
  • permanentTotalSupply = 20

Someone creates a new permanent lock, let's say the amount is 200 and they receive 20 boosted FNX:

  • Lock A = 10
  • Lock B = 10
  • Lock C = 200+20 = 220
  • permanentTotalSupply = 20 + 200 = 220 (Should be 240, but the contract fails to account for this)

Now, let's say that someone suddenly changes their mind and call unlockPermanent:

  • Lock A = 10
  • Lock B = 10
  • Lock C = 220 (locked for 6 months)
  • permanentTotalSupply = 220 - 220 = 0

And after this, both lock A and lock B can't be unlocked from permanent lock because it would fail from underflow of permanentTotalSupply

@Ch-301
Copy link

Ch-301 commented Oct 4, 2024

@nnez Yeah, we can create scenarios with 3,4.. locks can't be unlocked from permanent lock because of this issue (the more preconditions required will lead to lower the probability).

So, as @c4-judge mentions here the medium severity is fair.

I think the last permanent lock being affected reasonably often merits a medium severity. ...

Please, @C4-Staff make sure to update the Label here if the judge confirms the medium severity.

@liveactionllama
Copy link

liveactionllama commented Oct 9, 2024

Per request from the judge @alcueca, updating the severity of this issue to Medium to match their intent in this comment: #11 (comment).

@liveactionllama liveactionllama added 2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value downgraded by judge Judge downgraded the risk level of this issue and removed 3 (High Risk) Assets can be stolen/lost/compromised directly labels Oct 9, 2024
@C4-Staff C4-Staff added M-04 and removed H-01 labels Oct 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value bug Something isn't working downgraded by judge Judge downgraded the risk level of this issue M-04 primary issue Highest quality submission among a set of duplicates 🤖_06_group AI based duplicate group recommendation satisfactory satisfies C4 submission criteria; eligible for awards selected for report This submission will be included/highlighted in the audit report sponsor confirmed Sponsor agrees this is a problem and intends to fix it (OK to use w/ "disagree with severity") upgraded by judge Original issue severity upgraded from QA/Gas by judge
Projects
None yet
Development

No branches or pull requests

10 participants