Skip to main content

Voting Escrow (veCRV)

Participating in Curve DAO governance requires that an account have a balance of vote-escrowed CRV (veCRV). veCRV is a non-standard ERC-20 implementation, used within the Aragon DAO to determine each account's voting power.

VotingEscrow.vy

The source code for the VotingEscrow.vy contract can be found on GitHub. The contract is written using Vyper version 0.2.4.

The contract is deployed on Ethereum at 0x5f3b5DfEb7B28CDbD7FAba78963EE202a494e2A2.

{ }Contract ABI
[{"name":"CommitOwnership","inputs":[{"type":"address","name":"admin","indexed":false}],"anonymous":false,"type":"event"},{"name":"ApplyOwnership","inputs":[{"type":"address","name":"admin","indexed":false}],"anonymous":false,"type":"event"},{"name":"Deposit","inputs":[{"type":"address","name":"provider","indexed":true},{"type":"uint256","name":"value","indexed":false},{"type":"uint256","name":"locktime","indexed":true},{"type":"int128","name":"type","indexed":false},{"type":"uint256","name":"ts","indexed":false}],"anonymous":false,"type":"event"},{"name":"Withdraw","inputs":[{"type":"address","name":"provider","indexed":true},{"type":"uint256","name":"value","indexed":false},{"type":"uint256","name":"ts","indexed":false}],"anonymous":false,"type":"event"},{"name":"Supply","inputs":[{"type":"uint256","name":"prevSupply","indexed":false},{"type":"uint256","name":"supply","indexed":false}],"anonymous":false,"type":"event"},{"outputs":[],"inputs":[{"type":"address","name":"token_addr"},{"type":"string","name":"_name"},{"type":"string","name":"_symbol"},{"type":"string","name":"_version"}],"stateMutability":"nonpayable","type":"constructor"},{"name":"commit_transfer_ownership","outputs":[],"inputs":[{"type":"address","name":"addr"}],"stateMutability":"nonpayable","type":"function"},{"name":"apply_transfer_ownership","outputs":[],"inputs":[],"stateMutability":"nonpayable","type":"function"},{"name":"commit_smart_wallet_checker","outputs":[],"inputs":[{"type":"address","name":"addr"}],"stateMutability":"nonpayable","type":"function"},{"name":"apply_smart_wallet_checker","outputs":[],"inputs":[],"stateMutability":"nonpayable","type":"function"},{"name":"create_lock","outputs":[],"inputs":[{"type":"uint256","name":"_value"},{"type":"uint256","name":"_unlock_time"}],"stateMutability":"nonpayable","type":"function"},{"name":"increase_amount","outputs":[],"inputs":[{"type":"uint256","name":"_value"}],"stateMutability":"nonpayable","type":"function"},{"name":"increase_unlock_time","outputs":[],"inputs":[{"type":"uint256","name":"_unlock_time"}],"stateMutability":"nonpayable","type":"function"},{"name":"deposit_for","outputs":[],"inputs":[{"type":"address","name":"_addr"},{"type":"uint256","name":"_value"}],"stateMutability":"nonpayable","type":"function"},{"name":"withdraw","outputs":[],"inputs":[],"stateMutability":"nonpayable","type":"function"},{"name":"checkpoint","outputs":[],"inputs":[],"stateMutability":"nonpayable","type":"function"},{"name":"changeController","outputs":[],"inputs":[{"type":"address","name":"_newController"}],"stateMutability":"nonpayable","type":"function"},{"name":"get_last_user_slope","outputs":[{"type":"int128","name":""}],"inputs":[{"type":"address","name":"addr"}],"stateMutability":"view","type":"function"},{"name":"user_point_history__ts","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_addr"},{"type":"uint256","name":"_idx"}],"stateMutability":"view","type":"function"},{"name":"locked__end","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_addr"}],"stateMutability":"view","type":"function"},{"name":"balanceOf","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"addr"}],"stateMutability":"view","type":"function"},{"name":"balanceOf","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"addr"},{"type":"uint256","name":"_t"}],"stateMutability":"view","type":"function"},{"name":"balanceOfAt","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"addr"},{"type":"uint256","name":"_block"}],"stateMutability":"view","type":"function"},{"name":"totalSupply","outputs":[{"type":"uint256","name":""}],"inputs":[],"stateMutability":"view","type":"function"},{"name":"totalSupply","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256","name":"t"}],"stateMutability":"view","type":"function"},{"name":"totalSupplyAt","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256","name":"_block"}],"stateMutability":"view","type":"function"},{"name":"token","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function"},{"name":"supply","outputs":[{"type":"uint256","name":""}],"inputs":[],"stateMutability":"view","type":"function"},{"name":"locked","outputs":[{"type":"int128","name":"amount"},{"type":"uint256","name":"end"}],"inputs":[{"type":"address","name":"arg0"}],"stateMutability":"view","type":"function"},{"name":"epoch","outputs":[{"type":"uint256","name":""}],"inputs":[],"stateMutability":"view","type":"function"},{"name":"point_history","outputs":[{"type":"int128","name":"bias"},{"type":"int128","name":"slope"},{"type":"uint256","name":"ts"},{"type":"uint256","name":"blk"}],"inputs":[{"type":"uint256","name":"arg0"}],"stateMutability":"view","type":"function"},{"name":"user_point_history","outputs":[{"type":"int128","name":"bias"},{"type":"int128","name":"slope"},{"type":"uint256","name":"ts"},{"type":"uint256","name":"blk"}],"inputs":[{"type":"address","name":"arg0"},{"type":"uint256","name":"arg1"}],"stateMutability":"view","type":"function"},{"name":"user_point_epoch","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"arg0"}],"stateMutability":"view","type":"function"},{"name":"slope_changes","outputs":[{"type":"int128","name":""}],"inputs":[{"type":"uint256","name":"arg0"}],"stateMutability":"view","type":"function"},{"name":"controller","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function"},{"name":"transfersEnabled","outputs":[{"type":"bool","name":""}],"inputs":[],"stateMutability":"view","type":"function"},{"name":"name","outputs":[{"type":"string","name":""}],"inputs":[],"stateMutability":"view","type":"function"},{"name":"symbol","outputs":[{"type":"string","name":""}],"inputs":[],"stateMutability":"view","type":"function"},{"name":"version","outputs":[{"type":"string","name":""}],"inputs":[],"stateMutability":"view","type":"function"},{"name":"decimals","outputs":[{"type":"uint256","name":""}],"inputs":[],"stateMutability":"view","type":"function"},{"name":"future_smart_wallet_checker","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function"},{"name":"smart_wallet_checker","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function"},{"name":"admin","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function"},{"name":"future_admin","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function"}]

