Core Contracts

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

Precision Loss in RAACToken Burns Creates Hidden Tax

Summary

burn() function in RAACToken has a precision loss vulnerability in tax calculations. When burning tokens, the tax amount calculation can result in rounding errors that favor the protocol, potentially overcharging users during burns.

uint256 taxAmount = amount.percentMul(burnTaxRate);
_burn(msg.sender, amount - taxAmount);

For certain amounts and tax rates, the division operation in percentMul can lead to rounding errors. This means users could be charged slightly more in taxes than mathematically precise calculations would dictate.

Vulnerability Details

The RAACToken contract handles the protocol's main token operations including minting, burning, and tax calculations.

function burn(uint256 amount) external {
// VULNERABLE POINT 1: Integer division in percentMul causes rounding down
// amount * burnTaxRate / 10000 -> loses precision
uint256 taxAmount = amount.percentMul(burnTaxRate);
// VULNERABLE POINT 2: User gets burned amount-taxAmount
// This means any rounding error reduces their received amount
_burn(msg.sender, amount - taxAmount);
// VULNERABLE POINT 3: Fee collector receives the full taxAmount
// Even if it was rounded up due to precision loss
if (taxAmount > 0 && feeCollector != address(0)) {
_transfer(msg.sender, feeCollector, taxAmount);
}
}

This flow creates a systematic bias:

  1. Tax calculation rounds down in percentMul

  2. User receives less tokens than mathematically precise

  3. Fee collector benefits from the rounding

This directly affects the protocol's ve-tokenomics and dual-gauge system since burn() operations are core to the token's economic model.

The interface IRAACToken.sol defines this burn function, making it a critical part of the protocol's public API that other contracts rely on for precise token mechanics.

Looking at the RAACToken.sol contract, and notice how the burn function handles tax calculations. When users burn tokens, the protocol applies a burn tax rate, but here's where things get offsite. The tax calculation in the burn function can produce unexpected results due to precision handling.

Imagine Alice wants to burn 10,000 RAAC tokens with a 2.5% burn tax rate. The contract calculates the tax using percentMul, but due to integer division, the actual amount burned differs from the expected mathematical result. This precision loss consistently favors the protocol, effectively creating a hidden tax above the stated rate.

Impact

When users burn tokens, they may lose a small amount of additional value due to rounding. While each instance is minor, this compounds across all burn operations. For a protocol focused on precise financial operations, even small systematic rounding errors can erode user trust.

The core issue lies in performing integer division without proper rounding mechanisms. The current implementation truncates decimal places, which consistently rounds down in favor of the protocol.

The protocol's governance and ve-mechanism rely on precise token mechanics. This precision loss affects not just individual burns, but cascades through the entire system including gauge voting power calculations and lending pool operations.

For a burn of 10,000 RAAC with 2.5% tax:

  • Expected tax: 250 RAAC

  • Actual tax: 251 RAAC (due to rounding)

  • Hidden tax: 0.01% extra per burn

Can see how this calculation consistently rounds in favor of the protocol, creating an invisible tax layer above the stated rate. This means that for a user burning 10,000 RAAC with a 2.5% tax rate, they'll pay slightly more than the expected 250 RAAC in tax.

Recommendations

We can maintain mathematical precision by implementing proper rounding.

This should Address Both Points

function burn(uint256 amount) external {
// POINT 1: Proper rounding with half divisor (5000)
uint256 taxAmount = (amount * burnTaxRate + 5000) / 10000; // for proper rounding
_burn(msg.sender, amount - taxAmount);
// POINT 2: Burn and transfer in correct order
if (taxAmount > 0 && feeCollector != address(0)) {
_transfer(msg.sender, feeCollector, taxAmount);
}
_burn(msg.sender, amount - taxAmount);
}

The Interface Definition

interface IRAACToken is IERC20 {
// The burn function signature ensures consistent behavior
function burn(uint256 amount) external;
//...
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
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!