Vyper Vested Claims

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

Unrestricted Owner Token Rescue Function in the `rescue_tokens` function.

Description: The rescue_tokens function allows the owner to withdraw any amount of tokens from the contract, including tokens that are meant to be distributed to users through the vesting schedule. There are no safeguards to prevent the owner from draining all tokens, effectively stealing from users with valid claims.

Lines 167-177:

@external
def rescue_tokens(to: address, amount: uint256):
"""
@notice This function is used to rescue tokens from the contract
@param to address, the address to send the tokens to
@param amount uint256, the amount of tokens to send
@dev this is a "better safe then sorry" function, use it only in case of emergency
@dev This function can only be called by the owner
"""
self.onlyOwner()
log TokensRescued(to, amount)
_success: bool = extcall IERC20(self.token).transfer(to, amount)
assert _success, "Transfer failed"

Impact: A malicious (or compromised) owner could:

  1. Drain all tokens from the contract

  2. Prevent legitimate users from claiming their vested tokens

  3. Effectively steal all funds meant for distribution

While centralization risks are often rated as "low" severity, this is a high severity issue because there is no protection whatsoever against the owner taking all funds at any time, violating the core security assumption of a vesting contract.

Recommended Mitigation: Add restrictions to the rescue_tokens function to ensure it can only rescue tokens that aren't allocated to users:

total_allocated_tokens: uint256
@external
def add_allocations(allocations: DynArray[Allocation, 100]):
"""
@notice Add or update token allocations
@dev Only callable by owner
"""
self.onlyOwner()
for allocation in allocations:
self.total_allocated_tokens += allocation.amount
@external
def rescue_tokens(to: address, amount: uint256):
self.onlyOwner()
# Get the contract balance
contract_balance: uint256 = extcall IERC20(self.token).balanceOf(self)
# Calculate the total amount that should remain locked
locked_amount: uint256 = self.total_allocated_tokens
for allocation in already_claimed:
locked_amount -= allocation.claimed_amount
# Ensure we're not rescuing tokens meant for users
assert amount <= contract_balance - locked_amount, "Cannot rescue allocated tokens"
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.