Summary
The RAACToken's tax rate change mechanism has a maxChange limit but no cooldown period, allowing owners to chain multiple small changes to bypass the intended rate limit.
Vulnerability Details
There is a protection for users to avoid big spike in tax rates, but this protection is useless. An owner can simply call multiple times setSwapTaxRate()
or setBurnTaxRate()
as there is no timestamp check on the last call.
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);
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);
}
}
Impact
The tax rate protection for users can be bypassed and is not as effective as it should. Owner can make drastic tax changes instantly, that will undermines user trust.
Tools Used
Manual
Recommendations
Add a timestamp for the swap and burn tax rate when last called and a threshold.
uint256 public constant RATE_CHANGE_COOLDOWN = 7 days;
mapping(bool => uint256) private lastRateChange;
function _setTaxRate(uint256 newRate, bool isSwapTax) private {
uint256 lastChange = lastRateChange[isSwapTax];
require(
block.timestamp >= lastChange + RATE_CHANGE_COOLDOWN,
"Must wait for cooldown"
);
lastRateChange[isSwapTax] = block.timestamp;
}