Stable Cyan Stallion
Medium
Users can bypass tax amount for airdrop claims during the final 26 minutes/1572 seconds/131 blocks of the claiming period
Due to the calculation in _calculateClaimTaxAmount()
, users can claim their airdrop tokens without paying any tax during the final 26
minutes (1572
seconds) of the airdrop claiming period. This vulnerability stems from a precision issue where division precedes multiplication in the tax calculation formula.
In the _calculateClaimTaxAmount()
function, taxFee
is derived by multiplying $.maxChargeableTax
by the remaining claiming time, and then dividing by AIRDROP_CLAIMING_PERIOD_LENGTH
uint256 taxFee = $.maxChargeableTax.mulDiv(claimingTimeLeft, AIRDROP_CLAIMING_PERIOD_LENGTH);
claimTaxAmount = claimerUsd0PPBalance.mulDiv(taxFee, BASIS_POINT_BASE);
When the claimingTimeLeft
approaches very low values near the end of the airdrop period, the calculation for claimTaxAmount
fails to capture a meaningful tax fee. Specifically:
- The tax calculation divides
$.maxChargeableTax
by the claiming periodAIRDROP_CLAIMING_PERIOD_LENGTH
before multiplying it byclaimingTimeLeft
. - This division results in a zero or near-zero tax fee when
claimingTimeLeft
becomes small, allowing users to avoid paying tax within a "free claim window" as the period ends.
- Assume
AIRDROP_CLAIMING_PERIOD_LENGTH = 182 days
and$.maxChargeableTax = BASIS_POINT_BASE = 10,000
. - With
claimingTimeLeft = 1572 seconds
(approximately 26 minutes) before the claiming period ends. - The existing formula of tax fee calculation would result in a zero
claimTaxAmount
due to precision loss, allowing users to avoid tax entirely if they claim in this final 26-minute period.
AIRDROP_CLAIMING_PERIOD_LENGTH = 182 days = 15,724,800 seconds
$.maxChargeableTax = BASIS_POINT_BASE = 10,000
claimerUsd0PPBalance = 1e18
claimingTimeLeft = 1572 seconds
taxFee = ( 10,000 * 1572 ) / 15,724,800 = 0
claimTaxAmount = ( 1e18 * 0 ) / 10,000 = 0
- An airdrop has started and the claiming window has reached to last 26 minutes
No response
- Any claim at this moment can be performed without paying any tax amount.
This vulnerability creates an unintended "free claim window" towards the end of the airdrop period, where users can avoid the intended tax by claiming just before the period expires. This could lead to significant losses in expected tax revenue for the protocol.
function testDivisionBeforeMultiplication()
external
userHasPrelaunchBalance(alice, 1500 ether)
afterClaimingPeriodStart(AIRDROP_CLAIMING_PERIOD_LENGTH - 1572) // 1572 is 26 Minute and 12 Seconds
{
uint256 taxAmount = airdropTaxCollector.calculateClaimTaxAmount(alice);
assertEq(taxAmount, 0);
}
Please place this test case in packages/solidity/test/airdrop/AirdropTaxCollector.t.sol
and run forge test --match-test testDivisionBeforeMultiplication
To resolve this issue, change the tax calculation formula like:
function _calculateClaimTaxAmount(AirdropTaxCollectorStorage storage $, address account)
internal
view
returns (uint256 claimTaxAmount)
{
uint256 claimerUsd0PPBalance = $.prelaunchUsd0ppBalance[account];
uint256 claimingTimeLeft;
if (block.timestamp > AIRDROP_INITIAL_START_TIME + AIRDROP_CLAIMING_PERIOD_LENGTH) {
claimingTimeLeft = 0;
} else {
claimingTimeLeft =
AIRDROP_INITIAL_START_TIME + AIRDROP_CLAIMING_PERIOD_LENGTH - block.timestamp;
}
- uint256 taxFee = $.maxChargeableTax.mulDiv(claimingTimeLeft, AIRDROP_CLAIMING_PERIOD_LENGTH);
- claimTaxAmount = claimerUsd0PPBalance.mulDiv(taxFee, BASIS_POINT_BASE);
+ claimTaxAmount = claimerUsd0PPBalance.mulDiv($.maxChargeableTax * claimingTimeLeft, AIRDROP_CLAIMING_PERIOD_LENGTH * BASIS_POINT_BASE);
}