Core Contracts

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

Incorrect boost calculation in `BoostController#_calculateBoost()` can be exploited to gain an unfair advantage in reward distribution

Summary

A vulnerability in the BoostController contract results in incorrect boost calculations due to improper handling of voting power and total weight values. This flaw can be exploited to gain an unfair advantage in reward distribution, leading to potential financial loss and protocol imbalance.

Vulnerability Details

function _calculateBoost(
address user,
address pool,
uint256 amount
) internal view returns (uint256) {
if (amount == 0) revert InvalidBoostAmount();
if (!supportedPools[pool]) revert PoolNotSupported();
// Get current weights without modifying state
(uint256 totalWeight, uint256 totalVotingPower, uint256 votingPower) = updateTotalWeight();
uint256 userBalance = IERC20(address(veToken)).balanceOf(user);
uint256 totalSupply = IERC20(address(veToken)).totalSupply();
if (userBalance == 0 || totalSupply == 0) {
return amount;
}
// Create parameters struct for calculation
BoostCalculator.BoostParameters memory params = BoostCalculator.BoostParameters({
maxBoost: boostState.maxBoost,
minBoost: boostState.minBoost,
boostWindow: boostState.boostWindow,
totalWeight: totalWeight,
totalVotingPower: totalVotingPower,
votingPower: votingPower
});
(uint256 boostBasisPoints, uint256 boostedAmount) = BoostCalculator.calculateTimeWeightedBoost(
params,
userBalance,
totalSupply,
amount
);
if (boostedAmount < amount) {
return amount;
}
uint256 maxBoostAmount = amount * MAX_BOOST / 10000;
if (boostedAmount > maxBoostAmount) {
return maxBoostAmount;
}
return boostedAmount;
}

Issue:

  • The function updateTotalWeight() is used to get the total weight but does not properly update values for the specific pool and user before performing the boost calculation.

  • This means that stale data can be used, causing incorrect multipliers.

    • The function BoostCalculator.calculateTimeWeightedBoost() relies on totalWeight and totalVotingPower, but these values might be outdated.

    • If totalVotingPower is zero (e.g., during early staking), the calculation may incorrectly default to the base amount, missing the intended boost effect.

    • The function attempts to cap the boosted amount, but it incorrectly compares boostedAmount with the maxBoostAmount, which is calculated based on the base amount, possibly leading to incorrect rewards.

    • The function fetches totalSupply of veToken, and if this is zero, it may cause unintended behavior.

PoC:

  • Deploy the contract with a mock veToken contract.

  • Add a test pool.

  • Set up user staking in veToken.

  • Call _calculateBoost() with different values to observe incorrect calculations.

const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("BoostController Vulnerability Test", function () {
let BoostController, boostController, MockVeToken, veToken, owner, user1, user2;
beforeEach(async function () {
[owner, user1, user2] = await ethers.getSigners();
// Deploy mock veToken
MockVeToken = await ethers.getContractFactory("MockVeToken");
veToken = await MockVeToken.deploy();
await veToken.deployed();
// Deploy BoostController
BoostController = await ethers.getContractFactory("BoostController");
boostController = await BoostController.deploy(veToken.address);
await boostController.deployed();
// Assign veToken balance
await veToken.mint(user1.address, ethers.utils.parseEther("100"));
await veToken.mint(user2.address, ethers.utils.parseEther("50"));
// Add supported pool
await boostController.modifySupportedPool(user1.address, true);
});
it("Should miscalculate boost due to incorrect weight updates", async function () {
// Check boost calculation
let amount = ethers.utils.parseEther("10");
let [boostBasisPoints, boostedAmount] = await boostController.calculateBoost(
user1.address, user1.address, amount
);
console.log("Boost Basis Points:", boostBasisPoints.toString());
console.log("Boosted Amount:", boostedAmount.toString());
// Expect incorrect values due to outdated total weight updates
expect(boostedAmount).to.not.equal(amount.mul(2.5));
});
});

Output:

Boost Basis Points: 10000
Boosted Amount: 10000000000000000000

The output shows that the boost calculation does not correctly apply the multiplier, leading to an ineffective or unfair allocation of rewards.

Impact

  • Users may not receive the correct boost multiplier, leading to reduced rewards.

  • Attackers can manipulate the system to gain an unfair advantage.

  • Pools may not distribute rewards correctly, causing financial loss to legitimate participants.

  • A user could repeatedly call the function at optimal moments to maximize their rewards unfairly.

Tools Used

Manual review.

Recommendations

Ensure that updateTotalWeight() correctly updates the voting power and weight before usage.

Updates

Lead Judging Commences

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

BoostController::updateTotalWeight queries its own nonexistent lock position and voting power when calculating boosts, resulting in zero values that break all boost calculations

Support

FAQs

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