locktime is denominated in years. The maximum lock duration is four years and the minimum is one week.

CRVveCRVLocktime
114 years
10.753 years
10.52 years
10.251 year
xx * n/4n
warning

When a user locks their CRV tokens for voting, they will receive veCRV based on the lock duration and the amount locked. Locking is not reversible and veCRV tokens are non-transferable. If a user decides to vote-lock their CRV tokens, they will only be able to reclaim the CRV tokens after the lock duration has ended.

Additionally, a user cannot have multiple locks with different expiry dates. However, a lock can be extended, or additional CRV can be added to it at any time.


Implementation Details

User voting power wiw_{i} is linearly decreasing since the moment of lock. So does the total voting power WW. In order to avoid periodic check-ins, every time the user deposits, or withdraws, or changes the locktime, we record user's slope and bias for the linear function wi(t)w_{i}(t) in the public mapping user_point_history. We also change slope and bias for the total voting power W(t)W(t) and record it in point_history. In addition, when a user's lock is scheduled to end, we schedule change of slopes of W(t)W(t) in the future in slope_changes. Every change involves increasing the epoch by 1.

This way we don't have to iterate over all users to figure out how much W(t)W(t) should change by, neither do we require users to check in periodically. However, we limit the end of user locks to times rounded off by whole weeks.

Slopes and biases change both when a user deposits and locks governance tokens, and when the locktime expires. All the possible expiration times are rounded to whole weeks to make the number of reads from blockchain proportional to the number of missed weeks at most, not the number of users (which is potentially large).


Lock Management

create_lock

VotingEscrow.create_lock(_value: uint256, _unlock_time: uint256)

Function to deposit _value CRV into the VotingEscrow and create a new lock until _unlock_time. The unlock time is rounded down to whole weeks.

Emits: Deposit and Supply

InputTypeDescription
_valueuint256Amount of CRV to deposit
_unlock_timeuint256Timestamp of the unlock time
<>Source code
struct LockedBalance:
amount: int128
end: uint256

locked: public(HashMap[address, LockedBalance])

WEEK: constant(uint256) = 7 * 86400 # all future times are rounded by week
MAXTIME: constant(uint256) = 4 * 365 * 86400 # 4 years

@external
@nonreentrant('lock')
def create_lock(_value: uint256, _unlock_time: uint256):
"""
@notice Deposit `_value` tokens for `msg.sender` and lock until `_unlock_time`
@param _value Amount to deposit
@param _unlock_time Epoch time when tokens unlock, rounded down to whole weeks
"""
self.assert_not_contract(msg.sender)
unlock_time: uint256 = (_unlock_time / WEEK) * WEEK # Locktime is rounded down to weeks
_locked: LockedBalance = self.locked[msg.sender]

