Core Contracts

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

Cannot update user boost for delegated users

Summary

When a user delegates boost to a pool, delegation amount is not updated correctly. Due to this, their boost amount cannot be updated due to underflow.

Vulnerability Details

When user delegates boost to the pool, delegation amount is not updated correctly

function delegateBoost(
address to,
uint256 amount,
uint256 duration
) external override nonReentrant {
if (paused()) revert EmergencyPaused();
if (to == address(0)) revert InvalidPool();
if (amount == 0) revert InvalidBoostAmount();
if (duration < MIN_DELEGATION_DURATION || duration > MAX_DELEGATION_DURATION)
revert InvalidDelegationDuration();
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; // @audit userBoost amount is veToken amount
delegation.expiry = block.timestamp + duration;
delegation.delegatedTo = to;
delegation.lastUpdateTime = block.timestamp;
emit BoostDelegated(msg.sender, to, amount, duration);
}

Because as we see in updateUserBoost, amount is user's boost, not veToken amount:

function updateUserBoost(address user, address pool) external override nonReentrant whenNotPaused {
if (paused()) revert EmergencyPaused();
if (user == address(0)) revert InvalidPool();
if (!supportedPools[pool]) revert PoolNotSupported();
UserBoost storage userBoost = userBoosts[user][pool];
PoolBoost storage poolBoost = poolBoosts[pool];
uint256 oldBoost = userBoost.amount;
// Calculate new boost based on current veToken balance
uint256 newBoost = _calculateBoost(user, pool, 10000); // Base amount
@> userBoost.amount = newBoost; // @audit userBoost.amount is newBoost
userBoost.lastUpdateTime = block.timestamp;
// Update pool totals safely
if (newBoost >= oldBoost) {
poolBoost.totalBoost = poolBoost.totalBoost + (newBoost - oldBoost);
} else {
poolBoost.totalBoost = poolBoost.totalBoost - (oldBoost - newBoost);
}
poolBoost.workingSupply = newBoost; // Set working supply directly to new boost
poolBoost.lastUpdateTime = block.timestamp;
emit BoostUpdated(user, pool, newBoost);
emit PoolBoostUpdated(pool, poolBoost.totalBoost, poolBoost.workingSupply);
}

Moreover, we can see that poolBoostparameters are not updated correctly on user's delegation

  • poolBoost.workingSupplyis not increased by user boost

  • poolBoost.totalBoostis not increased by user boost

  • poolBoost.workingSuplplyis set to newBoostwhen updating user boost

Due to above reasons, updateUserBoostwill revert with underflow.

POC

pragma solidity ^0.8.19;
import "../lib/forge-std/src/Test.sol";
import {BoostController} from "../contracts/core/governance/boost/BoostController.sol";
import {veRAACToken} from "../contracts/core/tokens/veRAACToken.sol";
import {RAACToken} from "../contracts/core/tokens/RAACToken.sol";
import {MockPool} from "../contracts/mocks/core/pools/MockPool.sol";
contract BoostControllerTest is Test {
veRAACToken veToken;
RAACToken raacToken;
BoostController boostController;
address pool;
address alice = makeAddr("alice");
address bob = makeAddr("bob");
address eve = makeAddr("eve");
function setUp() external {
raacToken = new RAACToken(address(this), 0, 0);
raacToken.setMinter(address(this));
veToken = new veRAACToken(address(raacToken));
boostController = new BoostController(address(veToken));
pool = address(new MockPool());
boostController.modifySupportedPool(pool, true);
vm.label(pool, "pool");
}
function testDelegationRemovalFailure() external {
_dealVeToken(alice, 1000e18);
vm.startPrank(alice);
boostController.delegateBoost(pool, 100e18, 7 days);
vm.stopPrank();
skip(7 days);
vm.startPrank(pool);
vm.expectRevert(stdError.arithmeticError);
boostController.updateUserBoost(alice, pool);
vm.stopPrank();
}
function _dealVeToken(address account, uint256 amount) internal {
if (amount == 0) {
return;
}
deal(address(raacToken), account, amount);
vm.startPrank(account);
raacToken.approve(address(veToken), amount);
veToken.lock(amount, veToken.MAX_LOCK_DURATION());
vm.stopPrank();
}
}

Impact

  • User's boost won't be updated correctly

Tools Used

Manual Review, Foundry

Recommendations

Delegation amount should be set to user's boost amount

Updates

Lead Judging Commences

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

BoostController removes pool boost on delegation removal without adding it on delegation creation, leading to accounting inconsistencies and potential underflows

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

BoostController removes pool boost on delegation removal without adding it on delegation creation, leading to accounting inconsistencies and potential underflows

Support

FAQs

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

Give us feedback!