Core Contracts

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

An attacker can FrontRun governance execution to DOS proposal execution, this can also occur if a whale/honest user locks their raac token before execution is called

Summary

After a proposal ends the check inaccurately checks if the total vote casted is below some percentage of The total votes.

An attacker/ a normal user can decide to deposit, the attacker can deposit as low as 1 wei depending on the number of users that voted. While a normal users action also will increament the total amount needed and cause an unintended reversion.

Vulnerability Details

This bug exist because the Execution function depends on the total supply or total number of votes in the entire veRAAC token contract.

/**
* @notice Executes a successful proposal through the timelock
* @dev Two-step execution process:
* 1. Queue - Schedule proposal in timelock when vote succeeds
* 2. Execute - Execute proposal after timelock delay
* @param proposalId The ID of the proposal to execute
*/
function execute(uint256 proposalId) external override nonReentrant {
ProposalCore storage proposal = _proposals[proposalId];
if (proposal.executed) revert ProposalAlreadyExecuted(proposalId, block.timestamp);
@audit>>>>> ProposalState currentState = state(proposalId);
// Check if the proposal is in the correct state for execution
if (currentState == ProposalState.Succeeded) {
// Queue the proposal
_queueProposal(proposalId);
} else if (currentState == ProposalState.Queued) {
// Execute the queued proposal
_executeProposal(proposalId);
} else {
// If not in Succeeded or Queued state, revert
revert InvalidProposalState(
proposalId,
currentState,
currentState == ProposalState.Active ? ProposalState.Succeeded : ProposalState.Queued,
"Invalid state for execution"
);
}
}

Locking RAAC tokens mints voting powers in the veRAAC contract.

there is no minimum to LOCK hence an attack is possible with as low as 1 wei if participation is low

Also an honest user can deposit and his deposit will also affect a proposal execution

IN THE VERAAC contract,

/**
* @notice Creates a new lock position for RAAC tokens
* @dev Locks RAAC tokens for a specified duration and mints veRAAC tokens representing voting power
* @param amount The amount of RAAC tokens to lock
* @param duration The duration to lock tokens for, in seconds
*/
function lock(uint256 amount, uint256 duration) external nonReentrant whenNotPaused {
if (amount == 0) revert InvalidAmount();
if (amount > MAX_LOCK_AMOUNT) revert AmountExceedsLimit();
if (totalSupply() + amount > MAX_TOTAL_SUPPLY) revert TotalSupplyLimitExceeded(); /// Bug wrong check total supply is not the same as amount // bug no check for global lock
if (duration < MIN_LOCK_DURATION || duration > MAX_LOCK_DURATION)
revert InvalidLockDuration();
// Do the transfer first - this will revert with ERC20InsufficientBalance if user doesn't have enough tokens
raacToken.safeTransferFrom(msg.sender, address(this), amount);
// Calculate unlock time
uint256 unlockTime = block.timestamp + duration;
// Create lock position
_lockState.createLock(msg.sender, amount, duration);
_updateBoostState(msg.sender, amount);
// Calculate initial voting power
(int128 bias, int128 slope) = _votingState.calculateAndUpdatePower(
msg.sender,
amount,
unlockTime
);
// Update checkpoints
uint256 newPower = uint256(uint128(bias));
_checkpointState.writeCheckpoint(msg.sender, newPower);
// Mint veTokens
@audit>>> _mint(msg.sender, newPower); // minting new power. add this plus total supply........Note bug
emit LockCreated(msg.sender, amount, unlockTime);
}

Total supply will change hence total vote

/**
* @notice Gets the total voting power of all veRAAC tokens
* @dev Returns the total supply of veRAAC tokens
* @return The total voting power across all holders
*/
function getTotalVotingPower() external view override returns (uint256) {
return totalSupply();
}

This will affect the calculation

*/
function state(uint256 proposalId) public view override returns (ProposalState) {
ProposalCore storage proposal = _proposals[proposalId];
if (proposal.startTime == 0) revert ProposalDoesNotExist(proposalId);
if (proposal.canceled) return ProposalState.Canceled;
if (proposal.executed) return ProposalState.Executed;
if (block.timestamp < proposal.startTime) return ProposalState.Pending;
if (block.timestamp < proposal.endTime) return ProposalState.Active; <= note // last 1 second vote can change it all
// After voting period ends, check quorum and votes
ProposalVote storage proposalVote = _proposalVotes[proposalId];
@audit>>> uint256 currentQuorum = proposalVote.forVotes + proposalVote.againstVotes;
@audit>>> uint256 requiredQuorum = quorum();
// Check if quorum is met and votes are in favor
@audit>>> if (currentQuorum < requiredQuorum || proposalVote.forVotes <= proposalVote.againstVotes) { // according to comment above and not or
return ProposalState.Defeated;
}

Since the quorum is dynamic,

increase in locked funds will affect the amount returned, this increasing due to an attcker/honest user will dos proposals

/**
* @notice Gets the current quorum requirement
* @dev Calculates required quorum based on total voting power
* Uses quorumNumerator/QUORUM_DENOMINATOR ratio
* @return Current quorum threshold in voting power units
*/
function quorum() public view override returns (uint256) {
@audit>> return (_veToken.getTotalVotingPower() * quorumNumerator) / QUORUM_DENOMINATOR;
}

Impact

Inability to execute proposals because of an increase in the Quorum amount

Tools Used

Manual Review

Recommendations

It is safer to cache and save the total vote power at the point of proposing or set a fixed threshold instead of using a dynamic one.

Updates

Lead Judging Commences

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

Governance::quorum uses current total voting power instead of proposal creation snapshot, allowing manipulation of threshold requirements to force proposals to pass or fail

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

Governance::quorum uses current total voting power instead of proposal creation snapshot, allowing manipulation of threshold requirements to force proposals to pass or fail

Support

FAQs

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