Core Contracts

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

Tax Stacking When RAAC's 10% Cap Becomes 20%

Summary

RAACToken's tax mechanism can exceed the maximum allowed tax rate when both swap and burn taxes are combined. This goes against the core economic constraints of the protocol. The issue stems from insufficient validation of combined tax rates in the token contract.

When we checks that the sum of swap and burn tax rates never exceeds MAX_TAX_RATE (1000 basis points or 10%). Looking at the contract code: RAACToken.sol/#L17-L22

uint256 public swapTaxRate = 100; // 1% swap tax
uint256 public burnTaxRate = 50; // 0.5% burn tax
uint256 public constant MAX_TAX_RATE = 1000; // 10%

The issue arises in the tax rate setting mechanism. While individual tax rates are checked against MAX_TAX_RATE, their combined effect isn't validated. For example:

  • swapTaxRate could be set to 900 (9%)

  • burnTaxRate could be set to 200 (2%)

  • Total tax would be 1100 basis points (11%), exceeding MAX_TAX_RATE

The root cause is in the _setTaxRate function which only validates individual rates: /#L119

if (newRate > MAX_TAX_RATE) revert TaxRateExceedsLimit();

This could lead to users paying more tax than the protocol intends, potentially up to 20% in extreme cases. This breaks the core economic assumption that total tax never exceeds 10%.

Vulnerability Details

RAAC's Tax Mechanism: When 1+1 Equals More Than 2

The RAACToken implements tax mechanisms to maintain protocol stability. Imagine a real estate transaction where both transfer and property taxes are meant to cap at 10% total. In our protocol, this translates to the MAX_TAX_RATE of 1000 basis points. However, the current implementation allows these taxes to stack beyond this limit.

How this happens:

The protocol allows setting two types of taxes

uint256 public swapTaxRate; // For market operations
uint256 public burnTaxRate; // For deflationary mechanics

each tax rate is validated individually against MAX_TAX_RATE, but their combination isn't checked. This means a governance action could set:

  • swapTaxRate = 900 (9%)

  • burnTaxRate = 900 (9%)

This results in an effective tax rate of 18%, significantly exceeding our intended 10% maximum. In practice, this could affect the real estate transactions on the platform, where users might face unexpectedly high fees during periods of market stress when both tax rates are elevated. _setTaxRate()

function _setTaxRate(uint256 newRate, bool isSwapTax) private {
// VULNERABLE POINT 1: Individual rate check
// ✓ Validates single tax rate against MAX_TAX_RATE
// ✗ Misses combined tax rate validation
if (newRate > MAX_TAX_RATE) revert TaxRateExceedsLimit();
uint256 currentRate = isSwapTax ? swapTaxRate : burnTaxRate;
// VULNERABLE POINT 2: Rate change limits
// This section handles incremental changes but doesn't prevent
// the cumulative effect of both taxes exceeding MAX_TAX_RATE
if (currentRate != 0) {
uint256 maxChange = currentRate.percentMul(taxRateIncrementLimit);
bool isTooHighOrTooLow = newRate > currentRate + maxChange ||
newRate < currentRate && currentRate - newRate > maxChange;
if (isTooHighOrTooLow) {
revert TaxRateChangeExceedsAllowedIncrement();
}
}
// VULNERABLE POINT 3: State updates
// Updates happen independently without checking combined impact
if (isSwapTax) {
swapTaxRate = newRate; // Could be 900 (9%)
emit SwapTaxRateUpdated(newRate);
} else {
burnTaxRate = newRate; // Could also be 900 (9%)
emit BurnTaxRateUpdated(newRate);
}
// Total effective tax: 18% > MAX_TAX_RATE (10%)
}

Impact

The protocol uses a dual-tax system to maintain stability and incentivize long-term holding. However, I've discovered an interesting interaction in how these taxes compound.

The core mistake lies in how the protocol handles tax stacking. When you notice how the RAACToken implements two distinct tax mechanisms. a swap tax for market operations and a burn tax for deflationary pressure. While each tax rate is carefully bounded, their interaction creates an unexpected multiplication effect.

This means that during high-volume periods, users could face effective tax rates of up to 18%, nearly double the protocol's intended 10% maximum. For perspective, on a $1,000,000 real estate transaction, this difference represents an extra $80,000 in fees.

Let's examine how this unfolds in the protocol

// Each tax passes validation individually
swapTaxRate = 900; // 9% looks fine
burnTaxRate = 900; // 9% also looks fine
// But together they create an 18% tax burden
effectiveTax = 1800; // Exceeds MAX_TAX_RATE of 1000

The impact ripples through the entire protocol ecosystem. The LendingPool's collateral calculations become skewed, the StabilityPool's incentive structure gets distorted, and most importantly, the core promise of efficient real estate tokenization is compromised.

Recommendations

Current Vulnerable Implementation

function _setTaxRate(uint256 newRate, bool isSwapTax) private {
// STEP 1: Individual Rate Check ✗
// Only checks if new rate <= 1000 (10%)
// Example: newRate = 900 passes this check
if (newRate > MAX_TAX_RATE) revert TaxRateExceedsLimit();
// STEP 2: Rate Change Increment Check
// Focuses on rate changes, but misses total tax impact
if (currentRate != 0) {
uint256 maxChange = currentRate.percentMul(taxRateIncrementLimit);
bool isTooHighOrTooLow = newRate > currentRate + maxChange ||
newRate < currentRate && currentRate - newRate > maxChange;
if (isTooHighOrTooLow) {
revert TaxRateChangeExceedsAllowedIncrement();
}
}
// STEP 3: State Update ✗
// Can set swapTaxRate = 900 and burnTaxRate = 900
// Total = 1800 (18%) > MAX_TAX_RATE
if (isSwapTax) {
swapTaxRate = newRate;
emit SwapTaxRateUpdated(newRate);
} else {
burnTaxRate = newRate;
emit BurnTaxRateUpdated(newRate);
}
}

Proposed Fix

function _setTaxRate(uint256 newRate, bool isSwapTax) private {
// STEP 1: Individual Rate Check ✓
if (newRate > MAX_TAX_RATE) revert TaxRateExceedsLimit();
// STEP 2: Combined Rate Check ✓
// Ensures swapTaxRate + burnTaxRate <= 1000 (10%)
uint256 otherRate = isSwapTax ? burnTaxRate : swapTaxRate;
if (newRate + otherRate > MAX_TAX_RATE)
revert CombinedTaxRateExceedsLimit();
// STEP 3: Rate Change Increment Check
// Original increment validation logic...
// STEP 4: Safe State Update ✓
// Now guaranteed total tax <= 10%
if (isSwapTax) {
swapTaxRate = newRate;
emit SwapTaxRateUpdated(newRate);
} else {
burnTaxRate = newRate;
emit BurnTaxRateUpdated(newRate);
}
}

I dd a crucial combined rate check before any state updates, preventing the total tax from exceeding MAX_TAX_RATE under all circumstances.

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!