Token Economics
Vesting03 Jul 20267 min read

Vesting Smart Contracts: 7 Patterns Audits Flag

A vesting contract that owner can drain isn't a vesting contract.

Smart-contract auditors look at vesting contracts with a different lens than founders do. Founders care about “does this match my schedule?” Auditors care about “can this be drained, frozen, or accelerated by anyone?” Here are seven patterns that consistently fail audits — and the fixes.

DEFINITION

A vesting smart contract holds tokens on behalf of a beneficiary and releases them according to a schedule. Common implementations: OpenZeppelin VestingWallet, Sablier streams, Hedgey lockup NFTs, or custom contracts. Audits focus on access controls, emergency paths, and arithmetic correctness.

1. Owner can revoke at any time

The most-flagged failure: the contract has a revoke() function callable by an admin/owner that pulls all unvested tokens back to the deployer. Often implemented for “just in case the recipient leaves the team”.

ANTIPATTERN
// BAD: owner can revoke at any time, draining unvested tokens
function revoke(address beneficiary) external onlyOwner {
    uint256 unvested = _totalAllocated[beneficiary] - _vested(beneficiary);
    token.transfer(owner(), unvested);
}

Audit finding: this contract does not actually lock tokens. The owner controls the entire allocation. The vesting schedule is decorative.

Fix: remove revoke entirely (immutable lockup), or restrict it to a defined leaver clause (e.g., termination + 30-day notice + multisig consensus). The OpenZeppelin VestingWalletWithCliff + ownership renunciation pattern is audit-clean.

2. Beneficiary can be reassigned

The contract permits the admin to change beneficiary after deployment. Implies the team retains the option to redirect locked tokens to a different address — defeating the lockup’s purpose.

ANTIPATTERN
// BAD: admin can reassign beneficiary
address public beneficiary;
function setBeneficiary(address newBeneficiary) external onlyOwner {
    beneficiary = newBeneficiary;
}

Fix: make beneficiary immutable at deployment. Or allow only the existing beneficiary to nominate a successor (with a multi-day delay).

3. Schedule can be modified post-deployment

Contract has setter functions for cliffDuration, vestingDuration, or tgeUnlockPercent. The team can rewrite the schedule whenever they want.

Fix: all schedule parameters set in constructor, marked immutable. Documented as immutable in the contract’s natspec.

4. Block.timestamp manipulation

Vesting math depends on block.timestamp. On L1 Ethereum, a validator can shift timestamps by ~12 seconds in either direction. On some sidechains, much more. If your vesting unlocks based on seconds, edge cases at cliff boundaries can be exploited.

EDGE CASE
// At cliff boundary, validator pushes timestamp +13s
// The cliff has just become reachable
function release() external {
    require(block.timestamp >= cliffEnd, "before cliff");
    // ... releases immediately, taking advantage of validator nudge
}

Fix: prefer block-number-based vesting on L1s where blocks are regular, or add a small buffer (e.g., require timestamp ≥ cliff + 60 seconds) on networks where validators have meaningful timestamp control.

5. Reentrancy on token release

The release function does state updates after the token transfer. ERC-777 / ERC-1363 tokens have callbacks that re-enter and double-claim.

ANTIPATTERN
// BAD: external call before state update
function release() external {
    uint256 amount = _vested(msg.sender) - _released[msg.sender];
    token.transfer(msg.sender, amount);  // ← callback re-enters here
    _released[msg.sender] += amount;
}

Fix: update state before transfer (CEI pattern). Use OpenZeppelin’sReentrancyGuard as belt-and-braces.

6. Integer overflow on long durations

Less common since Solidity 0.8 has built-in overflow checks, but still seen in contracts compiled with older versions. A vest duration of type(uint64).max seconds can cause unexpected math elsewhere.

Fix: cap vesting duration to a sensible maximum (e.g. 100 years in seconds) at constructor. Validate inputs.

7. Custom token assumptions

Contract assumes the underlying token is a standard ERC-20. Doesn’t handle:

  • Fee-on-transfer tokens (some stablecoins, deflationary tokens). The contract receives fewer tokens than it expects, breaks the vesting math.
  • Rebasing tokens (AMPL, OHM-style). The held balance changes continuously, the “vested amount” calculation drifts.
  • Tokens that revert on transfer to zero address. Burn calls fail, the contract gets stuck.
  • Tokens with blocklists (USDC, USDT). The contract or beneficiary can be blocklisted, freezing the vest.

Fix: document the token type assumption explicitly. If the underlying token can be paused/blocklisted, add a circuit-breaker the beneficiary controls. If the token has fee-on-transfer, calculate vested amounts on actual received balance, not nominal.

The audit checklist

Before you ship a vesting contract:

  • No revoke function, or revoke is restricted to defined good-leaver clauses with multi-day notice.
  • Beneficiary immutable, or change requires beneficiary’s own signature.
  • Schedule parameters immutable (cliff, duration, TGE%) — set in constructor, marked immutable.
  • CEI pattern on release function. ReentrancyGuard. State updated before transfer.
  • Token type validated: either restricted to vanilla ERC-20 (no rebases, no fees, no blocklist) or explicit handling for the specific token.
  • Block-number or timestamp-with-buffer: not raw block.timestamp at exact boundaries.
  • Public events emitted on every release / configuration change. Public verifiability is the credibility layer.

A vesting contract that owner can drain isn’t a vesting contract. It’s a marketing graphic.

Use audited libraries

The cleanest path: deploy via well-known providers rather than rolling your own. Each has been audited multiple times, exists at scale, has documented governance:

  • OpenZeppelin VestingWallet — minimal, immutable, ETH+ERC-20 compatible. The reference implementation.
  • Sablier — token streaming with rich admin controls (cancellable, transferable). Audited and battle-tested.
  • Hedgey Finance — lockups as NFTs, transferable. Used by major 2023+ launches.
  • Magna — vesting infrastructure for institutional launches. Multi-sig admin, on-chain disclosure, used by several token sale agreements.

THE TOOL'S TAKE

The Token Economics editor models vesting at the schedule level — cliff, duration, TGE%. The smart-contract implementation is a separate concern that the schedule alone doesn’t reveal. When you ship, use a battle-tested library and have it audited. The schedule in the editor is the policy; the contract is the enforcement.

Try the tool

Token Economics is the free designer behind every chart and computation in this article. Replicate any of 300+ real-world tokenomics, edit allocations, see live sell-pressure and health-score updates.

Open the editor

Building something similar?

3UILD is the web3 services team behind Token Economics. We audit tokenomics, deploy contracts, and advise on launches. 30-min review, no pitch.

Talk to 3UILD

Related reading