Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TokenVesting

A non-custodial, multi-grant token vesting vault. A single deployment can hold many independent grants, each with its own beneficiary, ERC-20 token, cliff + linear release schedule, and optional revocability. This is not OpenZeppelin's single-beneficiary VestingWallet: one contract manages an unbounded set of unrelated grants that never cross-fund each other.

Vesting curve

Each grant vests on the standard OpenZeppelin linear curve, gated by a cliff:

vested(t) = 0                                for t < cliff
            total * (t - start) / duration   for cliff <= t < start + duration
            total                            for t >= start + duration
  • start anchors the linear math; duration is measured from start.
  • cliff must lie within [start, start + duration]. Nothing is releasable before it, but at the cliff the beneficiary can immediately claim everything that has linearly accrued between start and cliff (a standard "cliff catch-up").
  • At or after start + duration the grant is fully vested and the beneficiary can withdraw exactly total.

release(grantId) transfers vested(now) - released to the beneficiary. Anyone may call it; the funds always go to the grant's beneficiary regardless of who pays the gas.

Revocable grants and conservation

A grant created with revocable = true may be revoked by its grantor (the address that funded it), exactly once. On revoke:

  1. The vested-so-far amount (vested(now) - released) is paid to the beneficiary.
  2. The unvested remainder (total - vested(now)) is returned to the grantor.
  3. Vesting is frozen at the revoke timestamp; the grant can never vest further.

These two transfers sum to the grant's entire remaining balance in the vault (total - released): nothing is minted, nothing is stranded. After revoke, releasable is 0 and any later release call is a no-op. Non-revocable grants can never be revoked; a second revoke reverts.

FoT-safe funding

createGrant pulls total tokens from the funder with SafeERC20 and records the actually received balance delta as the grant total. For a fee-on-transfer token the recorded total therefore equals what the vault holds, so a grant can never be under-funded relative to its own accounting and the solvency invariant holds.

Trust model (honest)

  • The contract is non-custodial except for the single defined path: the grantor of a revocable grant can revoke it, which claws back only the still-unvested portion. The already-vested portion always belongs to the beneficiary and is paid out on revoke.
  • Non-revocable grants have no path for anyone to divert funds from the beneficiary.
  • There is no owner, admin, pause, or upgrade path. ReentrancyGuard protects createGrant, release, and revoke.
  • Choosing a well-behaved ERC-20 is the deployer's responsibility. FoT tokens are handled safely for funding; the beneficiary of an FoT grant still pays that token's transfer fee on withdrawal, as with any transfer of such a token.

Invariants (enforced by the test suite)

  • released <= vestedAmount(now) <= total for every grant, at all times.
  • Solvency: the vault's balance of a token is at least the sum of total - released over all live (unrevoked) grants of that token.
  • Revoke conservation: paidToBeneficiary + returnedToGrantor == total - released.
  • vestedAmount is non-decreasing in time up to revoke, then frozen.

A stateful invariant suite drives ~12,800 random createGrant / warp / release / revoke calls and asserts solvency and ordering after every call.

Layout

src/TokenVesting.sol              the vault
test/TokenVesting.t.sol           unit tests (cliff, linear, revoke, FoT, reentrancy)
test/TokenVesting.invariant.t.sol stateful invariants
test/handlers/VestingHandler.sol  invariant handler
test/mocks/                       plain, fee-on-transfer, and reentrant ERC-20 mocks
script/Deploy.s.sol               deployment script

Build & test

forge build --sizes
forge test
forge snapshot

Toolchain: Solidity 0.8.26, via_ir, EVM cancun, OpenZeppelin Contracts v5.0.2.

About

Multi-grant token vesting: cliff + linear release, revocable grants with conservation, fee-on-transfer-safe funding

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages