L2 veCRV Delegation
The L2veCRVDelegation contract enables users to delegate their veCRV voting power to different addresses on other networks (Layer 2s or sidechains). This is essential for veCRV-utility activities like veCRV boosting on chains where their original address may not be available or convenient. The contract supports both user-initiated delegation and DAO-administered delegation for special cases (e.g., lost keys or non-reachable addresses). The contract also includes mechanisms to allow or revoke delegation to an address and to prevent frontrunning attacks.
L2veCRVDelegation.vyThe source code for the L2veCRVDelegation.vy contract is available on GitHub. The contract is written in Vyper version 0.4.0.
The L2veCRVDelegation on Ethereum is deployed at
0xde1e6A7E8297076f070E857130E593107A0E0cF5 and contract version is 0.0.1.
{ }Contract ABI▼
[{"anonymous":false,"inputs":[{"indexed":true,"name":"_chain_id","type":"uint256"},{"indexed":true,"name":"_to","type":"address"}],"name":"AllowDelegation","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_chain_id","type":"uint256"},{"indexed":true,"name":"_from","type":"address"},{"indexed":false,"name":"_to","type":"address"}],"name":"Delegate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"previous_owner","type":"address"},{"indexed":true,"name":"new_owner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"inputs":[{"name":"new_owner","type":"address"}],"name":"transfer_ownership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"name":"_chain_id","type":"uint256"},{"name":"_from","type":"address"}],"name":"delegated","outputs":[{"name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"name":"_chain_id","type":"uint256"},{"name":"_to","type":"address"}],"name":"delegator","outputs":[{"name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"name":"_chain_id","type":"uint256"},{"name":"_to","type":"address"}],"name":"delegation_allowed","outputs":[{"name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"name":"_chain_id","type":"uint256"},{"name":"_to","type":"address"}],"name":"delegate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"_chain_id","type":"uint256"}],"name":"allow_delegation","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"_chain_id","type":"uint256"},{"name":"_allow","type":"bool"}],"name":"allow_delegation","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"_chain_id","type":"uint256"},{"name":"_from","type":"address"},{"name":"_to","type":"address"}],"name":"delegate_from","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"version","outputs":[{"name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"name":"_owner","type":"address"}],"outputs":[],"stateMutability":"nonpayable","type":"constructor"}]
Delegation
The delegation system in L2veCRVDelegation is designed to be flexible and secure. Users can delegate their veCRV voting power to another address on a specific chain, revoke delegation, or allow others to delegate to them. The contract also provides DAO-level controls for exceptional cases, ensuring that delegation can be managed even if a user is unable to interact directly. The following functions describe the available delegation mechanisms and their intended use cases.
delegate
L2veCRVDelegation.delegate(_chain_id: uint256, _to: address)Function to delegate veCRV to another address on a specific chain. Only addresses that have explicitly allowed delegation (via allow_delegation) can be delegated to. To revoke delegation, delegate to your own address.
Emits: Delegate event.
| Input | Type | Description |
|---|---|---|
_chain_id | uint256 | Chain ID where to set the delegation |
_to | address | Address to delegate to |
<>Source code▼
event Delegate:
_chain_id: indexed(uint256)
_from: indexed(address)
_to: address
# [chain id][address from][address to]
delegation_from: HashMap[uint256, HashMap[address, address]]
delegation_to: HashMap[uint256, HashMap[address, address]]
@external
def delegate(_chain_id: uint256, _to: address):
"""
@notice Delegate veCRV balance to another address
@dev To revoke delegation set delegation to yourself
@param _chain_id Chain ID where to set
@param _to Address to delegate to
"""
assert self.delegation_to[_chain_id][_to] == self, "Not allowed"
self._delegate(_chain_id, msg.sender, _to)
def _delegate(_chain_id: uint256, _from: address, _to: address):
# Clean previous delegation
prev_to: address = self.delegation_from[_chain_id][_from]
if prev_to not in [empty(address), self]:
self.delegation_to[_chain_id][prev_to] = empty(address)
self.delegation_from[_chain_id][_from] = _to
self.delegation_to[_chain_id][_to] = _from
log Delegate(_chain_id, _from, _to)
▶Example▼
This example delegates the caller's veCRV balance to 0x71F718D3e4d1449D1502A6A7595eb84eBcCB1683 on chain 146.
>>> L2veCRVDelegation.delegate(146, '0x71F718D3e4d1449D1502A6A7595eb84eBcCB1683')
delegate_from
L2veCRVDelegation.delegate_from(_chain_id: uint256, _from: address, _to: address)This contract makes use of a Snekmate module to manage roles and permissions. This specific function can only be called by the current owner of the contract.
DAO-only function to set delegation for addresses that cannot interact directly (e.g., lost keys or non-reachable addresses). Only callable by the contract owner.
Emits: Delegate event.
| Input | Type | Description |
|---|---|---|
_chain_id | uint256 | Chain ID where to set the delegation |
_from | address | Address that delegates |
_to | address | Address balance being delegated to |
<>Source code▼
- L2veCRVDelegation.vy
- ownable.vy (Snekmate 🐍)
from snekmate.auth import ownable
event Delegate:
_chain_id: indexed(uint256)
_from: indexed(address)
_to: address
# [chain id][address from][address to]
delegation_from: HashMap[uint256, HashMap[address, address]]
delegation_to: HashMap[uint256, HashMap[address, address]]
@external
def delegate_from(_chain_id: uint256, _from: address, _to: address):
"""
@notice DAO-owned method to set delegation for non-reachable addresses
@param _chain_id Chain ID where to set
@param _from Address that delegates
@param _to Address balance being delegated to
"""
ownable._check_owner()
self._delegate(_chain_id, _from, _to)
def _delegate(_chain_id: uint256, _from: address, _to: address):
# Clean previous delegation
prev_to: address = self.delegation_from[_chain_id][_from]
if prev_to not in [empty(address), self]:
self.delegation_to[_chain_id][prev_to] = empty(address)
self.delegation_from[_chain_id][_from] = _to
self.delegation_to[_chain_id][_to] = _from
log Delegate(_chain_id, _from, _to)
owner: public(address)
@internal
def _check_owner():
"""
@dev Throws if the sender is not the owner.
"""
assert msg.sender == self.owner, "ownable: caller is not the owner"
▶Example▼
In this example, the DAO delegates the veCRV balance from 0x5802ad5D5B1c63b3FC7DE97B55e6db19e5d36462 to 0x71F718D3e4d1449D1502A6A7595eb84eBcCB1683 on chain 146.
# DAO sets delegation for a non-reachable address
>>> L2veCRVDelegation.delegate_from(146, '0x5802ad5D5B1c63b3FC7DE97B55e6db19e5d36462', '0x71F718D3e4d1449D1502A6A7595eb84eBcCB1683')
allow_delegation
L2veCRVDelegation.allow_delegation(_chain_id: uint256, _allow: bool = True)Allows or revokes permission for others to delegate veCRV to your address on a specific chain. This must be called before anyone can delegate to you, and is required to prevent frontrunning attacks.
Emits: AllowDelegation or Delegate event.
| Input | Type | Description |
|---|---|---|
_chain_id | uint256 | Chain ID |
_allow | bool | true to allow delegation, false to remove; defaults to true |
<>Source code▼
event AllowDelegation:
_chain_id: indexed(uint256)
_to: indexed(address)
event Delegate:
_chain_id: indexed(uint256)
_from: indexed(address)
_to: address
# [chain id][address from][address to]
delegation_from: HashMap[uint256, HashMap[address, address]]
delegation_to: HashMap[uint256, HashMap[address, address]]
@external
def allow_delegation(_chain_id: uint256, _allow: bool = True):
"""
@notice Allow delegation to your address
@dev Needed to deal with frontrun
@param _chain_id Chaind ID to allow for
@param _allow True(default) if allow, and False to remove delegation
"""
# Clean current delegation
_from: address = self.delegation_to[_chain_id][msg.sender]
if _from not in [empty(address), self]:
self.delegation_from[_chain_id][_from] = empty(address)
log Delegate(_chain_id, _from, empty(address))
if _allow:
self.delegation_to[_chain_id][msg.sender] = self
log AllowDelegation(_chain_id, msg.sender)
else:
self.delegation_to[_chain_id][msg.sender] = empty(address)
▶Example▼
This example shows how to allow and revoke delegation.
>>> L2veCRVDelegation.allow_delegation(146, True) # Allow delegation to your address on chain 146
>>> L2veCRVDelegation.allow_delegation(146, False) # Revoke delegation
delegation_allowed
L2veCRVDelegation.delegation_allowed(_chain_id: uint256, _to: address) -> bool: viewGetter method to check whether delegation to a given address is currently allowed on a specific chain.
Returns: true if delegation is allowed, false otherwise (bool).
| Input | Type | Description |
|---|---|---|
_chain_id | uint256 | Chain ID |
_to | address | Address to check for |
<>Source code▼
delegation_to: HashMap[uint256, HashMap[address, address]]
@external
@view
def delegation_allowed(_chain_id: uint256, _to: address) -> bool:
"""
@notice Check whether delegation to this address is allowed
@param _chain_id Chain ID to check for
@param _to Address to check for
@return True if allowed to delegate
"""
return self.delegation_to[_chain_id][_to] == self
▶Example▼
This example checks if delegation for a given address is allowed on a specific chain. Enter a chain ID and address, then click Query to fetch the value live from the blockchain.
delegated
L2veCRVDelegation.delegated(_chain_id: uint256, _from: address) -> address: viewReturns the address to which a given user's veCRV balance is delegated on a specific chain. If no delegation is set, returns the original address.
Returns: delegation destination (address).
| Input | Type | Description |
|---|---|---|
_chain_id | uint256 | Chain ID |
_from | address | Delegator |
<>Source code▼
delegation_from: HashMap[uint256, HashMap[address, address]]
@external
@view
def delegated(_chain_id: uint256, _from: address) -> address:
"""
@notice Get contract balance being delegated to
@param _chain_id Chain ID to check for
@param _from Address of delegator
@return Destination address of delegation
"""
addr: address = self.delegation_from[_chain_id][_from]
if addr == empty(address):
addr = _from
return addr
▶Example▼
This example returns the address to which a given user's veCRV balance is delegated on a specific chain. Enter a chain ID and delegator address, then click Query to fetch the value live from the blockchain.
delegator
L2veCRVDelegation.delegator(_chain_id: uint256, _to: address) -> address: viewGetter for the address that delegated their veCRV balance to a given address on a specific chain. If no delegator is set, returns the _to address itself.
Returns: delegator (address).
| Input | Type | Description |
|---|---|---|
_chain_id | uint256 | Chain ID to check for |
_to | address | Delegatee |
<>Source code▼
delegation_to: HashMap[uint256, HashMap[address, address]]
@external
@view
def delegator(_chain_id: uint256, _to: address) -> address:
"""
@notice Get contract delegating balance to `_to`
@param _chain_id Chain ID to check for
@param _to Address of delegated to
@return Address of delegator
"""
addr: address = self.delegation_to[_chain_id][_to]
if addr in [empty(address), self]:
return _to
return addr
▶Example▼
This example returns the address that delegated their veCRV balance to a given address on a specific chain. Enter a chain ID and delegatee address, then click Query to fetch the value live from the blockchain.
Contract Ownership
Ownership of the contract is managed using the ownable.vy module from 🐍 Snekmate which implements a basic access control mechanism, where there is an owner that can be granted exclusive access to specific functions.
owner
L2veCRVDelegation.owner() -> address: viewGetter for the owner of the contract. This is the address that can call restricted functions like transfer_ownership and delegate_from.
Returns: contract owner (address).
<>Source code▼
- L2veCRVDelegation.vy
- ownable.vy (Snekmate 🐍)
from snekmate.auth import ownable
initializes: ownable
exports: (
ownable.transfer_ownership,
ownable.owner,
)
@deploy
def __init__(_owner: address):
"""
@notice Contract constructor
@param _owner Owner address
"""
ownable.__init__()
ownable._transfer_ownership(_owner)
owner: public(address)
@deploy
@payable
def __init__():
"""
@dev To omit the opcodes for checking the `msg.value`
in the creation-time EVM bytecode, the constructor
is declared as `payable`.
@notice The `owner` role will be assigned to
the `msg.sender`.
"""
self._transfer_ownership(msg.sender)
▶Example▼
transfer_ownership
L2veCRVDelegation.transfer_ownership(new_owner: address)This contract makes use of a Snekmate module to manage roles and permissions. This specific function can only be called by the current owner of the contract.
Function to transfer the ownership of the contract to a new address.
Emits: OwnershipTransferred
| Input | Type | Description |
|---|---|---|
new_owner | address | New owner of the contract |
<>Source code▼
- L2veCRVDelegation.vy
- ownable.vy (Snekmate 🐍)
from snekmate.auth import ownable
initializes: ownable
exports: (
ownable.transfer_ownership,
ownable.owner,
)
@deploy
def __init__(_owner: address):
"""
@notice Contract constructor
@param _owner Owner address
"""
ownable.__init__()
ownable._transfer_ownership(_owner)
owner: public(address)
event OwnershipTransferred:
previous_owner: indexed(address)
new_owner: indexed(address)
@external
def transfer_ownership(new_owner: address):
"""
@dev Transfers the ownership of the contract
to a new account `new_owner`.
@notice Note that this function can only be
called by the current `owner`. Also,
the `new_owner` cannot be the zero address.
@param new_owner The 20-byte address of the new owner.
"""
self._check_owner()
assert new_owner != empty(address), "ownable: new owner is the zero address"
self._transfer_ownership(new_owner)
@internal
def _check_owner():
"""
@dev Throws if the sender is not the owner.
"""
assert msg.sender == self.owner, "ownable: caller is not the owner"
@internal
def _transfer_ownership(new_owner: address):
"""
@dev Transfers the ownership of the contract
to a new account `new_owner`.
@notice This is an `internal` function without
access restriction.
@param new_owner The 20-byte address of the new owner.
"""
old_owner: address = self.owner
self.owner = new_owner
log OwnershipTransferred(old_owner, new_owner)
▶Example▼
In this example, the ownership of the contract is transferred to a new address.
>>> L2veCRVDelegation.transfer_ownership("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045")
Other Methods
version
L2veCRVDelegation.version() -> String: viewGetter for the contract version.
Returns: contract version (String).
<>Source code▼
version: public(constant(String[8])) = "0.0.1"