The RAACToken::_update
function does not correctly account for burn tax distribution, leading to the feeCollector
receiving a lower-than-expected amount. The issue arises due to incorrect handling of tax calculations and improper sequencing of _update
calls.
According to documenation:
Implements a burn function with a separate burn tax mechanism
The RAACToken::burn
function is expected to apply a burn tax based on the user’s desired amount multiplied by the burnTaxRate
. However, the way _update
processes the tax distribution causes the feeCollector
to receive less than the expected burn tax amount.
The RAACToken::burn
function is expected to apply a burn tax based on the user’s desired amount
multiplied by the burnTaxRate
. However, the way _update
processes the tax distribution causes the feeCollector
to receive less than the expected burn tax amount.
Consider the following scenario:
A user holds 100e18
RAAC tokens and intends to burn them.
The burnTaxRate
is set to 0.5%
(default value).
According to expectations, the feeCollector
should receive 100e18 * 0.5% = 5e17
RAAC tokens
The RAACToken::burn
function calls _transfer(msg.sender, feeCollector, taxAmount)
. This invokes _update(from, to, amount)
with (user, feeCollector, taxAmount)
.
Inside _update
, the if-condition fails to check to == feeCollector
, leading to an additional tax application:
This results in:
Additional amount * burnTaxRate
being burned to the zero address.
Only the remaining amount being sent to feeCollector
, reducing its expected fee.
Below is a test case demonstrating the issue:
Problematic code section: https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/tokens/RAACToken.sol#L185
FeeCollector Receives Less Fees: The incorrect tax accounting results in the feeCollector
receiving fewer tokens than expected.
Incorrect Burn Mechanism: The function unintentionally redistributes the burn tax incorrectly, deviating from expected behavior.
Manual Review
To fix this issue, update _update to correctly check when to == feeCollector and prevent double taxation:
By implementing this fix, the feeCollector will receive the expected burn tax, ensuring proper accounting and preventing unintended losses.
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
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
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
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.