When the FeeCollector contract calls distributeCollectedFees(), it attempts to burn tokens through raacToken.burn(shares[1]). However, the RAAC token's burn function applies a tax on the burn amount and sends this tax back to the FeeCollector. This creates an accounting discrepancy where not all tokens intended for burning are actually burned, and some remain in the FeeCollector contract unaccounted for.
The issue occurs in the following sequence:
Fees are collected via FeeCollector::collectFee()for the feeType 6 (swapTaxes)
distributeCollectedFees()gets called to distribute the collected fees
distributeCollectedFees() calls _processDistributions()
_processDistributions() function calls raacToken.burn(shares[1]) to burn a portion of collected fees
Even though FeeCollector is whitelisted, the burn tax is still applied
These returned tokens are not accounted for in the FeeCollector's accounting system
Inside RAACToken's burn() function:
The test demonstrate this behavior:
In order to run the test you need to:
Run foundryup to get the latest version of Foundry
Install hardhat-foundry: npm install --save-dev @nomicfoundation/hardhat-foundry
Import it in your Hardhat config: require("@nomicfoundation/hardhat-foundry");
Make sure you've set the BASE_RPC_URL in the .env file or comment out the forking option in the hardhat config.
Run npx hardhat init-foundry
There is one file in the test folder that will throw an error during compilation so rename the file in test/unit/libraries/ReserveLibraryMock.sol to => ReserveLibraryMock.sol_broken so it doesn't get compiled anymore (we don't need it anyways).
Create a new folder test/foundry
Paste the below code into a new test file i.e.: FoundryTest.t.sol
Run the test: forge test --mc FoundryTest -vvvv
Not all tokens intended for burning are actually burned
Creates accounting discrepancies in the FeeCollector contract
Results in accumulation of unaccounted tokens in the FeeCollector contract
May affect protocol tokenomics as burn mechanism is not working as intended
The severity is considered LOW because:
The FeeCollector has an emergency withdrawal mechanism that can recover stuck tokens (but only when the contract is paused)
The tokens remain within the protocol's control
No direct financial loss to users
The issue doesn't affect core protocol functionality
Manual Review
Foundry
Modify the burn function to skip tax application when the caller is the feeCollector or add a separate burnWithoutTax function that can only be called by the FeeCollector
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.