-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add RelaySugar * Account deposits() function, add inactive field * Relay struct flattened (#20) * Relay struct flattened * Add account veNFT ids to Relay struct * Remove VeSugar dependency * Remove Deposit struct and function * Vyper range bound check * byId() -> byAddress() * feat: use RelayFactory, add latest epoch compounded rewards (#22) * feat: use RelayFactory, add latest epoch compounded rewards * refactor: rename rewards_compounded * refactor: fetch Relays from Registry (#24) * refactor: fetch all Relays from Registry's factories (#26) * feat: add run_at to Relay struct output (#27) * Add Relay to readme * Add Relay tests
- Loading branch information
Showing
4 changed files
with
260 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
# SPDX-License-Identifier: BUSL-1.1 | ||
# @version >=0.3.6 <0.4.0 | ||
|
||
# @title Velodrome Finance Relay Sugar v2 | ||
# @author stas, ZoomerAnon | ||
# @notice Makes it nicer to work with Relay. | ||
|
||
MAX_RELAYS: constant(uint256) = 50 | ||
MAX_RESULTS: constant(uint256) = 1000 | ||
MAX_PAIRS: constant(uint256) = 30 | ||
WEEK: constant(uint256) = 7 * 24 * 60 * 60 | ||
|
||
struct LpVotes: | ||
lp: address | ||
weight: uint256 | ||
|
||
struct Relay: | ||
venft_id: uint256 | ||
decimals: uint8 | ||
amount: uint128 | ||
voting_amount: uint256 | ||
voted_at: uint256 | ||
votes: DynArray[LpVotes, MAX_PAIRS] | ||
token: address | ||
compounded: uint256 | ||
run_at: uint256 | ||
manager: address | ||
relay: address | ||
inactive: bool | ||
name: String[100] | ||
account_venft_ids: DynArray[uint256, MAX_RESULTS] | ||
|
||
interface IVoter: | ||
def ve() -> address: view | ||
def lastVoted(_venft_id: uint256) -> uint256: view | ||
def poolVote(_venft_id: uint256, _index: uint256) -> address: view | ||
def votes(_venft_id: uint256, _lp: address) -> uint256: view | ||
def usedWeights(_venft_id: uint256) -> uint256: view | ||
|
||
interface IVotingEscrow: | ||
def idToManaged(_venft_id: uint256) -> uint256: view | ||
def deactivated(_venft_id: uint256) -> bool: view | ||
def token() -> address: view | ||
def decimals() -> uint8: view | ||
def ownerOf(_venft_id: uint256) -> address: view | ||
def balanceOfNFT(_venft_id: uint256) -> uint256: view | ||
def locked(_venft_id: uint256) -> (uint128, uint256, bool): view | ||
def ownerToNFTokenIdList(_account: address, _index: uint256) -> uint256: view | ||
def voted(_venft_id: uint256) -> bool: view | ||
|
||
interface IRelayRegistry: | ||
def getAll() -> DynArray[address, MAX_RELAYS]: view | ||
|
||
interface IRelayFactory: | ||
def relays() -> DynArray[address, MAX_RELAYS]: view | ||
|
||
interface IRelay: | ||
def name() -> String[100]: view | ||
def mTokenId() -> uint256: view | ||
def token() -> address: view | ||
def keeperLastRun() -> uint256: view | ||
# Latest epoch rewards | ||
def amountTokenEarned(_epoch_ts: uint256) -> uint256: view | ||
|
||
# Vars | ||
registry: public(IRelayRegistry) | ||
voter: public(IVoter) | ||
ve: public(IVotingEscrow) | ||
token: public(address) | ||
|
||
@external | ||
def __init__(_registry: address, _voter: address): | ||
""" | ||
@dev Set up our external registry and voter contracts | ||
""" | ||
self.registry = IRelayRegistry(_registry) | ||
self.voter = IVoter(_voter) | ||
self.ve = IVotingEscrow(self.voter.ve()) | ||
self.token = self.ve.token() | ||
|
||
@external | ||
@view | ||
def all(_account: address) -> DynArray[Relay, MAX_RELAYS]: | ||
""" | ||
@notice Returns all Relays and account's deposits | ||
@return Array of Relay structs | ||
""" | ||
return self._relays(_account) | ||
|
||
@internal | ||
@view | ||
def _relays(_account: address) -> DynArray[Relay, MAX_RELAYS]: | ||
""" | ||
@notice Returns all Relays and account's deposits | ||
@return Array of Relay structs | ||
""" | ||
relays: DynArray[Relay, MAX_RELAYS] = empty(DynArray[Relay, MAX_RELAYS]) | ||
factories: DynArray[address, MAX_RELAYS] = self.registry.getAll() | ||
|
||
for factory_index in range(0, MAX_RELAYS): | ||
if factory_index == len(factories): | ||
break | ||
|
||
relay_factory: IRelayFactory = IRelayFactory(factories[factory_index]) | ||
addresses: DynArray[address, MAX_RELAYS] = relay_factory.relays() | ||
|
||
for index in range(0, MAX_RELAYS): | ||
if index == len(addresses): | ||
break | ||
|
||
relay: Relay = self._byAddress(addresses[index], _account) | ||
relays.append(relay) | ||
|
||
return relays | ||
|
||
@internal | ||
@view | ||
def _byAddress(_relay: address, _account: address) -> Relay: | ||
""" | ||
@notice Returns Relay data based on address, with optional account arg | ||
@param _relay The Relay address to lookup | ||
@param _account The account address to lookup deposits | ||
@return Relay struct | ||
""" | ||
|
||
relay: IRelay = IRelay(_relay) | ||
managed_id: uint256 = relay.mTokenId() | ||
|
||
account_venft_ids: DynArray[uint256, MAX_RESULTS] = empty(DynArray[uint256, MAX_RESULTS]) | ||
|
||
for venft_index in range(MAX_RESULTS): | ||
account_venft_id: uint256 = self.ve.ownerToNFTokenIdList(_account, venft_index) | ||
|
||
if account_venft_id == 0: | ||
break | ||
|
||
account_venft_manager_id: uint256 = self.ve.idToManaged(account_venft_id) | ||
if account_venft_manager_id == managed_id: | ||
account_venft_ids.append(account_venft_id) | ||
|
||
votes: DynArray[LpVotes, MAX_PAIRS] = [] | ||
amount: uint128 = self.ve.locked(managed_id)[0] | ||
last_voted: uint256 = 0 | ||
manager: address = self.ve.ownerOf(managed_id) | ||
inactive: bool = self.ve.deactivated(managed_id) | ||
|
||
epoch_start_ts: uint256 = block.timestamp / WEEK * WEEK | ||
|
||
# Rewards claimed this epoch | ||
rewards_compounded: uint256 = relay.amountTokenEarned(epoch_start_ts) | ||
|
||
if self.ve.voted(managed_id): | ||
last_voted = self.voter.lastVoted(managed_id) | ||
|
||
vote_weight: uint256 = self.voter.usedWeights(managed_id) | ||
# Since we don't have a way to see how many pools the veNFT voted... | ||
left_weight: uint256 = vote_weight | ||
|
||
for index in range(MAX_PAIRS): | ||
if left_weight == 0: | ||
break | ||
|
||
lp: address = self.voter.poolVote(managed_id, index) | ||
|
||
if lp == empty(address): | ||
break | ||
|
||
weight: uint256 = self.voter.votes(managed_id, lp) | ||
|
||
votes.append(LpVotes({ | ||
lp: lp, | ||
weight: weight | ||
})) | ||
|
||
# Remove _counted_ weight to see if there are other pool votes left... | ||
left_weight -= weight | ||
|
||
return Relay({ | ||
venft_id: managed_id, | ||
decimals: self.ve.decimals(), | ||
amount: amount, | ||
voting_amount: self.ve.balanceOfNFT(managed_id), | ||
voted_at: last_voted, | ||
votes: votes, | ||
token: relay.token(), | ||
compounded: rewards_compounded, | ||
run_at: relay.keeperLastRun(), | ||
manager: manager, | ||
relay: _relay, | ||
inactive: inactive, | ||
name: relay.name(), | ||
account_venft_ids: account_venft_ids | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
# SPDX-License-Identifier: BUSL-1.1 | ||
import os | ||
import pytest | ||
|
||
from collections import namedtuple | ||
from web3.constants import ADDRESS_ZERO | ||
|
||
|
||
@pytest.fixture | ||
def sugar_contract(RelaySugar, accounts): | ||
# Since we depend on the rest of the protocol, | ||
# we just point to an existing deployment | ||
yield RelaySugar.at(os.getenv('RELAY_SUGAR_ADDRESS')) | ||
|
||
|
||
@pytest.fixture | ||
def RelayStruct(sugar_contract): | ||
method_output = sugar_contract.all.abi['outputs'][0] | ||
members = list(map(lambda _e: _e['name'], method_output['components'])) | ||
|
||
yield namedtuple('RelayStruct', members) | ||
|
||
|
||
def test_initial_state(sugar_contract): | ||
assert sugar_contract.voter() == os.getenv('VOTER_ADDRESS') | ||
assert sugar_contract.registry() == \ | ||
os.getenv('RELAY_REGISTRY_ADDRESS') | ||
|
||
|
||
def test_all(sugar_contract, RelayStruct): | ||
relays = list(map( | ||
lambda _r: RelayStruct(*_r), | ||
sugar_contract.all(ADDRESS_ZERO) | ||
)) | ||
|
||
assert relays is not None |