Track 8 · Capstones · lesson 6
Token vesting
120 min
A vesting contract: tokens locked for a beneficiary, released gradually over time. Spec and test names again — you build it.
Scaffolding tier: spec. A specification and the tests it must pass. This one leans on time, which the EVM handles in a way worth getting right.
Specification
Tokens are deposited for a beneficiary and vest linearly over a schedule:
- A cliff: nothing is claimable until the cliff time passes.
- After the cliff, tokens vest linearly until a final end time, at which point the full amount is claimable.
- The beneficiary can claim the vested-but-unclaimed amount at any point.
- Claiming twice does not pay out twice — the contract tracks what has already been released.
- Optionally, the owner can revoke unvested tokens, returning them; already- vested tokens remain the beneficiary's.
The tests it must pass
test_NothingClaimableBeforeCliff
test_LinearVestingAfterCliff
test_FullyVestedAtEnd
test_Claim_TransfersOnlyNewlyVested
test_Claim_RevertsWithNothingToClaim
test_Revoke_ReturnsOnlyUnvested
The vesting maths is a proportion of elapsed time:
vested = total * (now - start) / (end - start)
claimable = vested - alreadyReleased
Use block.timestamp for "now" — and remember from track 1 that a validator can
nudge it by seconds. That wobble is harmless over a months-long schedule, which
is exactly why timestamps are fine here and dangerous for a coin flip. Match the
tool to the tolerance.
Check
Vesting relies on `block.timestamp`, which validators can nudge. Why is that acceptable here?
Worth remembering
- Capstone 6, spec scaffolding: specification and test names only.
- Linear vesting with a cliff, claimable incrementally, no double payout.
- vested = total * elapsed / duration; claimable subtracts what's already released.
- `block.timestamp` is fine here because seconds of drift are negligible over months.