Core Contracts

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

Proposal Threshold Verification Inconsistency

Summary

https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/governance/proposals/Governance.sol#L127C5-L169C1

Proposal Threshold Verification Inconsistency

Vulnerability Details

https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/governance/proposals/Governance.sol#L127C5-L169C1

function propose(
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
string memory description,
ProposalType proposalType
) external override returns (uint256) {
// Initial voting power check
uint256 proposerVotes = _veToken.getVotingPower(msg.sender);
// First threshold verification
if (proposerVotes < proposalThreshold) {
revert InsufficientProposerVotes(
msg.sender,
proposerVotes,
proposalThreshold,
"Below threshold"
);
}
// Create proposal without storing initial voting power
uint256 proposalId = _proposalCount++;
_proposals[proposalId] = ProposalCore({
id: proposalId,
proposer: msg.sender,
// ... other fields ...
// Note: proposerVotes not stored
});
}
function cancel(uint256 proposalId) external override {
ProposalCore storage proposal = _proposals[proposalId];
// Retrieve proposal state
ProposalState currentState = state(proposalId);
if (currentState == ProposalState.Executed) {
revert InvalidProposalState(proposalId, currentState, ProposalState.Active, "Cannot cancel executed proposal");
}
// Inconsistent threshold check
if (msg.sender != proposal.proposer &&
_veToken.getVotingPower(proposal.proposer) >= proposalThreshold) {
revert InsufficientProposerVotes(
proposal.proposer,
_veToken.getVotingPower(proposal.proposer),
proposalThreshold,
"Proposer lost required voting power"
);
}
proposal.canceled = true;
emit ProposalCanceled(proposalId, msg.sender, "Proposal canceled by proposer");
}
The Inconsistencies can be seen below:
// In propose() - Time T1
uint256 proposerVotes = _veToken.getVotingPower(msg.sender);
if (proposerVotes < proposalThreshold) { ... }
// In cancel() - Time T2 (could be days later)
if (_veToken.getVotingPower(proposal.proposer) >= proposalThreshold) { ... }
// propose() checks: votes < threshold (must be higher)
if (proposerVotes < proposalThreshold) { revert; }
// cancel() checks: votes >= threshold (must be lower)
if (_veToken.getVotingPower(proposal.proposer) >= proposalThreshold) { revert; }
struct ProposalCore {
uint256 id;
address proposer;
// Missing fields for threshold verification:
// uint256 initialProposerVotes;
// uint256 thresholdAtCreation;
}
// Scenario:
T1: proposerVotes = 110k (propose passes)
T2: proposerVotes = 95k (below threshold but can't cancel)
T3: proposerVotes = 105k (above threshold again)
// Attack scenario:
1. Attacker borrows 100k veRAAC
2. Creates proposal (passes threshold check)
3. Returns borrowed tokens
4. Proposal can't be canceled because inverse check
5. Proposal remains active with insufficient stake

Impact

Proposal Threshold Verification Inconsistency

Tools Used

Foundry

Recommendations

// Consistent cancellation check
function cancel(uint256 proposalId) external override {
ProposalCore storage proposal = _proposals[proposalId];
// Use consistent logic with stored values
uint256 currentVotes = _veToken.getVotingPower(proposal.proposer);
if (msg.sender != proposal.proposer &&
currentVotes < proposal.thresholdAtCreation) {
proposal.canceled = true;
emit ProposalCanceled(proposalId, msg.sender, "Insufficient proposer votes");
}
}
// Add threshold maintenance check
modifier maintainsThreshold(uint256 proposalId) {
_;
ProposalCore storage proposal = _proposals[proposalId];
require(
_veToken.getVotingPower(proposal.proposer) >= proposal.thresholdAtCreation,
"Must maintain threshold"
);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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