Assessment reports>Airlock>Informational findings>Revoke Function Forfeits Already-Vested Tokens Contrary to Documentation
Category: Business Logic

Revoke Function Forfeits Already-Vested Tokens Contrary to Documentation

Informational Impact
Informational Severity
N/A Likelihood

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

  1. 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.

Zellic © 2025Back to top ↑