Core Contracts

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

Missing Total locked amount Accounting in withdrawals in veRAACToken

Summary

In the veRAACToken contract, when users withdraw their locked RAAC tokens (either through regular withdraw() or emergencyWithdraw()), the contract fails to decrease the totalLocked state variable in the LockManager.LockState struct. This leads to an incorrect accounting of the total locked tokens in the system.

Vulnerability Details

The veRAACToken contract uses the LockManager library to manage token lock positions. The LockState struct in LockManager maintains a totalLocked variable that tracks the total amount of RAAC tokens locked in the system:

struct LockState {
mapping(address => Lock) locks; // User lock positions
uint256 totalLocked; // Total amount of tokens locked
uint256 minLockDuration;
uint256 maxLockDuration;
uint256 maxTotalLocked;
uint256 maxLockAmount;
}

When new tokens are locked via the lock() function, the totalLocked is correctly increased in LockManager.createLock():

function createLock(LockState storage state, address user, uint256 amount, uint256 duration)
internal
returns (uint256 end)
{
// ... validation logic ...
state.totalLocked += amount; // Correctly increases totalLocked
// ... other logic ...
}

However, in veRAACToken's withdraw() and emergencyWithdraw() functions, when users withdraw their tokens, the contract only deletes the individual lock mapping but fails to decrease the totalLocked counter:

function withdraw() external nonReentrant {
LockManager.Lock memory userLock = _lockState.locks[msg.sender];
// ... validation logic ...
uint256 amount = userLock.amount;
// Simply deletes the lock without decreasing totalLocked
delete _lockState.locks[msg.sender];
delete _votingState.points[msg.sender];
// ... rest of the function ...
}

Similarly in emergencyWithdraw():

function emergencyWithdraw() external nonReentrant {
// ... validation logic ...
uint256 amount = userLock.amount;
// Same issue - deletes lock without decreasing totalLocked
delete _lockState.locks[msg.sender];
delete _votingState.points[msg.sender];
// ... rest of the function ...
}

This causes the totalLocked to become permanently inflated as it never decreases when tokens are withdrawn. The inflated totalLocked value is used in boost calculations through _updateBoostState():

function _updateBoostState(address user, uint256 newAmount) internal {
_boostState.totalWeight = _lockState.totalLocked; // Uses incorrect totalLocked
// ... other logic ...
}

PoC

  1. Alice locks 1000 RAAC tokens - totalLocked = 1000

  2. Bob locks 2000 RAAC tokens - totalLocked = 3000

  3. Alice's lock expires and she withdraws - totalLocked remains at 3000 instead of decreasing to 2000

  4. Future boost calculations will use an incorrectly inflated totalLocked value

Impact

The inflated totalLocked value affects boost calculations in the system since the total weight is used as a denominator in boost computations. This results in users receiving lower boost multipliers than they should, as the total locked amount appears larger than it actually is.

Tools Used

Manual code review

Recommendations

Add _lockState.totalLocked -= amount before the delete operations in both withdraw() and emergencyWithdraw() functions:

function withdraw() external nonReentrant {
// ... existing code ...
_lockState.totalLocked -= amount; // Add this line
delete _lockState.locks[msg.sender];
// ... rest of the function ...
}
Updates

Lead Judging Commences

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

veRAACToken::withdraw / emergencyWithdraw doesn't substract the `_lockState.totalLocked`

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

veRAACToken::withdraw / emergencyWithdraw doesn't substract the `_lockState.totalLocked`

Support

FAQs

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