Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

veCRV balance oracle for boosting on L2 #1

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
10 changes: 10 additions & 0 deletions contracts/blockhash/OptimismBlockHashOracle.vy
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@ def get_block_hash(_number: uint256) -> bytes32:
return block_hash


@view
@external
def get_state_root(_number: uint256) -> bytes32:
"""
@notice Query the state root hash of a block.
@dev Reverts for block numbers which have yet to be set.
"""
raise "Not implemented"


@internal
def _update_block_hash() -> (uint256, bytes32):
number: uint256 = convert(staticcall L1_BLOCK.number(), uint256)
Expand Down
31 changes: 31 additions & 0 deletions contracts/vecrv/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
## About
Gauges allow users to [boost rewards](https://resources.curve.fi/reward-gauges/boosting-your-crv-rewards/) using veCRV.
This project extends the same mechanism to other networks, enabling cross-chain functionality for veCRV.

## UX
### Update balance
Users must first update their balance on the desired Layer-2 (L2) network.
This is achieved by providing an Ethereum State Proof based on the available block hash or state root.
Detailed instructions for this process are available in the [`scripts`](../../scripts/vecrv) directory.
Since the nature of blockhash feeds varies across networks, regular updates are essential to maintain accurate data.
The architecture ensures these updates occur automatically, with a maximum frequency of once per week,
reducing manual effort.

### Delegate veCRV
Certain Ethereum addresses may not be accessible from other networks (e.g., [yCRV](https://etherscan.io/address/0x52f541764E6e90eeBc5c21Ff570De0e2D63766B6)).
To work around this limitation, you can delegate your veCRV balance to a new address.

#### **Delegation Steps**
1. Call `.allow_delegation(CHAIN_ID)` from your target contract to enable delegation for the desired chain.
2. From the address holding veCRV, call `.delegate(CHAIN_ID, target)` to delegate your balance to the target address.
3. Verify the delegation on the destination chain (one whose `CHAIN_ID` you used above) with the provided [script](../../scripts/vecrv).

#### **Important Notes**
- Delegations are managed via the **VecrvDelegate Contract** ([`VecrvDelegate.vy`](VecrvDelegate.vy)) on Ethereum mainnet, which acts as a centralized entity for delegations.
- Delegations are **one-to-one mappings**; positions cannot be merged across multiple addresses.
- If calls cannot be made from specific addresses, it is possible to initiate a DAO vote to configure delegation.

### Gauges
Here are a few technical details to consider:
- Certain gauges may require a `.update_voting_escrow()` call to retrieve the latest contract information.
- Your working balance is influenced by the gauge’s total supply. To ensure optimal rewards, it is recommended to call `.user_checkpoint(addr)` after new deposits.
143 changes: 143 additions & 0 deletions contracts/vecrv/VecrvDelegate.vy
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# pragma version 0.4.0
"""
@title L2 veCRV delegation
@notice Handles veCRV delegation to use new addresses on other networks
@license MIT
@author curve.fi
@custom:version 0.0.1
@custom:security [email protected]
"""

from snekmate.auth import ownable

initializes: ownable
exports: (
ownable.transfer_ownership,
ownable.owner,
)

event AllowDelegation:
_chain_id: indexed(uint256)
_to: indexed(address)

event Delegate:
_chain_id: indexed(uint256)
_from: indexed(address)
_to: address


# maps chain and source to delegation target
# [chain id][address from][address to]
delegation_from: HashMap[uint256, HashMap[address, address]]

# maps chain and target to delegation source
# [chain id][address to][address from]
delegation_to: HashMap[uint256, HashMap[address, address]]

version: public(constant(String[8])) = "0.0.1"


@deploy
def __init__():
ownable.__init__()


@external
@view
def delegation_target(_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


@external
@view
def delegation_source(_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


@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


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)

# Update delegation
self.delegation_from[_chain_id][_from] = _to
self.delegation_to[_chain_id][_to] = _from
log Delegate(_chain_id, _from, _to)


@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)


@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))

# Update delegation
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)


@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)
Loading