Core Contracts

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

Fee Collector receives less fees when a user burns RAACToken

Summary

A vulnerability exists in the RAACToken smart contract that causes the fee collector to receive less tax than intended whenever a user burns tokens. This issue arises because of the burn effect introduced by RAACToken:: _update , which applies a secondary tax deduction on the tax amount before it reaches the fee collector.

This unintended behavior reduces protocol revenue, causing a potential loss of funds for the fee collector and affecting any protocol mechanisms that depend on these collected fees.

Vulnerability Details

RAACToken::burn() function in RAACToken is structured as follows:

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 burn tax is first calculated as taxAmount = amount * burnTaxRate / 10000.
The remaining amount (amount - taxAmount) is sent to _burn().
The tax amount is then sent to the feeCollector via _transfer().

RAACToken overrides the ERC20 _update() function, which applies a tax whenever a transfer occurs:

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);
}

Key Issue:

RAACToken::_burn() internally calls RAACToken::_update() with to = address(0).
Since RAACToken::_update() skips taxes when to = address(0), no additional tax is applied when _burn() is executed.
However, when the tax amount is sent to the fee collector (_transfer(msg.sender, feeCollector, taxAmount)), it goes through RAACToken::_update() again, where a second burn tax is applied to the tax amount.
This means that some of the tax that should go to the fee collector is instead burned, reducing protocol revenue.

Proof Of Code (POC)

This test was run in the RAACToken.test.js file in the "Tax Calculation" describe block

it("feecollector doesnt get full fees from user burns", async () => {
const transferAmount = BigInt("1000000000000000000000"); // 1000 tokens
const expectedTax = (transferAmount * TAX_RATE) / 10000n;
const expectedBurn = (transferAmount * BURN_RATE) / 10000n;
// Mint tokens to first user (using owner who is now minter)
await raacToken.mint(users[0].address, transferAmount);
// Track balances before transfer
const initialSupply = await raacToken.totalSupply();
const initialFeeCollector = await raacToken.balanceOf(
feeCollector.target
);
console.log(
"Initial Fee Collector Balance: ",
initialFeeCollector.toString()
);
console.log("Initial Supply: ", initialSupply.toString());
// Perform burn
await raacToken.connect(users[0]).burn(transferAmount);
//c since we know there will be a tax on burns, this is how much we expect to be burnt
const expectedBurnamount = transferAmount - expectedBurn;
console.log("Expected Burn Amount: ", expectedBurnamount.toString());
//c get expected amount to be sent to fee collector
const expectedFeeCollectorAmount = initialSupply - expectedBurnamount;
console.log(
"Expected Fee Collector Amount: ",
expectedFeeCollectorAmount.toString()
);
//c get actual amount sent to fee collector
const postBurnFeeCollector = await raacToken.balanceOf(
feeCollector.target
);
console.log(
"Post Burn Fee Collector Balance: ",
postBurnFeeCollector.toString()
);
//c due to the extra burn in transfer function, the fee collector will get less than the expected amount
assert(
expectedFeeCollectorAmount.toString() > postBurnFeeCollector.toString()
);
});

Impact

Fee Collector Receives Less Revenue Than Expected: The protocol expects full tax collection from token burns. Instead, some of the tax is being burned, causing a revenue shortfall.

Negative Effect on Protocol Mechanisms That Depend on Fees : RAAC Ddocumentation at https://docs.raac.io/quickstart/about-raac say the following about the RAACToken:

"It includes a built-in tax and fee collection mechanisms that is collected by the protocol in order to finance real-world aspects related to the properties (for instance, repairs). " If fee collection funds are reduced, RAAC has less funds to perform its real-world aspects

Tools Used

Manual Review, Hardhat

Recommendations

Modify _update() to exempt transfers where to == feeCollector so that the tax amount is not reduced by an extra burn.

if (baseTax == 0 || from == address(0) || to == address(0) || to == feeCollector || whitelistAddress[from] || whitelistAddress[to] || feeCollector == address(0)) {
super._update(from, to, amount);
return;
}

Now, tax is not applied when sending tax amounts to the fee collector.

Updates

Lead Judging Commences

inallhonesty Lead Judge 7 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 7 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 6 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.

Give us feedback!