Core Contracts

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

Double Taxation on Tax Collection in RAAC Token's Burn Function causes loss of funds for fee collector.

Summary

In the burn function of RAACToken, when collecting the burn tax fee, the implementation incorrectly uses _transfer instead of directly updating balances, causing the tax fee itself to be taxed again due to the overridden _update function.

Vulnerability Details

The issue lies in how the burn function handles the tax collection. Let's break down the flow:

First, let's look at the burn function:

function burn(uint256 amount) external {
uint256 taxAmount = amount.percentMul(burnTaxRate);
_burn(msg.sender, amount - taxAmount);
if (taxAmount > 0 && feeCollector != address(0)) {
@> _transfer(msg.sender, feeCollector, taxAmount);
}
}

The key problem is in using _transfer for tax collection. The _transfer function ultimately calls the overridden _update function, which applies taxes again to the tax amount being transferred and keep in mind that when taxing the amount again, there is a a burn tax, meaning that always a small portion will be lost forever instead of going to feeCollector.

Looking at the _update function:

function _update(address from, address to, uint256 amount) internal virtual override {
uint256 baseTax = swapTaxRate + burnTaxRate;
if (baseTax == 0 || from == address(0) || to == address(0) ||
whitelistAddress[from] || whitelistAddress[to] ||
feeCollector == address(0)) {
super._update(from, to, amount);
return;
}
uint256 totalTax = amount.percentMul(baseTax);
uint256 burnAmount = (totalTax * burnTaxRate) / baseTax;
@> super._update(from, feeCollector, totalTax - burnAmount);
@> super._update(from, address(0), burnAmount);
@> super._update(from, to, amount - totalTax);
}

When transferring the tax amount to the fee collector using _transfer, this _update function gets called again, applying another layer of taxes to what was already a tax payment. This creates an unintended double taxation scenario.

Notice that only swap tax part of the tax goes back to the fee collect but the remaining portion is unneccesarily being burnt. i.e super._update(from, address(0), burnAmount);

PoC

  1. User wants to burn 1000 tokens

  2. With burnTaxRate of 50 (0.5%), initial tax = 5 tokens

  3. When transferring these 5 tokens to feeCollector:

    • The transfer triggers _update

    • These 5 tokens get taxed again (with both burn and swap tax)

    • Results in less than 5 tokens actually reaching the feeCollector

Impact

  • The fee collector receives less than the intended tax amount (since a portion of the actual tax will also be burned)

Tools Used

Manual code review

Recommendations

Modify the burn function to use super._update directly instead of _transfer:

function burn(uint256 amount) external {
uint256 taxAmount = amount.percentMul(burnTaxRate);
_burn(msg.sender, amount - taxAmount);
if (taxAmount > 0 && feeCollector != address(0)) {
super._update(msg.sender, feeCollector, taxAmount);
}
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 6 months ago
Submission Judgement Published
Validated
Assigned finding tags:

RAACToken::burn applies burn tax twice when transferring to feeCollector, causing excess tokens to be burned and reduced fees to be collected

This is by design, sponsor's words: Yes, burnt amount, done by whitelisted contract or not always occur the tax. The feeCollector is intended to always be whitelisted and the address(0) is included in the _transfer as a bypass of the tax amount, so upon burn->_burn->_update it would have not applied (and would also do another burn...). For this reason, to always apply such tax, the burn function include the calculation (the 2 lines that applies) and a direct transfer to feeCollector a little bit later. This is done purposefully

inallhonesty Lead Judge 6 months ago
Submission Judgement Published
Validated
Assigned finding tags:

RAACToken::burn applies burn tax twice when transferring to feeCollector, causing excess tokens to be burned and reduced fees to be collected

This is by design, sponsor's words: Yes, burnt amount, done by whitelisted contract or not always occur the tax. The feeCollector is intended to always be whitelisted and the address(0) is included in the _transfer as a bypass of the tax amount, so upon burn->_burn->_update it would have not applied (and would also do another burn...). For this reason, to always apply such tax, the burn function include the calculation (the 2 lines that applies) and a direct transfer to feeCollector a little bit later. This is done purposefully

Appeal created

inallhonesty Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

RAACToken::burn applies burn tax twice when transferring to feeCollector, causing excess tokens to be burned and reduced fees to be collected

This is by design, sponsor's words: Yes, burnt amount, done by whitelisted contract or not always occur the tax. The feeCollector is intended to always be whitelisted and the address(0) is included in the _transfer as a bypass of the tax amount, so upon burn->_burn->_update it would have not applied (and would also do another burn...). For this reason, to always apply such tax, the burn function include the calculation (the 2 lines that applies) and a direct transfer to feeCollector a little bit later. This is done purposefully

Support

FAQs

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