Core Contracts

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

veRAACToken Contract - Contradicting Supply Limits Analysis

Summary

The veRAACToken contract has contradicting supply limits where MAX_TOTAL_LOCKED_AMOUNT is set to 1B RAAC, but MAX_TOTAL_SUPPLY effectively prevents locking more than ~370M RAAC due to veToken voting power calculations.

Vulnerability Details

uint256 public constant MAX_TOTAL_LOCKED_AMOUNT = 1_000_000_000 ether; // 1B
uint256 public constant MAX_TOTAL_SUPPLY = 100_000_000 ether; // 100M

Proof of Concept

// filepath: hardhat.config.cjs
module.exports = {
networks: {
hardhat: {
accounts: {
count: 150, // Increase number of test accounts
accountsBalance: "10000000000000000000000000" // Enough ETH for gas
}
}
}
// ... rest of config
};
// filepath: test/unit/core/tokens/veRAACToken.test.js
// ... Existing test
describe("Lock Amount Constants Validation", () => {
it("demonstrates MAX_TOTAL_LOCKED_AMOUNT vs MAX_TOTAL_SUPPLY inconsistency", async function() {
this.timeout(600000);
// Constants from contract
const MAX_LOCK_AMOUNT = ethers.parseEther("10000000"); // 10M per lock
const MAX_TOTAL_LOCKED = ethers.parseEther("1000000000"); // 1B limit (unused)
const MAX_TOTAL_SUPPLY = ethers.parseEther("100000000"); // 100M limit (enforced)
// Calculate needed locks to exceed 1B
const individualAmount = ethers.parseEther("9999999"); // Just under 10M
const numUsers = 101; // Will try to lock ~1.01B total (101 * ~10M)
const veRAACTokenAddress = await veRAACToken.getAddress();
console.log("\n=== Testing Lock Amount Constants Inconsistency ===");
console.log(`MAX_TOTAL_LOCKED_AMOUNT: ${ethers.formatEther(MAX_TOTAL_LOCKED)} RAAC (unused)`);
console.log(`Individual Lock Amount: ${ethers.formatEther(individualAmount)} RAAC`);
console.log(`Number of Users: ${numUsers}`);
console.log(`Target Total: ${ethers.formatEther(individualAmount * BigInt(numUsers))} RAAC`);
// Get all signers
const allSigners = await ethers.getSigners();
const testSigners = allSigners.slice(0, numUsers);
// Setup users in smaller batches
const BATCH_SIZE = 5;
for (let i = 0; i < numUsers; i += BATCH_SIZE) {
const batchEnd = Math.min(i + BATCH_SIZE, numUsers);
for (let j = i; j < batchEnd; j++) {
await raacToken.mint(testSigners[j].address, individualAmount);
await raacToken.connect(testSigners[j]).approve(veRAACTokenAddress, individualAmount);
}
console.log(`Setup complete for users ${i} to ${batchEnd - 1}`);
}
let totalLocked = 0n;
let reachedMAXSupply = false;
// Create locks until we hit either MAX_TOTAL_SUPPLY or exceed MAX_TOTAL_LOCKED
for (let i = 0; i < numUsers; i++) {
try {
const tx = await veRAACToken.connect(testSigners[i]).lock(
individualAmount,
365 * 24 * 3600
);
await tx.wait();
totalLocked += individualAmount;
console.log(`Lock ${i + 1} created - Total Locked: ${ethers.formatEther(totalLocked)} RAAC`);
// Check if we've exceeded MAX_TOTAL_LOCKED_AMOUNT
if (totalLocked > MAX_TOTAL_LOCKED) {
console.log(`\n!!! Successfully exceeded MAX_TOTAL_LOCKED_AMOUNT (1B) !!!`);
console.log(`Amount over limit: ${ethers.formatEther(totalLocked - MAX_TOTAL_LOCKED)} RAAC`);
break;
}
} catch (error) {
if (error.message.includes("TotalSupplyLimitExceeded")) {
reachedMAXSupply = true;
console.log(`\n!!! Hit MAX_TOTAL_SUPPLY limit (100M) at ${ethers.formatEther(totalLocked)} RAAC`);
} else {
console.log(`\nError at ${ethers.formatEther(totalLocked)} total locked:`, error.message);
}
break;
}
}
// Final verification
const finalSupply = await veRAACToken.totalSupply();
console.log("\n=== Final State ===");
console.log(`Total RAAC Locked: ${ethers.formatEther(totalLocked)} RAAC`);
console.log(`Final veToken Supply: ${ethers.formatEther(finalSupply)}`);
// The actual findings
if (reachedMAXSupply) {
console.log("\nCritical Finding:");
console.log("1. MAX_TOTAL_LOCKED_AMOUNT (1B) is unreachable");
console.log("2. MAX_TOTAL_SUPPLY (100M) prevents locking more than 100M tokens");
console.log("3. Contract has contradicting limits: claims to allow 1B locked but caps at 100M");
}
// We should fail if we can't prove either way
if (!reachedMAXSupply && totalLocked <= MAX_TOTAL_LOCKED) {
console.log("\nTest Failed:");
console.log("Could not prove if MAX_TOTAL_LOCKED_AMOUNT is enforced or reachable");
}
});
});
// ... Closing test file

Running the test (PoC):

npx hardhat test test/unit/core/tokens/veRAACToken.test.js --grep "Lock Amount Constants Validation"

Test results demonstrate:

=== Testing Lock Amount Constants Inconsistency ===
MAX_TOTAL_LOCKED_AMOUNT: 1000000000 RAAC (unused)
Individual Lock Amount: 9999999 RAAC
Number of Users: 101
Target Total: 1009999899 RAAC
Lock 37 created - Total Locked: 369999963.0 RAAC
!!! Hit MAX_TOTAL_SUPPLY limit (100M) at 369999963.0 RAAC
=== Final State ===
Total RAAC Locked: 369999963.0 RAAC
Final veToken Supply: 92499990.75 veRAAC

The test shows:

  1. Contract allows locking up to 1B RAAC theoretically

  2. veToken supply has a hard cap of 100M

  3. Due to 1-year lock giving 0.25x voting power:

    • 370M RAAC locked generates ~92.5M veRAAC

    • Next lock would exceed 100M veRAAC limit

  4. Makes it impossible to reach advertised 1B RAAC lock limit

Impact

  • Users are misled about maximum lockable amount (370M vs advertised 1B)

  • Contract has internally contradicting limits

  • Documentation and constants give incorrect expectations

  • System capacity is significantly lower than intended

Tools Used

  • Custom Hardhat test suite

  • Manual review

  • Math calculations

Recommendations

  1. Align the limits by either:

    // Option 1: Increase MAX_TOTAL_SUPPLY to allow 1B locks
    uint256 public constant MAX_TOTAL_SUPPLY = 250_000_000 ether; // 250M veRAAC
    // Option 2: Reduce MAX_TOTAL_LOCKED_AMOUNT to match reality
    uint256 public constant MAX_TOTAL_LOCKED_AMOUNT = 400_000_000 ether; // 400M RAAC
  2. Add clear documentation explaining:

    • Relationship between locked RAAC and generated veRAAC

    • Actual maximum lockable amount considering voting power calculations

    • Why limits are set at these specific values

  3. Consider making the voting power multiplier configurable to allow future adjustments of these relationships if needed.

Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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

Give us feedback!