assert _value > 0 # dev: need non-zero value
assert _locked.amount == 0, "Withdraw old tokens first"
assert unlock_time > block.timestamp, "Can only lock until time in the future"
assert unlock_time <= block.timestamp + MAXTIME, "Voting lock can be 4 years max"

self._deposit_for(msg.sender, _value, unlock_time, _locked, CREATE_LOCK_TYPE)
Example

This example creates a new lock of 100 CRV tokens until a specified unlock timestamp.

>>> VotingEscrow.create_lock(100000000000000000000, 1694003759)

increase_amount

VotingEscrow.increase_amount(_value: uint256)

Function to deposit _value additional CRV tokens to an existing lock without modifying the unlock time.

Emits: Deposit and Supply

InputTypeDescription
_valueuint256Amount of CRV to additionally lock
<>Source code
struct LockedBalance:
amount: int128
end: uint256

locked: public(HashMap[address, LockedBalance])

@external
@nonreentrant('lock')
def increase_amount(_value: uint256):
"""
@notice Deposit `_value` additional tokens for `msg.sender`
without modifying the unlock time
@param _value Amount of tokens to deposit and add to the lock
"""
self.assert_not_contract(msg.sender)
_locked: LockedBalance = self.locked[msg.sender]

assert _value > 0 # dev: need non-zero value
assert _locked.amount > 0, "No existing lock found"
assert _locked.end > block.timestamp, "Cannot add to expired lock. Withdraw"

self._deposit_for(msg.sender, _value, 0, _locked, INCREASE_LOCK_AMOUNT)
Example

This example adds 100 CRV tokens to an existing lock.

>>> VotingEscrow.increase_amount(100000000000000000000)

increase_unlock_time

VotingEscrow.increase_unlock_time(_unlock_time: uint256)

Function to extend the unlock time on an already existing lock until _unlock_time. The unlock time is rounded down to whole weeks.

Emits: Deposit and Supply

InputTypeDescription
_unlock_timeuint256New unlock timestamp
<>Source code
struct LockedBalance:
amount: int128
end: uint256

locked: public(HashMap[address, LockedBalance])

WEEK: constant(uint256) = 7 * 86400 # all future times are rounded by week
MAXTIME: constant(uint256) = 4 * 365 * 86400 # 4 years

@external
@nonreentrant('lock')
def increase_unlock_time(_unlock_time: uint256):
"""
@notice Extend the unlock time for `msg.sender` to `_unlock_time`
@param _unlock_time New epoch time for unlocking
"""
self.assert_not_contract(msg.sender)
_locked: LockedBalance = self.locked[msg.sender]
unlock_time: uint256 = (_unlock_time / WEEK) * WEEK # Locktime is rounded down to weeks

assert _locked.end > block.timestamp, "Lock expired"
assert _locked.amount > 0, "Nothing is locked"
assert unlock_time > _locked.end, "Can only increase lock duration"
assert unlock_time <= block.timestamp + MAXTIME, "Voting lock can be 4 years max"

self._deposit_for(msg.sender, 0, unlock_time, _locked, INCREASE_UNLOCK_TIME)
Example

This example extends the unlock time of an existing lock to a new timestamp.

>>> VotingEscrow.increase_unlock_time(1694003759)

deposit_for

VotingEscrow.deposit_for(_addr: address, _value: uint256)

Function to deposit _value tokens for _addr and add them to an existing lock. Anyone (even a smart contract) can deposit for someone else, but cannot extend their locktime or deposit for a brand new user.

Emits: Deposit and Supply

InputTypeDescription
_addraddressAddress to deposit for
_valueuint256Amount of tokens to lock
<>Source code
struct LockedBalance:
amount: int128
end: uint256

locked: public(HashMap[address, LockedBalance])

@external
@nonreentrant('lock')
def deposit_for(_addr: address, _value: uint256):
"""
@notice Deposit `_value` tokens for `_addr` and add to the lock
@dev Anyone (even a smart contract) can deposit for someone else, but
cannot extend their locktime and deposit for a brand new user
@param _addr User's wallet address
@param _value Amount to add to user's lock
"""
_locked: LockedBalance = self.locked[_addr]

assert _value > 0 # dev: need non-zero value
assert _locked.amount > 0, "No existing lock found"
assert _locked.end > block.timestamp, "Cannot add to expired lock. Withdraw"

self._deposit_for(_addr, _value, 0, self.locked[_addr], DEPOSIT_FOR_TYPE)
Example

This example deposits 100 CRV tokens into an existing lock owned by another address.

>>> VotingEscrow.deposit_for("0x7a16fF8270133F063aAb6C9977183D9e72835428", 100000000000000000000)

withdraw

VotingEscrow.withdraw()

Function to withdraw all deposited CRV tokens once a lock has expired.

Emits: Withdraw and Supply

