The _calculate_vested_amount
function uses integer division to calculate the instant release and linear vesting portions, which can lead to token loss due to rounding errors when the total amount is not perfectly divisible by 100.
The vesting calculation splits the total amount into two parts using integer division:
When total_amount
is not perfectly divisible by 100, both calculations will lose some precision due to truncation in integer division. This means that instant_release + linear_vesting
might not equal total_amount
, resulting in some tokens being permanently locked in the contract.
For example:
If total_amount = 101
:
instant_release = (101 * 31) // 100 = 31.31 → 31
(truncated)
linear_vesting = (101 * 69) // 100 = 69.69 → 69
(truncated)
instant_release + linear_vesting = 31 + 69 = 100
(not 101!)
Result: 1 token is lost due to rounding errors
The following Python code demonstrates the rounding issue:
When executed, this PoC shows that:
For amounts divisible by 100 (like 100, 1000), no tokens are lost
For amounts not divisible by 100 (like 101, 199, 999, 1001, 10001), 1 token is lost due to rounding
The percentage of loss varies by allocation size (0.99% for 101 tokens, 0.01% for 10001 tokens)
Token Loss: Some tokens might be permanently locked in the contract because they can never be claimed due to rounding errors
Accounting Discrepancy: The sum of all claimed tokens might be less than the total tokens allocated in the Merkle tree
Fairness Issue: Users with certain allocation amounts might lose more tokens proportionally than others
The severity is low because:
The contract correctly returns the full amount at the end of vesting with if current_time >= end_time: return total_amount
The percentage of tokens lost is very small (typically 1 token)
It's a precision issue rather than a security vulnerability
While the contract already handles the end of vesting correctly, for better precision during the vesting period, consider one of these approaches:
Adjust one of the percentages to ensure they sum to 100% after division:
Or calculate the percentage first, then apply it once:
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.