Core Contracts

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

Pool-Wide Working Supply Update in updateUserBoost

Summary

The BoostController contract contains a vulnerability in the working supply calculation, where the working supply is overwritten instead of being accumulated. This leads to incorrect boost calculations for users.

Vulnerability Details

  • Issue: The line poolBoost.workingSupply = newBoost; in the updateUserBoost() function directly assigns the new boost value to the working supply, overwriting previous values.

  • Location: contracts/core/governance/boost/BoostController.sol

Impact

  • Incorrect Calculations: The total working supply reflects only the last user's boost, leading to inaccurate boost calculations for the pool.

  • User Trust: Users may lose trust in the protocol due to incorrect financial metrics.

PoC

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "forge-std/Test.sol";
import {BoostController} from "../contracts/core/governance/boost/BoostController.sol";
import {MockVeToken} from "../test/mocks/MockVeToken.sol";
/**
* @title BoostController Vulnerability Test Suite
* @notice This test suite systematically demonstrates and verifies vulnerabilities in the BoostController contract.
* @dev Test methodology:
* 1. Each test focuses on a specific vulnerability
* 2. Tests are designed to FAIL when the vulnerability exists
* 3. Tests will PASS once the vulnerability is fixed
* 4. Detailed logging is included to help understand the vulnerability
*/
contract BoostControllerTest is Test {
// Contract instances
BoostController public boostController;
MockVeToken public veToken;
// Test addresses with specific roles
address public admin = address(1); // Has DEFAULT_ADMIN_ROLE
address public manager = address(2); // Has MANAGER_ROLE
address public emergencyAdmin = address(3); // Has EMERGENCY_ADMIN role
address public user1 = address(4); // Regular user with 1000e18 veToken
address public user2 = address(5); // Regular user with 1500e18 veToken
address public delegator = address(6); // User who delegates boost (2000e18 veToken)
address public delegatee = address(7); // User who receives delegation (500e18 veToken)
address public pool = address(8); // Supported pool for testing
// Events for detailed logging
event LogTestStep(string description);
event LogValue(string name, uint256 value);
event LogAddress(string name, address value);
event LogBoostState(
string description,
uint256 boost,
uint256 workingSupply,
uint256 baseSupply
);
function setUp() public {
console.log("Setting up test environment...");
// Step 1: Deploy and configure mock veToken
veToken = new MockVeToken();
emit LogTestStep("Deployed MockVeToken");
// Set up test balances with different amounts to test various scenarios
veToken.setBalance(user1, 1000e18); // 1000 veTokens (40% of max)
veToken.setBalance(user2, 1500e18); // 1500 veTokens (60% of max)
veToken.setBalance(delegator, 2000e18); // 2000 veTokens (80% of max)
veToken.setBalance(delegatee, 500e18); // 500 veTokens (20% of max)
emit LogTestStep("Set up test balances");
// Step 2: Deploy BoostController with admin as deployer
vm.startPrank(admin);
boostController = new BoostController(address(veToken));
emit LogTestStep("Deployed BoostController");
emit LogAddress("veToken", address(veToken));
// Step 3: Set up roles
boostController.grantRole(boostController.MANAGER_ROLE(), manager);
emit LogTestStep("Granted MANAGER_ROLE to manager");
vm.stopPrank();
// Step 4: Configure test pool
vm.prank(manager);
boostController.modifySupportedPool(pool, true);
emit LogTestStep("Configured test pool as supported");
emit LogAddress("pool", pool);
console.log("Setup complete");
}
/**
* @notice Tests the working supply aggregation vulnerability
* @dev Vulnerability details:
* - Issue: Working supply is overwritten instead of accumulated
* - Impact: Incorrect total working supply leads to wrong boost calculations
* - Location: updateUserBoost() function
* - Line: poolBoost.workingSupply = newBoost;
*
* Test scenario:
* 1. User1 updates boost (1000e18 veToken = 40% capacity)
* 2. User2 updates boost (1500e18 veToken = 60% capacity)
* 3. Working supply should be sum of both boosts
* 4. Currently, it's only equal to User2's boost
*/
function testWorkingSupplyAggregationFails() public {
emit LogTestStep("Starting working supply aggregation test");
// Log initial state
(, uint256 initialWorkingSupply, , ) = boostController.getPoolBoost(
pool
);
emit LogValue("Initial working supply", initialWorkingSupply);
// Step 1: Update boost for user1
console.log("Updating boost for user1...");
vm.prank(user1);
boostController.updateUserBoost(user1, pool);
(uint256 boost1, , , ) = boostController.getUserBoost(user1, pool);
emit LogValue("User1 boost", boost1);
// Log intermediate state
(, uint256 intermediateWorkingSupply, , ) = boostController
.getPoolBoost(pool);
emit LogValue("Working supply after user1", intermediateWorkingSupply);
console.log("User1 boost calculated:", boost1);
// Step 2: Update boost for user2
console.log("Updating boost for user2...");
vm.prank(user2);
boostController.updateUserBoost(user2, pool);
(uint256 boost2, , , ) = boostController.getUserBoost(user2, pool);
emit LogValue("User2 boost", boost2);
// Log final state
(, uint256 finalWorkingSupply, , ) = boostController.getPoolBoost(pool);
emit LogValue("Final working supply", finalWorkingSupply);
console.log("User2 boost calculated:", boost2);
// Calculate expected working supply
uint256 expectedWorkingSupply = boost1 + boost2;
emit LogValue("Expected working supply", expectedWorkingSupply);
emit LogValue("Actual working supply", finalWorkingSupply);
// Verify working supply
console.log("Verifying working supply calculation...");
console.log("Expected:", expectedWorkingSupply);
console.log("Actual:", finalWorkingSupply);
assertEq(
finalWorkingSupply,
expectedWorkingSupply,
"Working supply should be sum of all user boosts"
);
}
}

Tools Used

  • Foundry for testing and debugging.

  • Solidity for contract development.

Recommendations

  • Modify the updateUserBoost() function to accumulate the working supply instead of overwriting it. For example, use:

    poolBoost.workingSupply += newBoost; // Update cumulatively
  • Implement comprehensive unit tests to verify that the working supply aggregates correctly across multiple users.Pool-Wide Working Supply Update in updateUserBoost

Updates

Lead Judging Commences

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

BoostController::updateUserBoost overwrites workingSupply with single user's boost value instead of accumulating, breaking reward multipliers and allowing last updater to capture all benefits

Support

FAQs

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