<>Source code
struct LockedBalance:
amount: int128
end: uint256

locked: public(HashMap[address, LockedBalance])
supply: public(uint256)
token: public(address)

@external
@nonreentrant('lock')
def withdraw():
"""
@notice Withdraw all tokens for `msg.sender`
@dev Only possible if the lock has expired
"""
_locked: LockedBalance = self.locked[msg.sender]
assert block.timestamp >= _locked.end, "The lock didn't expire"
value: uint256 = convert(_locked.amount, uint256)

old_locked: LockedBalance = _locked
_locked.end = 0
_locked.amount = 0
self.locked[msg.sender] = _locked
supply_before: uint256 = self.supply
self.supply = supply_before - value

# old_locked can have either expired <= timestamp or zero end
# _locked has only 0 end
# Both can have >= 0 amount
self._checkpoint(msg.sender, old_locked, _locked)

assert ERC20(self.token).transfer(msg.sender, value)

log Withdraw(msg.sender, value, block.timestamp)
log Supply(supply_before, supply_before - value)
Example

This example withdraws all CRV tokens after the lock has expired.

>>> VotingEscrow.withdraw()

checkpoint

VotingEscrow.checkpoint()

Function to record global data to a checkpoint. This updates the global point_history and epoch. Can be called by anyone.

<>Source code
ZERO_ADDRESS: constant(address) = 0x0000000000000000000000000000000000000000

struct LockedBalance:
amount: int128
end: uint256

@external
def checkpoint():
"""
@notice Record global data to checkpoint
"""
self._checkpoint(ZERO_ADDRESS, empty(LockedBalance), empty(LockedBalance))
Example

This example records a global data checkpoint, updating the point_history and epoch.

>>> VotingEscrow.checkpoint()

Voting Power & Balances

balanceOf

VotingEscrow.balanceOf(addr: address, _t: uint256 = block.timestamp) -> uint256: view

Getter for the current veCRV balance (= voting power) of addr at timestamp _t. Defaults to block.timestamp.

note

These are not real ERC-20 balances. They measure voting weights that decay linearly over time.

Returns: voting power (uint256).

InputTypeDescription
addraddressUser wallet address
_tuint256Timestamp; defaults to block.timestamp
<>Source code
struct Point:
bias: int128
slope: int128 # - dweight / dt
ts: uint256
blk: uint256 # block

user_point_epoch: public(HashMap[address, uint256])
user_point_history: public(HashMap[address, Point[1000000000]]) # user -> Point[user_epoch]

@external
@view
def balanceOf(addr: address, _t: uint256 = block.timestamp) -> uint256:
"""
@notice Get the current voting power for `msg.sender`
@dev Adheres to the ERC20 `balanceOf` interface for Aragon compatibility
@param addr User wallet address
@param _t Epoch time to return voting power at
@return User voting power
"""
_epoch: uint256 = self.user_point_epoch[addr]
if _epoch == 0:
return 0
else:
last_point: Point = self.user_point_history[addr][_epoch]
last_point.bias -= last_point.slope * convert(_t - last_point.ts, int128)
if last_point.bias < 0:
last_point.bias = 0
return convert(last_point.bias, uint256)
Example

This example returns the current veCRV balance (voting power) of an address. Enter an address and click Query to fetch the value live from the blockchain.

balanceOfAt

VotingEscrow.balanceOfAt(addr: address, _block: uint256) -> uint256: view

Getter for the veCRV balance (= voting power) of addr at block height _block.

Returns: voting power (uint256) at a specific block.

InputTypeDescription
addraddressUser wallet address
_blockuint256Block height
<>Source code
struct Point:
bias: int128
slope: int128 # - dweight / dt
ts: uint256
blk: uint256 # block

epoch: public(uint256)
point_history: public(Point[100000000000000000000000000000]) # epoch -> unsigned point
user_point_epoch: public(HashMap[address, uint256])
user_point_history: public(HashMap[address, Point[1000000000]]) # user -> Point[user_epoch]

@external
@view
def balanceOfAt(addr: address, _block: uint256) -> uint256:
"""
@notice Measure voting power of `addr` at block height `_block`
@dev Adheres to MiniMe `balanceOfAt` interface: https://github.com/Giveth/minime
@param addr User's wallet address
@param _block Block to calculate the voting power at
@return Voting power
"""
assert _block <= block.number

# Binary search
_min: uint256 = 0
_max: uint256 = self.user_point_epoch[addr]
for i in range(128): # Will be always enough for 128-bit numbers
if _min >= _max:
break
_mid: uint256 = (_min + _max + 1) / 2
if self.user_point_history[addr][_mid].blk <= _block:
_min = _mid
else:
_max = _mid - 1

upoint: Point = self.user_point_history[addr][_min]

