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.vyThe 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:
Optimism:
0xf1946d4879646e0fcd8f5bb32a5636ed8055176dArbitrum:
0x4D1AF9911e4c19f64Be36c36EF39Fd026Bc9bb61Fraxtal:
0xF3daD3Ca2eF135b248128Ab1Ed984FB6F2185CBfSonic:
0x361aa6D20fbf6185490eB2ddf1DD1D3F301C201dMantle:
0x852F32c22C5035EA12566EDFB4415625776D75d5Base:
0xeB896fB7D1AaE921d586B0E5a037496aFd3E2412Taiko:
0x5C57BdcFF69B4F1D894EA70c0470D39C8FA0ee30
{ }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):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
| Input | Type | Description |
|---|---|---|
_user | address | Address of the user to update the balance |
_user_point_epoch | uint256 | Last _user's checkpointed epoch |
_user_point_history | Point | Last _user's point history |
_locked | LockedBalance | _user's locked balance |
_block_number | uint256 | Block number |
<>Source code▼
- L2VotingEscrowOracle.vy
- access_control.vy (Snekmate 🐍)
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
@internal
@view
def _check_role(role: bytes32, account: address):
"""
@dev Reverts with a standard message if `account`
is missing `role`.
@param role The 32-byte role definition.
@param account The 20-byte address of the account.
"""
assert self.hasRole[role][account], "access_control: account is missing role"
▶Example▼
>>> soon
balanceOf
L2VotingEscrowOracle.balanceOf(_user: address, _timestamp: uint256 = block.timestamp) -> uint256: viewReturns the veCRV balance of a user at a given timestamp, accounting for delegation.
Returns: veCRV balance of the user at a specific timestamp (uint256).
| Input | Type | Description |
|---|---|---|
_user | address | Address of the user |
_timestamp | uint256 | Timestamp for balance check; defaults to current ts |
<>Source code▼
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):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
| Input | Type | Description |
|---|---|---|
_epoch | uint256 | Current epoch in VotingEscrow contract |
_point_history | Point | Last epoch point history |
_slope_changes | DynArray[int128, SLOPE_CHANGES_CNT] | Slope changes for upcoming epochs |
_block_number | uint256 | Block number for update linearization |
<>Source code▼
- L2VotingEscrowOracle.vy
- access_control.vy (Snekmate 🐍)
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
@internal
@view
def _check_role(role: bytes32, account: address):
"""
@dev Reverts with a standard message if `account`
is missing `role`.
@param role The 32-byte role definition.
@param account The 20-byte address of the account.
"""
assert self.hasRole[role][account], "access_control: account is missing role"
▶Example▼
>>> soon
totalSupply
L2VotingEscrowOracle.totalSupply(_timestamp: uint256 = block.timestamp) -> uint256: viewGetter for the total veCRV voting power at a given timestamp.
Returns: total veCRV supply at a specific timestamp (uint256).
| Input | Type | Description |
|---|---|---|
_timestamp | uint256 | Timestamp for total supply check; defaults to current ts |
<>Source code▼
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: viewGetter for the address to which the veCRV balance of _from is delegated. If not delegated, returns _from itself.
Returns: address receiving the delegation (address).
| Input | Type | Description |
|---|---|---|
_from | address | Address of the delegator |
<>Source code▼
# [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: viewGetter for the address that delegated its veCRV balance to _to. If not delegated, returns _to itself.
Returns: address of the delegator (address).
| Input | Type | Description |
|---|---|---|
_to | address | Address of the delegatee |
<>Source code▼
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):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
| Input | Type | Description |
|---|---|---|
_from | address | Address being delegated |
_to | address | Address delegated to |
_block_number | uint256 | Block number at which delegation holds true |
<>Source code▼
- L2VotingEscrowOracle.vy
- access_control.vy (Snekmate 🐍)
# [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
@internal
@view
def _check_role(role: bytes32, account: address):
"""
@dev Reverts with a standard message if `account`
is missing `role`.
@param role The 32-byte role definition.
@param account The 20-byte address of the account.
"""
assert self.hasRole[role][account], "access_control: account is missing role"
▶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: viewThe role identifier for accounts allowed to update user balances.
Returns: role hash (bytes32).
<>Source code▼
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: viewThe role identifier for accounts allowed to update total supply.
Returns: role hash (bytes32).
<>Source code▼
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: viewThe role identifier for accounts allowed to update delegation.
Returns: role hash (bytes32).
<>Source code▼
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: viewGetter for the default admin role.
Returns: default admin (bytes32).
<>Source code▼
- L2VotingEscrowOracle.vy
- access_control.vy (Snekmate 🐍)
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,
)
# @dev The default 32-byte admin role.
DEFAULT_ADMIN_ROLE: public(constant(bytes32)) = empty(bytes32)
▶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):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
| Input | Type | Description |
|---|---|---|
role | bytes32 | Role identifier |
account | address | Address to grant the role to |
<>Source code▼
- L2VotingEscrowOracle.vy
- access_control.vy (Snekmate 🐍)
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,
)
@external
def grantRole(role: bytes32, account: address):
"""
@dev Grants `role` to `account`.
@notice If `account` had not been already
granted `role`, emits a `RoleGranted`
event. Note that the caller must have
`role`'s admin role.
@param role The 32-byte role definition.
@param account The 20-byte address of the account.
"""
self._check_role(self.getRoleAdmin[role], msg.sender)
self._grant_role(role, account)
@internal
def _grant_role(role: bytes32, account: address):
"""
@dev Grants `role` to `account`.
@notice This is an `internal` function without
access restriction.
@param role The 32-byte role definition.
@param account The 20-byte address of the account.
"""
if (not(self.hasRole[role][account])):
self.hasRole[role][account] = True
log IAccessControl.RoleGranted(role, account, msg.sender)
revokeRole
L2VotingEscrowOracle.revokeRole(role: bytes32, account: address):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
| Input | Type | Description |
|---|---|---|
role | bytes32 | Role identifier |
account | address | Address to revoke the role from |
<>Source code▼
- L2VotingEscrowOracle.vy
- access_control.vy (Snekmate 🐍)
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,
)
@external
def revokeRole(role: bytes32, account: address):
"""
@dev Revokes `role` from `account`.
@notice If `account` had been granted `role`,
emits a `RoleRevoked` event. Note that
the caller must have `role`'s admin role.
@param role The 32-byte role definition.
@param account The 20-byte address of the account.
"""
self._check_role(self.getRoleAdmin[role], msg.sender)
self._revoke_role(role, account)
@internal
def _revoke_role(role: bytes32, account: address):
"""
@dev Revokes `role` from `account`.
@notice This is an `internal` function without
access restriction.
@param role The 32-byte role definition.
@param account The 20-byte address of the account.
"""
if (self.hasRole[role][account]):
self.hasRole[role][account] = False
log IAccessControl.RoleRevoked(role, account, msg.sender)
supportsInterface
L2VotingEscrowOracle.supportsInterface(interface_id: bytes4) -> bool: viewGetter to check if the contract implements a specific interface ID.
Returns: true or false (bool).
| Input | Type | Description |
|---|---|---|
interface_id | bytes4 | Interface identifier |
<>Source code▼
- L2VotingEscrowOracle.vy
- access_control.vy (Snekmate 🐍)
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,
)
_SUPPORTED_INTERFACES: constant(bytes4[2]) = [
0x01FFC9A7, # The ERC-165 identifier for ERC-165.
0x7965DB0B, # The ERC-165 identifier for `IAccessControl`.
]
@external
@view
def supportsInterface(interface_id: bytes4) -> bool:
"""
@dev Returns `True` if this contract implements the
interface defined by `interface_id`.
@param interface_id The 4-byte interface identifier.
@return bool The verification whether the contract
implements the interface or not.
"""
return interface_id in _SUPPORTED_INTERFACES
▶Example▼
>>> L2VotingEscrowOracle.supportsInterface("0x01FFC9A7")
'true'
hasRole
L2VotingEscrowOracle.hasRole(arg0: bytes32, arg1: address) -> bool: viewGetter to check if an address has a specified role.
Returns: true or false (bool).
| Input | Type | Description |
|---|---|---|
role | bytes32 | Role identifier |
account | address | Address to check |
<>Source code▼
- L2VotingEscrowOracle.vy
- access_control.vy (Snekmate 🐍)
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,
)
# @dev Returns `True` if `account` has been granted `role`.
hasRole: public(HashMap[bytes32, HashMap[address, bool]])
▶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, andget_last_user_slopewill return0 - If a delegation is not set,
delegated(_from)anddelegator(_to)will return the address itself - If a user delegates out but is not delegated to, some getters may return
0to indicate no effective balance
get_last_user_slope
L2VotingEscrowOracle.get_last_user_slope(_addr: address) -> int128: viewReturns the most recently recorded rate of voting power decrease (slope) for a user.
Returns: last user slope (int128).
| Input | Type | Description |
|---|---|---|
_addr | address | Address of the user |
<>Source code▼
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: viewGetter for the timestamp when a user's lock finishes.
Returns: ts when the lock ends (uint256).
| Input | Type | Description |
|---|---|---|
_addr | address | Address of the user |
<>Source code▼
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: viewThe current epoch of the VotingEscrow contract.
Returns: current epoch (uint256).
<>Source code▼
epoch: public(uint256)
▶Example▼
>>> L2VotingEscrowOracle.epoch()
58959
point_history
L2VotingEscrowOracle.point_history(arg0: uint256) -> bias: int128, slope: int128, ts: uint256, blk: uint256: viewGetter for the point history of point arg0.
Returns: bias (int128), slope (int128), ts (uint256) and blk (uint256).
| Input | Type | Description |
|---|---|---|
arg0 | uint256 | value of the point to check |
<>Source code▼
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: viewGetter for the last checkpointed epoch for a user.
Returns: last checkpointed epoch (uint256).
| Input | Type | Description |
|---|---|---|
_addr | address | Address of the user |
<>Source code▼
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: viewGetter for the point history for a user at a given index.
Returns: Point struct containing bias (int128), slope (int128), ts (uint256) and blk (uint256).
| Input | Type | Description |
|---|---|---|
arg0 | address | Address of the user |
arg1 | uint256 | Epoch index |
<>Source code▼
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: viewReturns the locked balance struct for a user.
Returns: LockedBalance struct containing amount (int128) and end timestamp (uint256) of locked CRV.
| Input | Type | Description |
|---|---|---|
arg0 | address | Address of the user |
<>Source code▼
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: viewGetter for the slope change at a given future timestamp.
Returns: slope change (int128).
| Input | Type | Description |
|---|---|---|
arg0 | uint256 | Timestamp |
<>Source code▼
slope_changes: public(HashMap[uint256, int128])
▶Example▼
>>> L2VotingEscrowOracle.slope_changes(1760971290)
0
last_block_number
L2VotingEscrowOracle.last_block_number() -> uint256: viewGetter for the last ETH block number at which an update was made.
Returns: block number (uint256).
<>Source code▼
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]: viewGetter for the contract version.
Returns: version string (String[8]).
<>Source code▼
version: public(constant(String[8])) = "1.0.0"
▶Example▼
>>> L2VotingEscrowOracle.version()
'1.0.0'
name
L2VotingEscrowOracle.name() -> String[64]: viewGetter for the token name.
Returns: token name (String[64]).
<>Source code▼
name: public(constant(String[64])) = "Vote-escrowed CRV"
▶Example▼
>>> L2VotingEscrowOracle.name()
'Vote-escrowed CRV'
symbol
L2VotingEscrowOracle.symbol() -> String[32]: viewGetter for the token symbol.
Returns: token symbol (String[32]).
<>Source code▼
symbol: public(constant(String[32])) = "veCRV"
▶Example▼
>>> L2VotingEscrowOracle.symbol()
'veCRV'
decimals
L2VotingEscrowOracle.decimals() -> uint256: viewGetter for the token decimals.
Returns: decimals (uint256).
<>Source code▼
decimals: public(constant(uint256)) = 18
▶Example▼
>>> L2VotingEscrowOracle.decimals()
18