Revoke Function Forfeits Already-Vested Tokens Contrary to Documentation
Description
The revoke() function's documentation states it "forfeits all unvested tokens," implying that already-vested tokens remain claimable. However, the implementation sets terminated_timestamp = 1 (a sentinel value), which causes all subsequent withdraw() calls to abort with ERR_ALREADY_TERMINATED, preventing withdrawal of any tokens including those already vested.
Code evidence:
In terminate_allocation():
distribution_state.terminated_timestamp = if (is_being_revoked) 1 else (clock.timestamp_ms() / 1000) as u32;In withdraw():
assert!(distribution_state.terminated_timestamp != 1, ERR_ALREADY_TERMINATED);This differs from cancel(), which correctly sets terminated_timestamp to the current time, allowing beneficiaries to claim tokens vested up to the cancellation point.
Impact
Beneficiaries lose access to tokens they have already earned through time passage. For example:
A 4-year vesting schedule is 90% complete (3.6 years elapsed)
Beneficiary has not yet withdrawn
Benefactor calls
revoke()Beneficiary permanently loses 90% of their allocation despite those tokens being fully vested
This violates standard vesting semantics where "revoke" typically means "stop future vesting but preserve earned amounts".
Recommendations
Option 1: Match documentation behavior
Allow withdrawal of tokens vested up to the revocation timestamp:
Option 2: Update documentation to match implementation
If the current behavior is intentional, update the comment to:
/// @notice Revokes a vesting allocation (forfeits ALL tokens, including already vested)And clearly communicate this behavior to beneficiaries.
Remediation
This issue has been acknowledged by Magna, and a fix was implemented in commit e95f43bf↗.
Magna confirmed the function documentation was inaccurate and that the behavior is intentional.