max_epoch: uint256 = self.epoch
_epoch: uint256 = self.find_block_epoch(_block, max_epoch)
point_0: Point = self.point_history[_epoch]
d_block: uint256 = 0
d_t: uint256 = 0
if _epoch < max_epoch:
point_1: Point = self.point_history[_epoch + 1]
d_block = point_1.blk - point_0.blk
d_t = point_1.ts - point_0.ts
else:
d_block = block.number - point_0.blk
d_t = block.timestamp - point_0.ts
block_time: uint256 = point_0.ts
if d_block != 0:
block_time += d_t * (_block - point_0.blk) / d_block

upoint.bias -= upoint.slope * convert(block_time - upoint.ts, int128)
if upoint.bias >= 0:
return convert(upoint.bias, uint256)
else:
return 0
Example

This example returns the veCRV balance (voting power) of an address at a specific block height.

>>> VotingEscrow.balanceOfAt("0x7a16fF8270133F063aAb6C9977183D9e72835428", 18483472)
27109584974408936745457887

totalSupply

VotingEscrow.totalSupply(t: uint256 = block.timestamp) -> uint256: view

Getter for the current total supply of veCRV (= total voting power) at timestamp t. Defaults to block.timestamp.

Returns: total voting power (uint256).

InputTypeDescription
tuint256Timestamp; defaults to block.timestamp
<>Source code
struct Point:
bias: int128
slope: int128 # - dweight / dt
ts: uint256
blk: uint256 # block

epoch: public(uint256)
point_history: public(Point[100000000000000000000000000000]) # epoch -> unsigned point

@external
@view
def totalSupply(t: uint256 = block.timestamp) -> uint256:
"""
@notice Calculate total voting power
@dev Adheres to the ERC20 `totalSupply` interface for Aragon compatibility
@return Total voting power
"""
_epoch: uint256 = self.epoch
last_point: Point = self.point_history[_epoch]
return self.supply_at(last_point, t)
Example

This example returns the current total veCRV voting power. The value is fetched live from the blockchain.

totalSupplyAt

VotingEscrow.totalSupplyAt(_block: uint256) -> uint256: view

Getter for the total supply of veCRV (= total voting power) at block height _block.

Returns: total voting power (uint256) at a specific block.

InputTypeDescription
_blockuint256Block height
<>Source code
struct Point:
bias: int128
slope: int128 # - dweight / dt
ts: uint256
blk: uint256 # block

epoch: public(uint256)
point_history: public(Point[100000000000000000000000000000]) # epoch -> unsigned point

@external
@view
def totalSupplyAt(_block: uint256) -> uint256:
"""
@notice Calculate total voting power at some point in the past
@param _block Block to calculate the total voting power at
@return Total voting power at `_block`
"""
assert _block <= block.number
_epoch: uint256 = self.epoch
target_epoch: uint256 = self.find_block_epoch(_block, _epoch)

point: Point = self.point_history[target_epoch]
dt: uint256 = 0
if target_epoch < _epoch:
point_next: Point = self.point_history[target_epoch + 1]
if point.blk != point_next.blk:
dt = (_block - point.blk) * (point_next.ts - point.ts) / (point_next.blk - point.blk)
else:
if point.blk != block.number:
dt = (_block - point.blk) * (block.timestamp - point.ts) / (block.number - point.blk)
# Now dt contains info on how far are we beyond point

return self.supply_at(point, point.ts + dt)
Example

This example returns the total veCRV voting power at a specific block height.

>>> VotingEscrow.totalSupplyAt(18483472)
652219245965489504779222536

supply

VotingEscrow.supply() -> uint256: view

Getter for the total amount of CRV tokens locked in the contract.

Returns: locked CRV amount (uint256).

<>Source code
supply: public(uint256)
Example

This example returns the total amount of CRV tokens locked in the contract. The value is fetched live from the blockchain.

locked

VotingEscrow.locked(arg0: address) -> amount: int128, end: uint256: view

Getter for the locked balance of address arg0. Returns the LockedBalance struct containing the locked amount and the unlock timestamp.

Returns: amount (int128) and unlock time (uint256).

InputTypeDescription
arg0addressUser address
<>Source code
struct LockedBalance:
amount: int128
end: uint256

locked: public(HashMap[address, LockedBalance])
Example

This example returns the locked CRV amount and unlock timestamp for a given address.

>>> VotingEscrow.locked("0x7a16fF8270133F063aAb6C9977183D9e72835428")
27191329036660104386777000, 1808956800

locked__end

VotingEscrow.locked__end(_addr: address) -> uint256: view

Getter for the timestamp when _addr's lock finishes.

Returns: unlock timestamp (uint256).

InputTypeDescription
_addraddressUser address
<>Source code
struct LockedBalance:
amount: int128
end: uint256

