Vyper Vested Claims

First Flight #34
Beginner FriendlyDeFi
100 EXP
View results
Submission Details
Severity: low
Invalid

Emergency function `rescue_tokens` could lead to broken claim function

Summary

User with vesting allocation will not be able to claim after rescue_tokens emptied the contract's balance

Vulnerability Details

Owner can withdraw tokens from the contract anytime with any amount, including during the claiming period. This enables the owner to empty the contract's token. Hence, the transfer function will fail when people are trying to claim, despite having allocation.

Impact

User is unable to claim despite having an allocation

PoC

def test_vuln_claim_after_rescue_token(self):
amount = 100000000000000000000000
self.airdrop.rescue_tokens(self.token.address, amount)
# this will revert, transfer will fail
with boa.reverts():
self.airdrop.claim(self.user1, self.amount, self.proof)

Recommendations

Add deadline period to rescue_tokens

diff --git a/src/VestedAirdrop.vy b/src/VestedAirdrop.vy
index 5b00ff7..0e15a0c 100644
--- a/src/VestedAirdrop.vy
+++ b/src/VestedAirdrop.vy
@@ -10,6 +10,7 @@ interface IERC20:
vesting_start_time: public(uint256)
vesting_end_time: public(uint256)
+deadline: public(uint256)
merkle_root: public(bytes32)
token: public(address)
owner: public(address)
@@ -25,7 +26,7 @@ event TokensRescued:
amount: uint256
@deploy
-def __init__(merkle_root: bytes32, token: address, vesting_start_time: uint256, vesting_end_time: uint256):
+def __init__(merkle_root: bytes32, token: address, vesting_start_time: uint256, vesting_end_time: uint256, deadline: uint256):
"""
@notice Initialize the contract with the merkle root, token address, vesting start and end time
@param merkle_root: bytes32, the merkle root of the vesting list
@@ -33,10 +34,12 @@ def __init__(merkle_root: bytes32, token: address, vesting_start_time: uint256,
@param vesting_start_time: uint256, the start time of the vesting
@param vesting_end_time: uint256, the end time of the vesting
"""
+ assert deadline > vesting_end_time
self.merkle_root = merkle_root
self.token = token
self.vesting_start_time = vesting_start_time
self.vesting_end_time = vesting_end_time
+ self.deadline = deadline
self.owner = msg.sender
log MerkleRootUpdated(merkle_root)
@@ -132,6 +135,7 @@ def rescue_tokens(to: address, amount: uint256):
@dev This function can only be called by the owner
"""
self.onlyOwner()
+ assert block.timestamp >= self.deadline
log TokensRescued(to, amount)
_success: bool = extcall IERC20(self.token).transfer(to, amount)
assert _success, "Transfer failed"
Updates

Appeal created

bube Lead Judge 6 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

[Invalid] Owner can call rescue_tokens and withdraw users tokens

The `owner` is trusted and the function `rescue_tokens` can be called only by the owner and only in case of emergency. This means the owner will not act maliciously and will not call the function without need. Also, issues realated to the malicious admin actions are invalid according to the CodeHawks documentation: https://support.cyfrin.io/en/articles/10059196-findings-validity

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.