Core Contracts

Regnum Aurum Acquisition Corp
HardhatReal World AssetsNFT
77,280 USDC
View results
Submission Details
Severity: medium
Valid

Inadequate Adjustment of Voting Power upon Lock Increase. Vulnerability causes Arithmetic error leads to a Dos.

Summary

The veRAACToken::increase function is intended to allow users to add more tokens to their lock, thereby increasing their voting power (ve tokens). However, because ve token power decays over time using a time-weighted formula, a situation may arise where a user’s new calculated voting power is lower than their current balance especially when the increase occurs near the end of the lock duration. The function currently only mints additional tokens based on the difference between the new and current power:

_mint(msg.sender, newPower - balanceOf(msg.sender));

This implementation lacks any mechanism to reduce (burn) the user's balance if the new calculated voting power is lower than the existing one. As a result, users could either experience unexpected transaction failures (if the subtraction underflows) or maintain an erroneously high ve token balance relative to their actual locked amount. This flaw poses a significant risk to the integrity of the governance mechanism.

Vulnerability Details

  1. Voting Power Calculation:

    • The voting power is determined by the formula:

      uint256 initialPower = (amount * duration) / MAX_LOCK_DURATION;
      bias = int128(int256(initialPower));
      slope = int128(int256(initialPower / duration)); // decay per second
    • A user locking 4 tokens for one year receives 1 ve token:

      veRAacToken = (4e18 * 365 days) / 1460 days = 1
  2. Time Decay and Its Implications:

    • The voting power decays linearly over time. Therefore, if a user calls increase near the lock expiration, the decay will have significantly reduced the current effective voting power.

    • For example, a user initially locks 100,000e18 tokens. Near the end of the lock (e.g., on the second last day), the decay means their current effective voting power is close to zero.

  3. Flawed Increase Function:

    • The function recalculates the voting power (newPower) after an increase in the locked amount.

    • It then attempts to adjust the user's ve token balance by minting:

      _mint(msg.sender, newPower - balanceOf(msg.sender));
    • Problem: If newPower is less than balanceOf(msg.sender), which is likely when the lock is nearing expiration, the subtraction yields a negative result. In Solidity (version 0.8+), this underflow causes a revert. Even if using an earlier version without automatic underflow checks, wrapping could result in an incorrect, excessive minting.

  4. Lack of a Burn Mechanism:

    • The absence of logic to reduce the user’s ve token balance (i.e., a burn function) means that the contract does not account for situations where the decayed voting power should be lower than the previously minted amount.

    • A proper implementation would compare the new calculated power with the current balance and, if necessary, burn the excess tokens:

      uint256 oldPower = balanceOf(msg.sender);
      if (newPower > oldPower) {
      _mint(msg.sender, newPower - oldPower);
      } else if (newPower < oldPower) {
      _burn(msg.sender, oldPower - newPower);
      }

Proof of Concept

Scenario:

  1. Initial Lock:

    • User locks 100,000e18 tokens for 365 days, receiving a corresponding ve token balance calculated via the decay formula.

  2. Near Lock Expiry:

    • On the second last day of the lock period, the user decides to increase their lock by adding a huge amount, e.g., 9,900,000e18 tokens, making the total locked amount near the maximum allowable (10,000,000e18).

  3. Calculation Outcome:

    • Despite the large additional amount, due to the heavy decay (since almost 365 days have elapsed), the newly calculated voting power (newPower) ends up being lower than the user's current ve token balance.

  4. Faulty Adjustment:

    • The function attempts:

      _mint(msg.sender, newPower - balanceOf(msg.sender));
    • Because newPower < balanceOf(msg.sender), the subtraction underflows. In Solidity 0.8+, this triggers a revert, blocking the increase; in earlier versions, it could wrap around, leading to a massive erroneous mint.

Code PoC (to above scenario)

To demonstrate this vulnerability, the following Proof of Concept (PoC) is provided. The PoC is written using the Foundry tool.

  1. Step 1: Create a Foundry project and place all the contracts in the src directory.

  2. Step 2: Create a test directory and a mocks folder within the src directory (or use an existing mocks folder).

  3. Step 3: Create all necessary mock contracts, if required.

  4. Step 4: Create a test file (with any name) in the test directory.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {Test, console} from "forge-std/Test.sol";
import {veRAACToken} from "../src/core/tokens/veRAACToken.sol";
import {RAACToken} from "../src/core/tokens/RAACToken.sol";
import {TimeWeightedAverage} from "../src/libraries/math/TimeWeightedAverage.sol";
import {LockManager} from "../src/libraries/governance/LockManager.sol";
import {IveRAACToken} from "../src/interfaces/core/tokens/IveRAACToken.sol";
contract VeRAACTokenTest is Test {
veRAACToken veRaacToken;
RAACToken raacToken;
address RAAC_OWNER = makeAddr("RAAC_OWNER");
address RAAC_MINTER = makeAddr("RAAC_MINTER");
uint256 initialRaacSwapTaxRateInBps = 200; // 2%, 10000 - 100%
uint256 initialRaacBurnTaxRateInBps = 150; // 1.5%, 10000 - 100%
address VE_RAAC_OWNER = makeAddr("VE_RAAC_OWNER");
address ALICE = makeAddr("ALICE");
address BOB = makeAddr("BOB");
address CHARLIE = makeAddr("CHARLIE");
address DEVIL = makeAddr("DEVIL");
function setUp() public {
raacToken = new RAACToken(RAAC_OWNER, initialRaacSwapTaxRateInBps, initialRaacBurnTaxRateInBps);
vm.startPrank(VE_RAAC_OWNER);
veRaacToken = new veRAACToken(address(raacToken));
vm.stopPrank();
}
}
  1. Step 5: Add the following test PoC in the test file, after the setUp function.

function testArithmeticUnderflowOnIncreasingLockAmount() public {
uint256 LOCK_AMOUNT = 100_000e18;
uint256 LOCK_DURATION = 365 days;
vm.startPrank(RAAC_OWNER);
raacToken.setMinter(RAAC_MINTER);
vm.stopPrank();
vm.startPrank(RAAC_MINTER);
raacToken.mint(ALICE, LOCK_AMOUNT);
vm.stopPrank();
vm.startPrank(ALICE);
raacToken.approve(address(veRaacToken), LOCK_AMOUNT);
veRaacToken.lock(LOCK_AMOUNT, LOCK_DURATION);
vm.stopPrank();
vm.startPrank(RAAC_MINTER);
raacToken.mint(ALICE, 10_000_000e18 - LOCK_AMOUNT);
vm.stopPrank();
vm.warp(block.timestamp + 364 days);
vm.startPrank(ALICE);
raacToken.approve(address(veRaacToken), 10_000_000e18 - LOCK_AMOUNT);
vm.expectRevert();
veRaacToken.increase(10_000_000e18 - LOCK_AMOUNT);
vm.stopPrank();
}
  1. Step 6: To run the test, execute the following commands in your terminal

forge test --mt testArithmeticUnderflowOnIncreasingLockAmount -vv
  1. Step 7: Review the output.

[⠒] Compiling...
No files changed, compilation skipped
Ran 1 test for test/VeRAACTokenTest.t.sol:VeRAACTokenTest
[PASS] testArithmeticUnderflowOnIncreasingLockAmount() (gas: 694174)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.23ms (400.80µs CPU time)
Ran 1 test suite in 11.28ms (3.23ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)

As demonstrated, the test confirms that there exist an Arithmetic Over/Underflow (Actually Underflow) Error in the veRAACToken::increase.

Impact

  • Governance Manipulation:
    Users could maintain a ve token balance higher than their actual voting power based on the decayed locked amount. This discrepancy undermines the integrity of the governance mechanism by granting disproportionate influence.

  • Denial of Service (DoS):
    Legitimate users attempting to increase their lock near expiry may find their transactions reverted, effectively preventing them from updating their positions and potentially locking them out of governance participation.

  • Inconsistent State:
    The contract’s internal state may become inconsistent, with ve token balances not accurately reflecting the underlying locked amounts, risking further logic errors in any system relying on these values.

Tools Used

  • Manual Review

  • Foundry

Recommendations

  1. Implement Proper Adjustment Logic:

    • Modify the increase function to compare the newly calculated voting power (newPower) with the current balance. Depending on the outcome:

      • If newPower > current balance: Mint the difference.

      • If newPower < current balance: Burn the excess tokens to align the balance with the decayed power.

    • Example adjustment:

      uint256 oldPower = balanceOf(msg.sender);
      if (newPower > oldPower) {
      _mint(msg.sender, newPower - oldPower);
      } else if (newPower < oldPower) {
      _burn(msg.sender, oldPower - newPower);
      }
  2. Consider Upgrading to SafeMath or Solidity 0.8+:

    • Utilize Solidity 0.8 or later, which has built-in overflow and underflow checks, to prevent arithmetic errors that could lead to vulnerabilities.

    • If using an earlier version, incorporate SafeMath libraries to mitigate risks.

Updates

Lead Judging Commences

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Validated
Assigned finding tags:

veRAACToken::increase underflows on newPower - balanceOf(msg.sender)

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.