Users may lose airdropped tokens in ERC20Airdrop2
contract
#241
Labels
3 (High Risk)
Assets can be stolen/lost/compromised directly
bug
Something isn't working
duplicate-245
🤖_51_group
AI based duplicate group recommendation
satisfactory
satisfies C4 submission criteria; eligible for awards
upgraded by judge
Original issue severity upgraded from QA/Gas by judge
Lines of code
https://github.com/code-423n4/2024-03-taiko/blob/main/packages/protocol/contracts/team/airdrop/ERC20Airdrop2.sol#L39-L44
https://github.com/code-423n4/2024-03-taiko/blob/main/packages/protocol/contracts/team/airdrop/ERC20Airdrop2.sol#L88-L94
https://github.com/code-423n4/2024-03-taiko/blob/main/packages/protocol/contracts/team/airdrop/ERC20Airdrop2.sol#L114-L120
Vulnerability details
Issue Description
During an airdrop organized with the
ERC20Airdrop2
contract, there are two steps involved: first, the chosen users must claim their airdrop amounts by calling theclaim
function, and then they must wait until the withdrawal period begins to start withdrawing those amounts using thewithdraw
function.The withdrawal period operates similarly to a vesting period, during which initially
block.timestamp < claimEnd
, no amount can be withdrawn. Subsequently, users can gradually withdraw as time passes until the momentblock.timestamp == claimEnd + withdrawalWindow
, when the entire amount becomes withdrawable.In typical vesting scenarios, the full airdropped amount remains withdrawable after the withdrawal period has ended. However, in the case of
ERC20Airdrop2
, this is not the case due to the presence of theongoingWithdrawals
modifier within thewithdraw
function:The
ongoingWithdrawals
modifier allows users to withdraw only during the withdrawal period, defined asclaimEnd <= block.timestamp <= claimEnd + withdrawalWindow
, as illustrated in the code below:It's important to note that the full airdropped amount becomes withdrawable only when
block.timestamp == claimEnd + withdrawalWindow
, as enforced by thegetBalance
function invoked within thewithdraw
function:This setup can lead to users losing a part or all of their airdropped tokens.
Let's consider two scenarios to illustrate this issue:
- Scenario 1 (relatively low impact):
Bob was airdropped 1000 tokens which he claimed using the
claim
function.After 80% of the withdrawal period (
withdrawalWindow
) has passed, Bob decides to withdraw some of his airdropped tokens.According to
getBalance
, he should receive 80% of his total airdropped amount (the maximum withdrawable amount at that time), so Bob receives 800 tokens.When the withdrawal period (
withdrawalWindow
) is about to end, Bob submits a transaction to withdraw the remaining balance of his airdrop, 1 hour before the end (claimEnd + withdrawalWindow
).However, Bob's transaction remains pending for more than 1 hour, and when it finally gets executed, the withdrawal period has ended, causing Bob's
withdraw
call to revert.As
ongoingWithdrawals
only allows withdrawals during the specified period, Bob will not be able to callwithdraw
again, resulting in the loss of his remaining 200 airdropped tokens.- Scenario 2 (higher impact):
Alice was airdropped 1000 tokens which she claimed using the
claim
function.Alice chooses not to withdraw until the last moment (the end of the withdrawal period) to receive her full airdropped amount.
As the withdrawal period (
withdrawalWindow
) is about to end, Alice submits a transaction to withdraw the remaining balance of her airdrop, 1 hour before the period ends (claimEnd + withdrawalWindow
).However, Alice's transaction remains pending for more than 1 hour, and when it finally gets executed, the withdrawal period has ended, causing Alice's
withdraw
call to revert.As
ongoingWithdrawals
only allows withdrawals during the specified period, Alice will not be able to callwithdraw
again, resulting in the loss of all her airdropped tokens.In both scenarios, users risk losing a portion or all of their airdropped tokens due to the unpredictability of transaction execution timestamps (
block.timestamp
). And this aren't exceptional scenarios as this is expected to happen to many other users because the tx execution timestampblock.timestamp
is not the one in which the tx is submitted but rather the time of the block in which the tx is included which isn't always predictable as tx can stay pending in the mempool for many hours (or even days).And the fact that the full airdropped amount is only withdrawable at a specific timestamp
claimEnd + withdrawalWindow
makes the chances of this issue occurring even more frequent as users will want to get the maximum amount of tokens and end up losing some of itImpact
Users may lose a portion or all their airdropped amounts because of the withdrawable period.
Tools Used
Manual review, VS Code
Recommended Mitigation
One possible solution to address this issue is to remove the
ongoingWithdrawals
modifier from thewithdraw
function. This modification would allow users to withdraw their airdropped tokens at any time, mitigating the risk of loss due to transaction execution timing.Assessed type
Context
The text was updated successfully, but these errors were encountered: