Skip to main content

L2 VotingEscrow Oracle

The L2VotingEscrowOracle contract is used to fetch information from the VotingEscrow from Ethereum. This data can then be used to calculate boost rates for providing liquidity.

L2VotingEscrowOracle.vy

The source code for the L2VotingEscrowOracle.vy contract is available on GitHub. The contract is written in Vyper version 0.4.0.

The VotingEscrow on Ethereum is deployed at 0x5f3b5DfEb7B28CDbD7FAba78963EE202a494e2A2.

The L2VotingEscrowOracle contract is deployed at the following addresses and is version 1.0.0:

{ }Contract ABI
[{"anonymous":false,"inputs":[{"indexed":false,"name":"_epoch","type":"uint256"}],"name":"UpdateTotal","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_user","type":"address"},{"indexed":false,"name":"_user_point_epoch","type":"uint256"}],"name":"UpdateBalance","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_from","type":"address"},{"indexed":false,"name":"_to","type":"address"}],"name":"Delegate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"role","type":"bytes32"},{"indexed":true,"name":"account","type":"address"},{"indexed":true,"name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"role","type":"bytes32"},{"indexed":true,"name":"previousAdminRole","type":"bytes32"},{"indexed":true,"name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"role","type":"bytes32"},{"indexed":true,"name":"account","type":"address"},{"indexed":true,"name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"inputs":[{"name":"interface_id","type":"bytes4"}],"name":"supportsInterface","outputs":[{"name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"name":"arg0","type":"bytes32"},{"name":"arg1","type":"address"}],"name":"hasRole","outputs":[{"name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"name":"role","type":"bytes32"},{"name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"role","type":"bytes32"},{"name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"_from","type":"address"}],"name":"delegated","outputs":[{"name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"name":"_to","type":"address"}],"name":"delegator","outputs":[{"name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"name":"_user","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"name":"_user","type":"address"},{"name":"_timestamp","type":"uint256"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"name":"_timestamp","type":"uint256"}],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"name":"_addr","type":"address"}],"name":"get_last_user_slope","outputs":[{"name":"","type":"int128"}],"stateMutability":"view","type":"function"},{"inputs":[{"name":"_addr","type":"address"}],"name":"locked__end","outputs":[{"name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"name":"_user","type":"address"},{"name":"_user_point_epoch","type":"uint256"},{"components":[{"name":"bias","type":"int128"},{"name":"slope","type":"int128"},{"name":"ts","type":"uint256"},{"name":"blk","type":"uint256"}],"name":"_user_point_history","type":"tuple"},{"components":[{"name":"amount","type":"int128"},{"name":"end","type":"uint256"}],"name":"_locked","type":"tuple"},{"name":"_block_number","type":"uint256"}],"name":"update_balance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"_epoch","type":"uint256"},{"components":[{"name":"bias","type":"int128"},{"name":"slope","type":"int128"},{"name":"ts","type":"uint256"},{"name":"blk","type":"uint256"}],"name":"_point_history","type":"tuple"},{"name":"_slope_changes","type":"int128[]"},{"name":"_block_number","type":"uint256"}],"name":"update_total","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_block_number","type":"uint256"}],"name":"update_delegation","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"BALANCE_VERIFIER","outputs":[{"name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TOTAL_VERIFIER","outputs":[{"name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DELEGATION_VERIFIER","outputs":[{"name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"epoch","outputs":[{"name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"name":"arg0","type":"uint256"}],"name":"point_history","outputs":[{"components":[{"name":"bias","type":"int128"},{"name":"slope","type":"int128"},{"name":"ts","type":"uint256"},{"name":"blk","type":"uint256"}],"name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"name":"arg0","type":"address"}],"name":"user_point_epoch","outputs":[{"name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"name":"arg0","type":"address"},{"name":"arg1","type":"uint256"}],"name":"user_point_history","outputs":[{"components":[{"name":"bias","type":"int128"},{"name":"slope","type":"int128"},{"name":"ts","type":"uint256"},{"name":"blk","type":"uint256"}],"name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"name":"arg0","type":"address"}],"name":"locked","outputs":[{"components":[{"name":"amount","type":"int128"},{"name":"end","type":"uint256"}],"name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"name":"arg0","type":"uint256"}],"name":"slope_changes","outputs":[{"name":"","type":"int128"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"last_block_number","outputs":[{"name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"version","outputs":[{"name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"outputs":[],"stateMutability":"nonpayable","type":"constructor"}]

Updating the Oracle

The "Updating the Oracle" section describes the privileged functions that allow authorized verifiers to update the state of the L2VotingEscrowOracle contract. These include updating individual user veCRV balances (update_balance) and the global voting power state (update_total). Each function is protected by role-based access control, ensuring only designated accounts (with roles like BALANCE_VERIFIER or TOTAL_VERIFIER) can perform updates. Updates are linearized using a block number check to prevent outdated data from overwriting newer state. This mechanism ensures the oracle remains synchronized with the canonical VotingEscrow contract on Ethereum, providing accurate and secure off-chain voting power data for L2 environments.

update_balance

L2VotingEscrowOracle.update_balance(_user: address, _user_point_epoch: uint256, _user_point_history: Point, _locked: LockedBalance, _block_number: uint256):
Guarded Method by Snekmate 🐍

This contract makes use of a Snekmate module to manage roles and permissions. Calling the update_balance() function can only be done by the address holding the BALANCE_VERIFIER role.

Function to update the user's veCRV balance.

Emits: UpdateBalance

InputTypeDescription
_useraddressAddress of the user to update the balance
_user_point_epochuint256Last _user's checkpointed epoch
_user_point_historyPointLast _user's point history
_lockedLockedBalance_user's locked balance
_block_numberuint256Block number
<>Source code
event UpdateBalance:
_user: address
_user_point_epoch: uint256

user_point_epoch: public(HashMap[address, uint256])
user_point_history: public(HashMap[address, HashMap[uint256, Point]])

locked: public(HashMap[address, LockedBalance])

@external
def update_balance(
_user: address,
_user_point_epoch: uint256,
_user_point_history: Point,
_locked: LockedBalance,
_block_number: uint256,
):
"""
@notice Update user balance
@param _user Address of the user to verify for
@param _user_point_epoch Last `_user`s checkpointed epoch
@param _user_point_history Last `_user`s point history
@param _locked `_user`s locked balance
"""
access_control._check_role(BALANCE_VERIFIER, msg.sender)
assert self.last_block_number <= _block_number, "Outdated update"
# assert (
# self.user_point_epoch[_user] <= _user_point_epoch
# and self.user_point_history[_user][_user_point_epoch].ts <= _user_point_history.ts
# ), "Outdated update"

self.user_point_epoch[_user] = _user_point_epoch
self.user_point_history[_user][_user_point_epoch] = _user_point_history
self.locked[_user] = _locked
log UpdateBalance(_user, _user_point_epoch)

self.last_block_number = _block_number
Example
>>> soon

balanceOf

L2VotingEscrowOracle.balanceOf(_user: address, _timestamp: uint256 = block.timestamp) -> uint256: view

Returns the veCRV balance of a user at a given timestamp, accounting for delegation.

Returns: veCRV balance of the user at a specific timestamp (uint256).

InputTypeDescription
_useraddressAddress of the user
_timestampuint256Timestamp for balance check; defaults to current ts
<>Source code
L2VotingEscrowOracle.vy
epoch: public(uint256)
point_history: public(HashMap[uint256, Point])

@view
@external
def balanceOf(_user: address, _timestamp: uint256 = block.timestamp) -> uint256:
"""
@notice Get veCRV balance of user
@param _user Address of the user
@param _timestamp Timestamp for the balance check
@return Balance of user
"""
user: address = self._get_user_after_delegation(_user)
if user == empty(address):
return 0
return self._balanceOf(user, _timestamp)

@view
def _balanceOf(user: address, timestamp: uint256) -> uint256:
epoch: uint256 = self.user_point_epoch[user]
if epoch == 0:
return 0

last_point: Point = self.user_point_history[user][epoch]
last_point.bias -= last_point.slope * convert(timestamp - last_point.ts, int128)
if last_point.bias < 0:
return 0

return convert(last_point.bias, uint256)
Example

This example returns the veCRV balance of 0x71F718D3e4d1449D1502A6A7595eb84eBcCB1683 on Optimism.

>>> L2VotingEscrowOracle.balanceOf("0x71F718D3e4d1449D1502A6A7595eb84eBcCB1683")
2036475234652423013212

update_total

L2VotingEscrowOracle.update_total(_epoch: uint256, _point_history: Point, _slope_changes: DynArray[int128, SLOPE_CHANGES_CNT], _block_number: uint256):
Guarded Method by Snekmate 🐍

This contract makes use of a Snekmate module to manage roles and permissions. Calling the update_total() function can only be done by the address holding the TOTAL_VERIFIER role.

Updates the global VotingEscrow values, including epoch, point history, and slope changes.

Emits: UpdateTotal

InputTypeDescription
_epochuint256Current epoch in VotingEscrow contract
_point_historyPointLast epoch point history
_slope_changesDynArray[int128, SLOPE_CHANGES_CNT]Slope changes for upcoming epochs
_block_numberuint256Block number for update linearization
<>Source code
event UpdateTotal:
_epoch: uint256

epoch: public(uint256)
point_history: public(HashMap[uint256, Point])

@external
def update_total(
_epoch: uint256,
_point_history: Point,
_slope_changes: DynArray[int128, SLOPE_CHANGES_CNT],
_block_number: uint256,
):
"""
@notice Update VotingEscrow global values
@param _epoch Current epoch in VotingEscrow contract
@param _point_history Last epoch point history
@param _slope_changes Slope changes for upcoming epochs
"""
access_control._check_role(TOTAL_VERIFIER, msg.sender)
assert self.last_block_number <= _block_number, "Outdated update"
# assert (
# self.epoch <= _epoch and self.point_history[_epoch].ts <= _point_history.ts
# ), "Outdated update"

self.epoch = _epoch
self.point_history[_epoch] = _point_history

start_time: uint256 = WEEK + (_point_history.ts // WEEK) * WEEK
for i: uint256 in range(len(_slope_changes), bound=SLOPE_CHANGES_CNT):
self.slope_changes[start_time + WEEK * i] = _slope_changes[i]

log UpdateTotal(_epoch)

self.last_block_number = _block_number
Example
>>> soon

totalSupply

L2VotingEscrowOracle.totalSupply(_timestamp: uint256 = block.timestamp) -> uint256: view

Getter for the total veCRV voting power at a given timestamp.

Returns: total veCRV supply at a specific timestamp (uint256).

InputTypeDescription
_timestampuint256Timestamp for total supply check; defaults to current ts
<>Source code
L2VotingEscrowOracle.vy
point_history: public(HashMap[uint256, Point])

slope_changes: public(HashMap[uint256, int128])

@view
@external
def totalSupply(_timestamp: uint256 = block.timestamp) -> uint256:
"""
@notice Calculate total voting power
@param _timestamp Timestamp at which to check totalSupply
@return Total supply
"""
last_point: Point = self.point_history[self.epoch]
t_i: uint256 = (last_point.ts // WEEK) * WEEK
for i: uint256 in range(256):
t_i += WEEK

d_slope: int128 = 0
if t_i > _timestamp:
t_i = _timestamp
else:
d_slope = self.slope_changes[t_i]
last_point.bias -= last_point.slope * convert(t_i - last_point.ts, int128)
if t_i == _timestamp or d_slope == 0:
break

last_point.slope += d_slope
last_point.ts = t_i

if last_point.bias < 0:
return 0

return convert(last_point.bias, uint256)
Example

This example returns the total veCRV supply on a specific Layer 2 network.

>>> L2VotingEscrowOracle.totalSupply(1751872783)
799701502604227430381519403

Delegations

The L2VotingEscrowOracle contract supports delegation of veCRV balances, allowing one address to delegate its voting power to another. This is managed through the delegated and delegator view functions, which let users query the current delegation relationships. When a user delegates, their veCRV balance is effectively counted towards the delegatee, enabling boosting strategies. Delegation updates are controlled by the DELEGATION_VERIFIER role, ensuring only authorized entities can modify delegation mappings.

delegated

L2VotingEscrowOracle.delegated(_from: address) -> address: view

Getter for the address to which the veCRV balance of _from is delegated. If not delegated, returns _from itself.

Returns: address receiving the delegation (address).

InputTypeDescription
_fromaddressAddress of the delegator
<>Source code
L2VotingEscrowOracle.vy
# [address from][address to]
delegation_from: HashMap[address, address]

@external
@view
def delegated(_from: address) -> address:
"""
@notice Get contract balance being delegated to
@param _from Address of delegator
@return Destination address of delegation
"""
addr: address = self.delegation_from[_from]
if addr == empty(address):
addr = _from
return addr
Example

This example shows the delegation of 0x5802ad5D5B1c63b3FC7DE97B55e6db19e5d36462 to 0x71F718D3e4d1449D1502A6A7595eb84eBcCB1683.

>>> L2VotingEscrowOracle.delegated("0x5802ad5D5B1c63b3FC7DE97B55e6db19e5d36462")
"0x71F718D3e4d1449D1502A6A7595eb84eBcCB1683"

delegator

L2VotingEscrowOracle.delegator(_to: address) -> address: view

Getter for the address that delegated its veCRV balance to _to. If not delegated, returns _to itself.

Returns: address of the delegator (address).

InputTypeDescription
_toaddressAddress of the delegatee
<>Source code
L2VotingEscrowOracle.vy
delegation_to: HashMap[address, address]

@external
@view
def delegator(_to: address) -> address:
"""
@notice Get contract delegating balance to `_to`
@param _to Address of delegated to
@return Address of delegator
"""
addr: address = self.delegation_to[_to]
if addr == empty(address):
addr = _to
return addr
Example

This example returns the delegator of 0x71F718D3e4d1449D1502A6A7595eb84eBcCB1683.

>>> L2VotingEscrowOracle.delegator("0x71F718D3e4d1449D1502A6A7595eb84eBcCB1683")
"0x5802ad5D5B1c63b3FC7DE97B55e6db19e5d36462"

update_delegation

L2VotingEscrowOracle.update_delegation(_from: address, _to: address, _block_number: uint256):
Guarded Method by Snekmate 🐍

This contract makes use of a Snekmate module to manage roles and permissions. Calling the update_delegation() function can only be done by the address holding the DELEGATION_VERIFIER role.

Function to update the delegation of veCRV balance from _from to _to.

Emits: Delegate

InputTypeDescription
_fromaddressAddress being delegated
_toaddressAddress delegated to
_block_numberuint256Block number at which delegation holds true
<>Source code
# [address from][address to]
delegation_from: HashMap[address, address]
delegation_to: HashMap[address, address]
last_delegation: HashMap[address, uint256]

last_block_number: public(uint256)

@external
def update_delegation(_from: address, _to: address, _block_number: uint256):
"""
@notice Update veCRV balance delegation
@dev Block number is used to linearize updates
@param _from Address being delegated
@param _to Address delegated to
@param _block_number Block number at which delegation holds true
"""
access_control._check_role(DELEGATION_VERIFIER, msg.sender)
assert self.last_block_number <= _block_number, "Outdated update"

delegated: address = self.delegation_from[_from]
if delegated != empty(address): # revoke delegation
self.delegation_to[delegated] = empty(address)
self.delegation_from[_from] = _to
if _to != empty(address):
self.delegation_to[_to] = _from
log Delegate(_from, _to)

self.last_block_number = _block_number
Example
>>> soon

Roles and Ownership Management

The L2VotingEscrowOracle contract uses a role-based access control system, implemented via the Snekmate access_control module, to manage permissions for sensitive operations. Roles such as BALANCE_VERIFIER, TOTAL_VERIFIER, and DELEGATION_VERIFIER restrict who can update user balances, total supply, and delegation mappings, respectively. The DEFAULT_ADMIN_ROLE acts as the admin for all roles, and only accounts with the appropriate admin role can grant or revoke roles. This structure ensures that only authorized entities can perform privileged actions, providing robust security and flexibility for contract management.

BALANCE_VERIFIER

L2VotingEscrowOracle.BALANCE_VERIFIER() -> bytes32: view

The role identifier for accounts allowed to update user balances.

Returns: role hash (bytes32).

<>Source code
L2VotingEscrowOracle.vy
BALANCE_VERIFIER: public(constant(bytes32)) = keccak256("BALANCE_VERIFIER")

@deploy
def __init__():
access_control.__init__()
access_control._set_role_admin(BALANCE_VERIFIER, access_control.DEFAULT_ADMIN_ROLE)
access_control._set_role_admin(TOTAL_VERIFIER, access_control.DEFAULT_ADMIN_ROLE)
access_control._set_role_admin(DELEGATION_VERIFIER, access_control.DEFAULT_ADMIN_ROLE)
Example

This example returns the BALANCE_VERIFIER role as bytes32 of the veCRV Oracle on Optimism.

>>> L2VotingEscrowOracle.BALANCE_VERIFIER()
'0x91ecbab409000ca436e362529d6a0ee19bfacafc306d0b7328e4b31a37513d1c'

TOTAL_VERIFIER

L2VotingEscrowOracle.TOTAL_VERIFIER() -> bytes32: view

The role identifier for accounts allowed to update total supply.

Returns: role hash (bytes32).

<>Source code
L2VotingEscrowOracle.vy
TOTAL_VERIFIER: public(constant(bytes32)) = keccak256("TOTAL_VERIFIER")

@deploy
def __init__():
access_control.__init__()
access_control._set_role_admin(BALANCE_VERIFIER, access_control.DEFAULT_ADMIN_ROLE)
access_control._set_role_admin(TOTAL_VERIFIER, access_control.DEFAULT_ADMIN_ROLE)
access_control._set_role_admin(DELEGATION_VERIFIER, access_control.DEFAULT_ADMIN_ROLE)
Example

This example returns the TOTAL_VERIFIER role as bytes32 of the veCRV Oracle on Optimism.

>>> L2VotingEscrowOracle.TOTAL_VERIFIER()
'0x91bab4a1f219aaf3591b80c219b7a6eda6e5ddcadf2001c395591dcc40ecfbb7'

DELEGATION_VERIFIER

L2VotingEscrowOracle.DELEGATION_VERIFIER() -> bytes32: view

The role identifier for accounts allowed to update delegation.

Returns: role hash (bytes32).

<>Source code
L2VotingEscrowOracle.vy
DELEGATION_VERIFIER: public(constant(bytes32)) = keccak256("DELEGATION_VERIFIER")

@deploy
def __init__():
access_control.__init__()
access_control._set_role_admin(BALANCE_VERIFIER, access_control.DEFAULT_ADMIN_ROLE)
access_control._set_role_admin(TOTAL_VERIFIER, access_control.DEFAULT_ADMIN_ROLE)
access_control._set_role_admin(DELEGATION_VERIFIER, access_control.DEFAULT_ADMIN_ROLE)
Example

This example returns the DELEGATION_VERIFIER role as bytes32 of the veCRV Oracle on Optimism.

>>> L2VotingEscrowOracle.DELEGATION_VERIFIER()
'0xe887cc0717dab2ad628f68695129fefff34ee397bdd39e44a259e2cae80f49b7'

DEFAULT_ADMIN_ROLE

L2VotingEscrowOracle.DEFAULT_ADMIN_ROLE() -> bytes32: view

Getter for the default admin role.

Returns: default admin (bytes32).

<>Source code
from snekmate.auth import access_control

initializes: access_control
exports: (
access_control.supportsInterface,
access_control.hasRole,
access_control.DEFAULT_ADMIN_ROLE,
access_control.grantRole,
access_control.revokeRole,
)
Example

This example returns the DEFAULT_ADMIN_ROLE role as bytes32 of the veCRV Oracle on Optimism.

>>> L2VotingEscrowOracle.DEFAULT_ADMIN_ROLE()
'0x0000000000000000000000000000000000000000000000000000000000000000'

grantRole

L2VotingEscrowOracle.grantRole(role: bytes32, account: address):
Guarded Method by Snekmate 🐍

This contract makes use of a Snekmate module to manage roles and permissions. Granting a role is only callable by an account with the admin role for the given role.

Function to grant a role to an account.

Emits: RoleGranted

InputTypeDescription
rolebytes32Role identifier
accountaddressAddress to grant the role to
<>Source code
from snekmate.auth import access_control

initializes: access_control
exports: (
access_control.supportsInterface,
access_control.hasRole,
access_control.DEFAULT_ADMIN_ROLE,
access_control.grantRole,
access_control.revokeRole,
)

revokeRole

L2VotingEscrowOracle.revokeRole(role: bytes32, account: address):
Guarded Method by Snekmate 🐍

This contract makes use of a Snekmate module to manage roles and permissions. Revoking a role is only callable by an account with the admin role for the given role.

Function to revoke a role from an account.

Emits: RoleRevoked

InputTypeDescription
rolebytes32Role identifier
accountaddressAddress to revoke the role from
<>Source code
from snekmate.auth import access_control

initializes: access_control
exports: (
access_control.supportsInterface,
access_control.hasRole,
access_control.DEFAULT_ADMIN_ROLE,
access_control.grantRole,
access_control.revokeRole,
)

supportsInterface

L2VotingEscrowOracle.supportsInterface(interface_id: bytes4) -> bool: view

Getter to check if the contract implements a specific interface ID.

Returns: true or false (bool).

InputTypeDescription
interface_idbytes4Interface identifier
<>Source code
from snekmate.auth import access_control

initializes: access_control
exports: (
access_control.supportsInterface,
access_control.hasRole,
access_control.DEFAULT_ADMIN_ROLE,
access_control.grantRole,
access_control.revokeRole,
)
Example
>>> L2VotingEscrowOracle.supportsInterface("0x01FFC9A7")
'true'

hasRole

L2VotingEscrowOracle.hasRole(arg0: bytes32, arg1: address) -> bool: view

Getter to check if an address has a specified role.

Returns: true or false (bool).

InputTypeDescription
rolebytes32Role identifier
accountaddressAddress to check
<>Source code
from snekmate.auth import access_control

initializes: access_control
exports: (
access_control.supportsInterface,
access_control.hasRole,
access_control.DEFAULT_ADMIN_ROLE,
access_control.grantRole,
access_control.revokeRole,
)
Example
>>> L2VotingEscrowOracle.hasRole('0xe887cc0717dab2ad628f68695129fefff34ee397bdd39e44a259e2cae80f49b7', '0x1d04Fcb6293690D75E9262A89Ac3B816772E6841')
'true'

User Info

The contract provides getter functions that allow querying detailed information about user voting power, lock status, and historical checkpoints in the L2VotingEscrowOracle contract. These functions enable users and integrators to track veCRV balances, lock expirations, voting power decay (slope), and historical states for any address.

  • If a user address is not found or has no history, functions like balanceOf, locked__end, and get_last_user_slope will return 0
  • If a delegation is not set, delegated(_from) and delegator(_to) will return the address itself
  • If a user delegates out but is not delegated to, some getters may return 0 to indicate no effective balance

get_last_user_slope

L2VotingEscrowOracle.get_last_user_slope(_addr: address) -> int128: view

Returns the most recently recorded rate of voting power decrease (slope) for a user.

Returns: last user slope (int128).

InputTypeDescription
_addraddressAddress of the user
<>Source code
L2VotingEscrowOracle.vy
user_point_epoch: public(HashMap[address, uint256])
user_point_history: public(HashMap[address, HashMap[uint256, Point]])

# [address from][address to]
delegation_from: HashMap[address, address]
delegation_to: HashMap[address, address]

@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
"""
user: address = self._get_user_after_delegation(_addr)
if user == empty(address):
return 0
uepoch: uint256 = self.user_point_epoch[user]
return self.user_point_history[user][uepoch].slope

@view
def _get_user_after_delegation(_user: address) -> address:
user: address = self.delegation_to[_user]
if user == empty(address):
if self.delegation_from[_user] not in [empty(address), _user]: # only delegation out
return empty(address)
user = _user
return user
Example
>>> L2VotingEscrowOracle.get_last_user_slope("0x71F718D3e4d1449D1502A6A7595eb84eBcCB1683")
31709791983764

locked__end

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

Getter for the timestamp when a user's lock finishes.

Returns: ts when the lock ends (uint256).

InputTypeDescription
_addraddressAddress of the user
<>Source code
L2VotingEscrowOracle.vy
locked: public(HashMap[address, LockedBalance])

# [address from][address to]
delegation_from: HashMap[address, address]
delegation_to: HashMap[address, address]

@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
"""
user: address = self._get_user_after_delegation(_addr)
if user == empty(address):
return 0
return self.locked[user].end

@view
def _get_user_after_delegation(_user: address) -> address:
user: address = self.delegation_to[_user]
if user == empty(address):
if self.delegation_from[_user] not in [empty(address), _user]: # only delegation out
return empty(address)
user = _user
return user
Example

This example returns the timestamp when the user's veCRV lock ends.

>>> L2VotingEscrowOracle.locked__end("0x71F718D3e4d1449D1502A6A7595eb84eBcCB1683")
1816214400

epoch

L2VotingEscrowOracle.epoch() -> uint256: view

The current epoch of the VotingEscrow contract.

Returns: current epoch (uint256).

<>Source code
L2VotingEscrowOracle.vy
epoch: public(uint256)
Example
>>> L2VotingEscrowOracle.epoch()
58959

point_history

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

Getter for the point history of point arg0.

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

InputTypeDescription
arg0uint256value of the point to check
<>Source code
L2VotingEscrowOracle.vy
struct Point:
bias: int128
slope: int128
ts: uint256
blk: uint256

point_history: public(HashMap[uint256, Point])
Example

This example returns the point history of an epoch.

>>> L2VotingEscrowOracle.point_history(58959)
800073125242408678972029063, 6788868070537972059, 1751818043, 22861249

user_point_epoch

L2VotingEscrowOracle.user_point_epoch(_addr: address) -> uint256: view

Getter for the last checkpointed epoch for a user.

Returns: last checkpointed epoch (uint256).

InputTypeDescription
_addraddressAddress of the user
<>Source code
L2VotingEscrowOracle.vy
struct Point:
bias: int128
slope: int128
ts: uint256
blk: uint256

user_point_history: public(HashMap[address, HashMap[uint256, Point]])
Example

This example returns the user point epoch of an address.

>>> L2VotingEscrowOracle.user_point_epoch("0x71F718D3e4d1449D1502A6A7595eb84eBcCB1683")
5

user_point_history

L2VotingEscrowOracle.user_point_history(arg0: address, arg1: uint256) -> Point: view

Getter for the point history for a user at a given index.

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

InputTypeDescription
arg0addressAddress of the user
arg1uint256Epoch index
<>Source code
L2VotingEscrowOracle.vy
struct Point:
bias: int128
slope: int128
ts: uint256
blk: uint256

user_point_history: public(HashMap[address, HashMap[uint256, Point]])
Example

This example returns the user point history of the address 0x71F718D3e4d1449D1502A6A7595eb84eBcCB1683 at epoch 5.

>>> L2VotingEscrowOracle.user_point_history("0x71F718D3e4d1449D1502A6A7595eb84eBcCB1683", 5)
3740249651192218998932, 1709791983764, 1712172287, 19577313

locked

L2VotingEscrowOracle.locked(arg0: address) -> LockedBalance: view

Returns the locked balance struct for a user.

Returns: LockedBalance struct containing amount (int128) and end timestamp (uint256) of locked CRV.

InputTypeDescription
arg0addressAddress of the user
<>Source code
L2VotingEscrowOracle.vy
struct LockedBalance:
amount: int128
end: uint256

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

This example returns the total amount of CRV tokens locked, along with the timestamp when the lock ends.

>>> L2VotingEscrowOracle.locked("0x71F718D3e4d1449D1502A6A7595eb84eBcCB1683")
4000000000000000000000, 1830124800

slope_changes

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

Getter for the slope change at a given future timestamp.

Returns: slope change (int128).

InputTypeDescription
arg0uint256Timestamp
<>Source code
L2VotingEscrowOracle.vy
slope_changes: public(HashMap[uint256, int128])
Example
>>> L2VotingEscrowOracle.slope_changes(1760971290)
0

last_block_number

L2VotingEscrowOracle.last_block_number() -> uint256: view

Getter for the last ETH block number at which an update was made.

Returns: block number (uint256).

<>Source code
L2VotingEscrowOracle.vy
last_block_number: public(uint256)
Example

This example returns the last Ethereum mainnet block at which an update to either the total supply or a user's veCRV balance was made.

>>> L2VotingEscrowOracle.last_block_number()
22861295

Other Methods

version

L2VotingEscrowOracle.version() -> String[8]: view

Getter for the contract version.

Returns: version string (String[8]).

<>Source code
L2VotingEscrowOracle.vy
version: public(constant(String[8])) = "1.0.0"
Example
>>> L2VotingEscrowOracle.version()
'1.0.0'

name

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

Getter for the token name.

Returns: token name (String[64]).

<>Source code
L2VotingEscrowOracle.vy
name: public(constant(String[64])) = "Vote-escrowed CRV"
Example
>>> L2VotingEscrowOracle.name()
'Vote-escrowed CRV'

symbol

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

Getter for the token symbol.

Returns: token symbol (String[32]).

<>Source code
L2VotingEscrowOracle.vy
symbol: public(constant(String[32])) = "veCRV"
Example
>>> L2VotingEscrowOracle.symbol()
'veCRV'

decimals

L2VotingEscrowOracle.decimals() -> uint256: view

Getter for the token decimals.

Returns: decimals (uint256).

<>Source code
L2VotingEscrowOracle.vy
decimals: public(constant(uint256)) = 18
Example
>>> L2VotingEscrowOracle.decimals()
18