locked: public(HashMap[address, LockedBalance])

@external
@view
def locked__end(_addr: address) -> uint256:
"""
@notice Get timestamp when `_addr`'s lock finishes
@param _addr User wallet
@return Epoch time of the lock end
"""
return self.locked[_addr].end
Example

This example returns the unlock timestamp for a given address.

>>> VotingEscrow.locked__end("0x7a16fF8270133F063aAb6C9977183D9e72835428")
1808956800

get_last_user_slope

VotingEscrow.get_last_user_slope(addr: address) -> int128: view

Getter for the most recently recorded rate of voting power decrease for addr.

Returns: slope value (int128).

InputTypeDescription
addraddressUser address
<>Source code
struct Point:
bias: int128
slope: int128 # - dweight / dt
ts: uint256
blk: uint256 # block

user_point_epoch: public(HashMap[address, uint256])
user_point_history: public(HashMap[address, Point[1000000000]]) # user -> Point[user_epoch]

@external
@view
def get_last_user_slope(addr: address) -> int128:
"""
@notice Get the most recently recorded rate of voting power decrease for `addr`
@param addr Address of the user wallet
@return Value of the slope
"""
uepoch: uint256 = self.user_point_epoch[addr]
return self.user_point_history[addr][uepoch].slope
Example

This example returns the most recent rate of voting power decrease for a given address.

>>> VotingEscrow.get_last_user_slope("0x7a16fF8270133F063aAb6C9977183D9e72835428")
215557846878647453

Checkpoints & History

epoch

VotingEscrow.epoch() -> uint256: view

Getter for the current global epoch. The epoch is incremented by 1 every time a checkpoint is recorded.

Returns: current epoch (uint256).

<>Source code
epoch: public(uint256)
Example

This example returns the current global epoch. The value is fetched live from the blockchain.

point_history

VotingEscrow.point_history(arg0: uint256) -> bias: int128, slope: int128, ts: uint256, blk: uint256: view

Getter for the global point history at epoch arg0. Each point records the aggregate bias, slope, timestamp, and block number.

Returns: bias (int128), slope (int128), ts (uint256) and blk (uint256).

InputTypeDescription
arg0uint256Epoch number
<>Source code
struct Point:
bias: int128
slope: int128 # - dweight / dt
ts: uint256
blk: uint256 # block

point_history: public(Point[100000000000000000000000000000]) # epoch -> unsigned point
Example

This example returns the global point history at epoch 3 (bias, slope, timestamp, block number).

>>> VotingEscrow.point_history(3)
127357905207521710167, 4570173769659, 1597370987, 10655341

user_point_epoch

VotingEscrow.user_point_epoch(arg0: address) -> uint256: view

Getter for the current checkpoint epoch for a specific user. This is incremented each time the user's lock state changes (create, increase amount, increase time, withdraw).

Returns: user epoch (uint256).

InputTypeDescription
arg0addressUser address
<>Source code
user_point_epoch: public(HashMap[address, uint256])
Example

This example returns the current checkpoint epoch for a specific user.

>>> VotingEscrow.user_point_epoch("0x7a16fF8270133F063aAb6C9977183D9e72835428")
5

user_point_history

VotingEscrow.user_point_history(arg0: address, arg1: uint256) -> bias: int128, slope: int128, ts: uint256, blk: uint256: view

Getter for a user's point history at a specific user epoch. Each point records the user's bias, slope, timestamp, and block number at that checkpoint.

Returns: bias (int128), slope (int128), ts (uint256) and blk (uint256).

InputTypeDescription
arg0addressUser address
arg1uint256User epoch number
<>Source code
user_point_history: public(HashMap[address, Point[1000000000]])  # user -> Point[user_epoch]
Example

This example returns the point history for a user at their first checkpoint (bias, slope, timestamp, block number).

>>> VotingEscrow.user_point_history("0x7a16fF8270133F063aAb6C9977183D9e72835428", 1)
127357905207521710167, 4570173769659, 1597565455, 10655341

user_point_history__ts

VotingEscrow.user_point_history__ts(_addr: address, _idx: uint256) -> uint256: view

Convenience getter for the timestamp of checkpoint _idx for _addr.

Returns: timestamp (uint256).

InputTypeDescription
_addraddressUser address
_idxuint256User epoch number
<>Source code
struct Point:
bias: int128
slope: int128 # - dweight / dt
ts: uint256
blk: uint256 # block

user_point_history: public(HashMap[address, Point[1000000000]]) # user -> Point[user_epoch]

@external
@view
def user_point_history__ts(_addr: address, _idx: uint256) -> uint256:
"""
@notice Get the timestamp for checkpoint `_idx` for `_addr`
@param _addr User wallet address
@param _idx User epoch number
@return Epoch time of the checkpoint
"""
return self.user_point_history[_addr][_idx].ts
Example

