Core Contracts

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

RAACToken taxRateIncrementLimit has low effect

Summary

The RAACToken::taxRateIncrementLimit can be overcame by multiple calls in one transaction.

Vulnerability Details

RAACToken tax rate update mechanism does not prevent multiple calls in one transaction. This allows to set maximum tax rate in one transaction.

/contracts/core/tokens/RAACToken.sol

function _setTaxRate(uint256 newRate, bool isSwapTax) private {
if (newRate > MAX_TAX_RATE) revert TaxRateExceedsLimit();
uint256 currentRate = isSwapTax ? swapTaxRate : burnTaxRate;
if (currentRate != 0) {
@> uint256 maxChange = currentRate.percentMul(taxRateIncrementLimit);
// Check if the new rate is too high (newRate > currentRate + maxChange) or too low (newRate < currentRate && currentRate - newRate > maxChange) by more than the allowed increment
bool isTooHighOrTooLow = newRate > currentRate + maxChange || newRate < currentRate && currentRate - newRate > maxChange;
if (isTooHighOrTooLow) {
revert TaxRateChangeExceedsAllowedIncrement();
}
}
if (isSwapTax) {
swapTaxRate = newRate;
emit SwapTaxRateUpdated(newRate);
} else {
burnTaxRate = newRate;
emit BurnTaxRateUpdated(newRate);
}
}

PoC (foundry)

// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;
import {Test, console} from "forge-std/Test.sol";
import {RAACToken} from "src/core/tokens/RAACToken.sol";
import {PercentageMath} from "src/libraries/math/PercentageMath.sol";
contract TestRAACToken is Test {
RAACToken public raacToken;
function setUp() public {
raacToken = new RAACToken(address(this), 0, 0);
}
function test_setMaxTax() public {
uint256 initialSwapRate = raacToken.swapTaxRate();
uint256 initialBurnRate = raacToken.burnTaxRate();
Attacker attacker = new Attacker(raacToken);
raacToken.transferOwnership(address(attacker));
attacker.setMaxTax();
uint256 finalSwapRate = raacToken.swapTaxRate();
uint256 finalBurnRate = raacToken.burnTaxRate();
console.log("Initial swap rate: ", initialSwapRate);
console.log(" Final swap rate: ", finalSwapRate);
console.log("Initial burn rate: ", initialBurnRate);
console.log(" Final burn rate: ", finalBurnRate);
}
}
contract Attacker {
using PercentageMath for uint256;
RAACToken public raacToken;
constructor(RAACToken _raacToken) {
raacToken = _raacToken;
}
function setMaxTax() public {
uint256 maxTaxRate = raacToken.MAX_TAX_RATE();
uint256 taxRateIncrementLimit = raacToken.taxRateIncrementLimit();
uint256 currentRate = raacToken.swapTaxRate();
while (currentRate < maxTaxRate) {
uint256 maxChange = currentRate.percentMul(taxRateIncrementLimit);
uint256 rate = maxChange + currentRate;
if (rate > maxTaxRate) {
rate = maxTaxRate;
}
raacToken.setSwapTaxRate(rate);
currentRate = raacToken.swapTaxRate();
}
currentRate = raacToken.burnTaxRate();
while (currentRate < maxTaxRate) {
uint256 maxChange = currentRate.percentMul(taxRateIncrementLimit);
uint256 rate = maxChange + currentRate;
if (rate > maxTaxRate) {
rate = maxTaxRate;
}
raacToken.setBurnTaxRate(rate);
currentRate = raacToken.burnTaxRate();
}
}
}

Impact

Users expecting that the taxRateIncrementLimit will prevent setting the tax rate to the maximum value immediately. But owner can set the maximum tax rate in one transaction. This can lead to poor user experience.

Recommendations

Add a limit to single call per block.

function _setTaxRate(uint256 newRate, bool isSwapTax) private {
if (newRate > MAX_TAX_RATE) revert TaxRateExceedsLimit();
+ if (block.number == lastTaxRateChangeBlock) revert TaxRateChangeTooSoon();
+ lastTaxRateChangeBlock = block.number;
uint256 currentRate = isSwapTax ? swapTaxRate : burnTaxRate;
if (currentRate != 0) {
uint256 maxChange = currentRate.percentMul(taxRateIncrementLimit);
// Check if the new rate is too high (newRate > currentRate + maxChange) or too low (newRate < currentRate && currentRate - newRate > maxChange) by more than the allowed increment
bool isTooHighOrTooLow = newRate > currentRate + maxChange || newRate < currentRate && currentRate - newRate > maxChange;
if (isTooHighOrTooLow) {
revert TaxRateChangeExceedsAllowedIncrement();
}
}
if (isSwapTax) {
swapTaxRate = newRate;
emit SwapTaxRateUpdated(newRate);
} else {
burnTaxRate = newRate;
emit BurnTaxRateUpdated(newRate);
}
}
Updates

Lead Judging Commences

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