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.vy
The source code for the L2veCRVDelegation.vy
contract is available on GitHub. The contract is written in Vyper version 0.4.0
.
The VotingEscrow
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)
delegate_from
¶
L2veCRVDelegation.delegate_from(_chain_id: uint256, _from: address, _to: address)
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
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)
In this example, the DAO delegates the veCRV balance from 0x5802ad5D5B1c63b3FC7DE97B55e6db19e5d36462
to 0x71F718D3e4d1449D1502A6A7595eb84eBcCB1683
on chain 146
.
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)
delegation_allowed
¶
L2veCRVDelegation.delegation_allowed(_chain_id: uint256, _to: address) -> bool
Getter method to check whether delegation to a given address is currently allowed on a specific chain.
Returns: true
if delegation is allowed, false
otherwise.
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
delegated
¶
L2veCRVDelegation.delegated(_chain_id: uint256, _from: address) -> address
Returns 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.
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
delegator
¶
L2veCRVDelegation.delegator(_chain_id: uint256, _to: address) -> address
Getter 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 where to set the delegation |
_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
Contract Ownership¶
Contract ownership is handled via the snekmate ownable
module. The owner
of the contract is the DAO, and ownership can be transferred using the transfer_ownership()
function.