veNOTE
fact Source contracts/src/governance/veNOTE.sol. veNOTE is a non-transferable voting-weight record in the Curve tradition: you lock NOTE for a fixed term and receive weight that falls linearly to zero at the unlock time. It implements IVotes in timestamp mode and is the IVotes source for both governors and for gauge votes.
The rule
Section titled “The rule”endis the unlock time you choose, floored to a week boundary (Thursday 00:00 UTC). A lock ending in 4 years from today has a weight of about 1 veNOTE per NOTE; a 1-year lock starts at about 0.25.- Weight decays every second. Nothing decays your principal: the NOTE amount stays fixed and is returned in full by
withdraw()once the lock expires. - One lock per address. There is no delegation:
delegates(a)returnsa, anddelegateordelegateBySigrevertDelegationNotSupported. - Weight is exact:
balanceOfAtandtotalSupplyAtuse a binary search on checkpointed points, so a governor snapshot at any past timestamp reads the true value at that instant.
Decay curve
Section titled “Decay curve”amount × (end − now) / MAXTIME at creation to zero at its unlock time. Longer locks start higher and fall more slowly in relative terms.Numeric example
Section titled “Numeric example”illustrative Same locks as the worked weekly example.
| Alice | Bob | |
|---|---|---|
| Locked | 100,000 NOTE | 60,000 NOTE |
| Term | 4 years (max) | 2 years |
Slope amount / MAXTIME | 100,000e18 / 126,144,000 ≈ 7.93e11 per second | 60,000e18 / 126,144,000 ≈ 4.76e11 per second |
| Weight at lock | 100,000 × 4/4 = 100,000 ve | 60,000 × 2/4 = 30,000 ve |
| Weight after 1 week | 100,000 − 7.93e11 × 604,800 / 1e18 ≈ 99,520 ve | 30,000 − 4.76e11 × 604,800 / 1e18 ≈ 29,712 ve |
| Weight after 1 year | 75,000 ve | 15,000 ve |
| Weight at unlock | 0 | 0 |
| Share of total (130,000 at lock) | 76.9% | 23.1% |
Bob’s lock loses 0.96% of its weight in the first week; Alice’s loses 0.48%. The shorter lock decays faster in relative terms, so relative weights drift toward longer lockers between votes unless Bob extends.
Functions
Section titled “Functions”fact
| Function | Who | Reverts |
|---|---|---|
createLock(amount, unlockTime) | anyone, whenNotPaused | ZeroAmount, LockExists, UnlockTimeInPast, UnlockTimeTooFar (more than 4 years) |
increaseAmount(amount) | the locker | NoLock, LockExpired |
depositFor(account, amount) | anyone | adds NOTE to another address’s lock; NoLock, LockExpired |
increaseUnlockTime(unlockTime) | the locker | UnlockTimeNotIncreased, UnlockTimeTooFar, LockExpired |
lockFromStaked(shares, unlockTime) | anyone | pulls sNOTE shares and credits their convertToAssets value; unlockTime must be non-zero for a new lock and 0 for a top-up (UnlockTimeMustBeZero) |
withdraw() | the locker, never pausable | NoLock, LockNotExpired; returns NOTE and any escrowed sNOTE shares. Succeeds even when the global history is more than 255 weeks behind (see below) |
checkpoint() | anyone | fills the global point history; each call walks at most 255 weeks |
balanceOf, balanceOfAt(account, ts), totalSupply, totalSupplyAt(ts) | view | FutureLookup for ts > now |
getVotes, getPastVotes, getPastTotalSupply, clock, CLOCK_MODE | view (IVotes) | getPast* need timepoint < clock() |
locked, lockedEnd, getLastUserSlope, userPointHistory, pointHistory, userPointEpoch, epoch | view |
Every mutating call re-checkpoints the user and the global supply, so totalSupply is never stale by more than the caller’s own change. The global walk covers at most 255 weeks per call; if the contract has been idle for longer than that, a lock, top-up or unlock-time extension reverts CheckpointRequired(reachedTs) until checkpoint() has been called enough times to bring the history up to the current block, so a non-zero user change is never merged into a stale global point.
withdraw() of an expired lock is the exception: its account delta is zero (the lock’s slope was already removed through slopeChanges[end] when the lock expired), so merging it into a partially walked global point changes nothing. The call persists the partial walk exactly as checkpoint() would and returns the funds; a later checkpoint() finishes the history and the global point sequence is identical whichever order the two calls run in. Withdrawal is therefore available at any time, as the pause rules promise.
fact Recovery cost after a long idle period. The scenario needs about five years with no lock, deposit, withdrawal or checkpoint on the contract. Measured in the test suite: after a 52 week lock and a 300 week idle period, one permissionless checkpoint() call walks 255 weeks for about 8.1M gas, and the next user action walks the remaining 45 weeks. Each further 255 idle weeks cost one more such call. Funds are delayed by at most one transaction, never lost, and the app decodes CheckpointRequired by name.
Locking from sNOTE
Section titled “Locking from sNOTE”lockFromStaked does not redeem the vault shares (sNOTE withdrawals need requestWithdraw plus a cooldown). It escrows the shares inside veNOTE and credits the lock with their asset value at that moment. Voting weight is fixed at lock time and does not track later vault yield, which is the conservative choice; the shares, including any yield accrued while escrowed, come back with withdraw().
What veNOTE is used for
Section titled “What veNOTE is used for”| Consumer | Reads | Note |
|---|---|---|
| NoteGovernor | getPastVotes(voter, snapshot), getPastTotalSupply | Snapshot is proposal creation plus votingDelay (1 day). Proposal threshold is 0.5% of supply at clock() − 1. |
| UpgradeGovernor | same | Quorum 8% of supply at snapshot. |
| GaugeController | getLastUserSlope, lockedEnd | Allocates your slope across gauges; the lock must extend past the next week boundary (LockExpiresTooSoon). |
Limits and non-features
Section titled “Limits and non-features”- No transfer, no wrapping, no delegation. Weight belongs to the address that locked.
- No early exit.
withdraw()revertsLockNotExpireduntilend. - Not a boost. sNOTE staking rewards are not multiplied by veNOTE in v1; bribe markets and lock boost are listed as future work in the contract documentation.
- Pausable creation only. The guardian can pause
createLock,increaseAmount,depositFor,increaseUnlockTimeandlockFromStaked;withdrawalways works.