Core Contracts

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

Double Taxation Vulnerability in RAAC Token

Summary

A critical vulnerability has been identified in the RAAC token's burn mechanism where users are subjected to double taxation during the burn operation. This occurs due to an interaction between the burn() function and the underlying _update() mechanism, resulting in excessive token burns and reduced tax collection.

Vulnerability Details

The vulnerability exists in the burn() function implementation:

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 issue arises because:

  • The function first calculates and applies the burn tax

  • Then uses _transfer for the tax amount, which triggers _update()

  • The _update() function applies the tax again on the tax transfer

Example calculation for burning 1000 tokens with 0.5% burn tax:

1. Initial tax calculation: 1000 0.5% = 5 tokens

2. First operation: Burns 995 tokens

  • Second operation: Transfers 5 tokens but gets taxed again

  • Final result:

  • FeeCollector receives 4.975 tokens (instead of 5)

  • Additional 0.025 tokens are burned

  • Total burned: 995.025 tokens (more than intended)

POC

describe("Burn Function Security", () => {
it("POC: should demonstrate burn function double-deduction vulnerability", async () => {
const burnAmount = ethers.parseEther("1000"); // 1000 tokens
const user = users[0];
// Mint exactly burnAmount to the user
await raacToken.mint(user.address, burnAmount);
const initialSupply = await raacToken.totalSupply();
const initialBalance = await raacToken.balanceOf(user.address);
expect(initialBalance).to.equal(burnAmount);
// Calculate expected amounts
const taxAmount = (burnAmount * BURN_RATE) / 10000n; // 0.5% tax
const actualBurnAmount = burnAmount - taxAmount;
// Perform the burn
await raacToken.connect(user).burn(burnAmount);
// Check final state
const finalBalance = await raacToken.balanceOf(user.address);
const finalSupply = await raacToken.totalSupply();
const feeCollectorBalance = await raacToken.balanceOf(feeCollector.target);
console.log("Initial Balance:", initialBalance.toString());
console.log("Tax Amount:", taxAmount.toString());
console.log("Actual Burn Amount:", actualBurnAmount.toString());
console.log("Final Balance:", finalBalance.toString());
console.log("Fee Collector Balance:", feeCollectorBalance.toString());
console.log("Supply Change:", (initialSupply - finalSupply).toString());
// Issue 1: Incorrect tax calculation
// When a user requests to burn 1000 tokens, the contract:
// 1. First calculates the tax: 1000 * 0.5% = 5 tokens
// 2. Burns 995 tokens
// 3. Then applies a 0.5% tax to the remaining 995 tokens ≈ 4.975 tokens
expect(feeCollectorBalance).to.be.lt(taxAmount,
"Fee collector received less tax than expected due to double calculation");
// Issue 2: Total supply decreased more than expected
// Expected decrease: 995 tokens (1000 - 5 tax)
// Actual decrease: 995.025 tokens (including the repeated calculation of tax)
const expectedSupplyReduction = actualBurnAmount;
const actualSupplyReduction = initialSupply - finalSupply;
expect(actualSupplyReduction).to.be.gt(expectedSupplyReduction,
"Total supply reduced more than expected due to double taxation");
});
});

Impact

The vulnerability has three main impacts:

  • Protocol Revenue Loss: The protocol receives less tax than intended (4.975 instead of 5 tokens in the example)

  • Excessive Token Burn: More tokens are burned than should be (995.025 instead of 995)

  • User Asset Loss: Users lose more tokens than they should in the burn process

While the impact per transaction is small (0.025% in this example), it becomes significant with:

  • Large burn transactions

  • High frequency of burns

  • Long-term accumulation of losses

Severity: Medium

  • The issue is mathematically certain to occur

  • Results in direct financial loss

  • Affects both users and protocol revenue

  • But the per-transaction impact is relatively small

Tools Used

  • Manual code review

  • Unit testing with Hardhat

  • Mathematical analysis

  • POC test case demonstrating the vulnerability

Updates

Lead Judging Commences

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