This example returns the timestamp of the first checkpoint for a given address.

>>> VotingEscrow.user_point_history__ts("0x7a16fF8270133F063aAb6C9977183D9e72835428", 1)
1597565455

slope_changes

VotingEscrow.slope_changes(arg0: uint256) -> int128: view

Getter for scheduled slope changes at a future timestamp. When a lock expires, the global slope decreases. These changes are pre-scheduled so the contract doesn't need to iterate over all users.

Returns: signed slope change (int128).

InputTypeDescription
arg0uint256Timestamp (rounded to week)
<>Source code
slope_changes: public(HashMap[uint256, int128])  # time -> signed slope change
Example

This example returns the scheduled slope change at a specific future timestamp.

>>> VotingEscrow.slope_changes(1808956800)
-215557846878647453

SmartWalletChecker

The SmartWalletChecker is an external contract referenced by VotingEscrow to determine whether smart contracts are allowed to lock CRV. The internal assert_not_contract function checks callers against this contract whenever create_lock, increase_amount, or increase_unlock_time is called.

SmartWalletChecker Upgrade

The SmartWalletChecker was originally used to restrict which smart contracts could lock CRV — contracts needed to be explicitly whitelisted via an approveWallet function, and access could be revoked via revokeWallet. This was in place to prevent tokenizing the escrow.

The checker has since been upgraded to a simple Vyper contract that returns True for any input, effectively removing all smart contract restrictions. Any smart contract can now lock CRV without needing prior approval.

# pragma version 0.4.1
# @title Smart Wallet Whitelist
# @notice Dummy contract bypassing smart wallet check for veCRV.
# Returns 'true' for any input.

@view
@external
def check(_wallet: address) -> bool:
return True

smart_wallet_checker

VotingEscrow.smart_wallet_checker() -> address: view

Getter for the current SmartWalletChecker contract address.

Returns: SmartWalletChecker contract (address).

<>Source code
smart_wallet_checker: public(address)
Example

This example returns the current SmartWalletChecker contract address.

>>> VotingEscrow.smart_wallet_checker()
'0xca719728Ef172d0961768581fdF35CB116e0B7a4'

future_smart_wallet_checker

VotingEscrow.future_smart_wallet_checker() -> address: view

Getter for the future SmartWalletChecker contract address. Set via commit_smart_wallet_checker and applied via apply_smart_wallet_checker.

Returns: future SmartWalletChecker contract (address).

<>Source code
future_smart_wallet_checker: public(address)
Example

This example returns the future SmartWalletChecker contract address (zero address means no pending change).

>>> VotingEscrow.future_smart_wallet_checker()
'0x0000000000000000000000000000000000000000'

commit_smart_wallet_checker

VotingEscrow.commit_smart_wallet_checker(addr: address)
Guarded Method

This function is only callable by the admin of the contract.

Function to commit a new SmartWalletChecker contract address. Changes need to be applied via apply_smart_wallet_checker.

InputTypeDescription
addraddressNew SmartWalletChecker contract
<>Source code
future_smart_wallet_checker: public(address)

@external
def commit_smart_wallet_checker(addr: address):
"""
@notice Set an external contract to check for approved smart contract wallets
@param addr Address of Smart contract checker
"""
assert msg.sender == self.admin
self.future_smart_wallet_checker = addr
Example

This example commits a new SmartWalletChecker contract address.

>>> VotingEscrow.commit_smart_wallet_checker("0x1234567890abcdef1234567890abcdef12345678")

apply_smart_wallet_checker

VotingEscrow.apply_smart_wallet_checker()
Guarded Method

This function is only callable by the admin of the contract.

Function to apply the previously committed SmartWalletChecker address.

<>Source code
smart_wallet_checker: public(address)
future_smart_wallet_checker: public(address)

@external
def apply_smart_wallet_checker():
"""
@notice Apply setting external contract to check approved smart contract wallets
"""
assert msg.sender == self.admin
self.smart_wallet_checker = self.future_smart_wallet_checker
Example

This example applies the previously committed SmartWalletChecker address.

>>> VotingEscrow.apply_smart_wallet_checker()

Contract Info

token

VotingEscrow.token() -> address: view

Getter for the CRV token address that is locked in the contract.

Returns: CRV token address (address).

<>Source code
token: public(address)

@external
def __init__(token_addr: address, _name: String[64], _symbol: String[32], _version: String[32]):
...
self.token = token_addr
...
Example

This example returns the CRV token address locked in the contract.

>>> VotingEscrow.token()
'0xD533a949740bb3306d119CC777fa900bA034cd52'

name

VotingEscrow.name() -> String[64]: view

Getter for the name of the token.

Returns: name (String[64]).

