Vyper Vested Claims

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

Inconsistent State on Merkle Root Update (Claimed Amount Mapping Not Reset)

Summary

Updating the Merkle root via set_merkle_root does not reset or adjust the claimed_amount mapping. This misalignment between the new user allocations (as defined by the updated root) and the historical claimed amounts can lead to users being able to claim additional tokens beyond their intended allocation.

Vulnerability Details

The set_merkle_root function allows the contract owner to change the Merkle root at any time:

@external
def set_merkle_root(merkle_root: bytes32):
self.onlyOwner()
self.merkle_root = merkle_root
log MerkleRootUpdated(merkle_root)

However, when this update occurs, the contract does not modify the claimed_amount mapping that tracks the cumulative tokens claimed by each user. If the new Merkle tree defines different per-user allocations than the previous one, the already claimed amounts remain based on the old allocations. This discrepancy can be exploited if the new total allocation for a user is higher than before, allowing the user to claim additional tokens computed as:

claimable = new_vested_amount (from new total_amount) - previously_claimed_amount

Impact

  • Overclaiming Risk: Users may be able to claim more tokens than originally allocated. For example, if a user's allocation is increased in the new Merkle tree while their claimed_amount remains unchanged, they can claim the difference, effectively receiving extra tokens.

  • Vesting Schedule Disruption: The vesting logic becomes inconsistent as the claimed_amount no longer aligns with the allocations defined by the current Merkle root.

  • Centralization Abuse: A malicious owner could intentionally update the Merkle root to inflate user allocations and, if colluding with a beneficiary, allow them to claim an excessive number of tokens.

PoC

  • Scenario Example:
    UserA is allocated 1,000 tokens in the initial Merkle tree and claims 310 tokens (31% TGE). Later, the owner updates the Merkle root to reflect a new allocation of 2,000 tokens for UserA without resetting claimed_amount. The contract now computes:

    vested = vested amount based on 2,000 tokens
    claimable = vested - 310

    Over time, as vested increases according to the new schedule, UserA can claim more tokens than originally intended.

Tools Used

N/A

Recommendations

When updating the Merkle root, either:
- Disallow updates once claims have begun, or
- Reset the claimed_amount mapping to zero (or adjust it in line with the new allocations) to ensure consistency between historical claims and the new Merkle tree.

Updates

Lead Judging Commences

bube Lead Judge
4 months ago

Appeal created

bube Lead Judge 4 months ago
Submission Judgement Published
Validated
Assigned finding tags:

`set_merkle_root` doesn't reset the `claimed_amount`

If the contract is reused with another Merkle root and there is a user participated in the previous airdrop, user's claimed amount will not be set to 0. This means if the new total amount is 1000 and the user has claimed amount (from previous aidrop) equals to 500, the user will be able to receive only 500 tokens instead of intended 1000. The `set_merkle_root` is called only by the owner who is trusted. Therefore, we can assume that the owner will not call the function during the existing vesting period or before all users claim their token amounts. The owner can check manually if all users have claimed their tokens.

freesultan Auditor
4 months ago
bube Lead Judge
4 months ago
bube Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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