Vyper Vested Claims

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

No Check for Duplicate Claims

Summary

The contract does not explicitly prevent duplicate claims for the same total_amount and proof. If a user submits the same proof multiple times, they could potentially claim more tokens than intended.

@external
def claim(user: address, total_amount: uint256, proof: DynArray[bytes32, 20]) -> bool:
"""
@notice This function is used to claim the tokens
@dev Anyone can claim for any user
@param user address, the address of the user
@param total_amount uint256, the total amount of tokens
@param proof DynArray[bytes32, 20], the merkle proof
@return bool True if the claim is successful
"""
# Checks
assert self.verify_proof(user, total_amount, proof), "Invalid proof"
assert block.timestamp >= self.vesting_start_time, "Claiming is not available yet"
claimable: uint256 = 0
current_amount: uint256 = self.claimed_amount[user]
vested: uint256 = self._calculate_vested_amount(total_amount)
# Calculate how much the user can claim now
if vested > current_amount:
claimable = vested - current_amount
assert claimable > 0, "Nothing to claim"
# Update the claimed amount - Effects
self.claimed_amount[user] += claimable
# invariant: claimed amount should always be less than or equal to amount (better safe then sorry)
assert current_amount + claimable <= total_amount, "Claimed amount exceeds total amount"
log Claimed(user, claimable)
# Transfer the claimable amount to the user - Interactions
_success: bool = extcall IERC20(self.token).transfer(user, claimable)
assert _success, "Transfer failed"
return True

Vulnerability Details

The issue of duplicate claims will arise when a user can submit the same proof multiple times to claim tokens, potentially allowing them to claim more tokens than they are entitled to. This can happen if the contract does not properly track or validate whether a claim has already been made for a specific user and total_amount.

Impact

Users could exploit this to claim more tokens than they are entitled to.

Tools Used

Manual Review

Recommendations

To prevent duplicate claims in the claim function, I added a check to ensure that the same user and total_amount combination cannot be claimed more than once. I added a mapping to track whether a specific user and total_amount combination has already been claimed.

# Add the new mapping to track claims
+ claimed_amount: public(HashMap[address, uint256])
@external
def claim(user: address, total_amount: uint256, proof: DynArray[bytes32, 20]) -> bool:
"""
@notice This function is used to claim the tokens
@dev Anyone can claim for any user
@param user address, the address of the user
@param total_amount uint256, the total amount of tokens
@param proof DynArray[bytes32, 20], the merkle proof
@return bool True if the claim is successful
"""
# Generate a unique claim ID for the user and total_amount combination
+ claim_id: bytes32 = keccak256(concat(convert(user, bytes20), convert(total_amount, bytes32)))
# Check if the claim has already been processed
+ assert not self.claimed[claim_id], "Claim already processed"
# Checks
assert self.verify_proof(user, total_amount, proof), "Invalid proof"
assert block.timestamp >= self.vesting_start_time, "Claiming is not available yet"
claimable: uint256 = 0
current_amount: uint256 = self.claimed_amount[user]
vested: uint256 = self._calculate_vested_amount(total_amount)
# Calculate how much the user can claim now
if vested > current_amount:
claimable = vested - current_amount
assert claimable > 0, "Nothing to claim"
# Update the claimed amount - Effects
self.claimed_amount[user] += claimable
# Mark the claim as processed
+ self.claimed[claim_id] = True
# invariant: claimed amount should always be less than or equal to amount (better safe then sorry)
assert current_amount + claimable <= total_amount, "Claimed amount exceeds total amount"
log Claimed(user, claimable)
# Transfer the claimable amount to the user - Interactions
_success: bool = extcall IERC20(self.token).transfer(user, claimable)
assert _success, "Transfer failed"
return True
Updates

Appeal created

bube Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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