<>Source code
name: public(String[64])
Example

This example returns the name of the token.

>>> VotingEscrow.name()
'Vote-escrowed CRV'

symbol

VotingEscrow.symbol() -> String[32]: view

Getter for the symbol of the token.

Returns: symbol (String[32]).

<>Source code
symbol: public(String[32])
Example

This example returns the symbol of the token.

>>> VotingEscrow.symbol()
'veCRV'

version

VotingEscrow.version() -> String[32]: view

Getter for the version of the contract.

Returns: version (String[32]).

<>Source code
version: public(String[32])
Example

This example returns the version of the contract.

>>> VotingEscrow.version()
'veCRV_1.0.0'

decimals

VotingEscrow.decimals() -> uint256: view

Getter for the decimals of the token.

Returns: decimals (uint256).

<>Source code
decimals: public(uint256)
Example

This example returns the number of decimals of the token.

>>> VotingEscrow.decimals()
18

controller

VotingEscrow.controller() -> address: view

Getter for the Aragon controller of the contract.

Returns: controller address (address).

<>Source code
controller: public(address)
Example

This example returns the Aragon controller address.

>>> VotingEscrow.controller()
'0xc4AD0Ef33A0A4ddA3461c479ccb6c36d1e4B7Be4'

changeController

VotingEscrow.changeController(_newController: address)
Guarded Method

This function is only callable by the controller of the contract.

Dummy method required for Aragon compatibility.

InputTypeDescription
_newControlleraddressNew controller address
<>Source code
controller: public(address)

@external
def changeController(_newController: address):
"""
@dev Dummy method required for Aragon compatibility
"""
assert msg.sender == self.controller
self.controller = _newController
Example

This example changes the Aragon controller address.

>>> VotingEscrow.changeController("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045")

transfersEnabled

VotingEscrow.transfersEnabled() -> bool: view

View method required for Aragon compatibility. Always returns True.

Returns: transfers enabled (bool).

<>Source code
transfersEnabled: public(bool)

@external
def __init__(token_addr: address, _name: String[64], _symbol: String[32], _version: String[32]):
...
self.transfersEnabled = True
...
Example

This example returns whether transfers are enabled (always True for Aragon compatibility).

>>> VotingEscrow.transfersEnabled()
True

Admin Controls

The CurveOwnershipAgent (0x40907540d8a6C65c637785e8f8B742ae6b0b9968) is the current admin of the VotingEscrow. Any changes to admin-controlled parameters require a successfully passed DAO vote.

admin

VotingEscrow.admin() -> address: view

Getter for the current admin of the contract.

Returns: admin (address).

<>Source code
admin: public(address)  # Can and will be a smart contract
Example

This example returns the current admin of the contract (CurveOwnershipAgent).

>>> VotingEscrow.admin()
'0x40907540d8a6C65c637785e8f8B742ae6b0b9968'

future_admin

VotingEscrow.future_admin() -> address: view

Getter for the future admin of the contract. Set via commit_transfer_ownership and applied via apply_transfer_ownership.

Returns: future admin (address).

<>Source code
future_admin: public(address)
Example

This example returns the future admin address (zero address means no pending transfer).

>>> VotingEscrow.future_admin()
'0x0000000000000000000000000000000000000000'

commit_transfer_ownership

VotingEscrow.commit_transfer_ownership(addr: address)
Guarded Method

This function is only callable by the admin of the contract.

Function to commit the transfer of ownership to addr. Changes need to be applied via apply_transfer_ownership.

Emits: CommitOwnership

InputTypeDescription
addraddressNew admin address
<>Source code
event CommitOwnership:
admin: address

future_admin: public(address)

@external
def commit_transfer_ownership(addr: address):
"""
@notice Transfer ownership of VotingEscrow contract to `addr`
@param addr Address to have ownership transferred to
"""
assert msg.sender == self.admin # dev: admin only
self.future_admin = addr
log CommitOwnership(addr)
Example

This example commits a new admin address for the ownership transfer.

>>> VotingEscrow.commit_transfer_ownership("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045")

apply_transfer_ownership

VotingEscrow.apply_transfer_ownership()
Guarded Method

This function is only callable by the admin of the contract.

Function to apply the previously committed ownership transfer.

Emits: ApplyOwnership

<>Source code
event ApplyOwnership:
admin: address

admin: public(address) # Can and will be a smart contract
future_admin: public(address)

@external
def apply_transfer_ownership():
"""
@notice Apply ownership transfer
"""
assert msg.sender == self.admin # dev: admin only
_admin: address = self.future_admin
assert _admin != ZERO_ADDRESS # dev: admin not set
self.admin = _admin
log ApplyOwnership(_admin)
Example

This example applies the previously committed ownership transfer.

>>> VotingEscrow.apply_transfer_ownership()