Skip to content

Commit

Permalink
V2 autocompounder (#28)
Browse files Browse the repository at this point in the history
* 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
ethzoomer authored Sep 25, 2023
1 parent dfae901 commit 85f5950
Show file tree
Hide file tree
Showing 4 changed files with 260 additions and 0 deletions.
193 changes: 193 additions & 0 deletions contracts/RelaySugar.vy
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
})
2 changes: 2 additions & 0 deletions env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ DIST_ADDRESS=0x9D4736EC60715e71aFe72973f7885DCBC21EA99b
CONVERTOR_ADDRESS=0x585Af0b397AC42dbeF7f18395426BF878634f18D
LP_SUGAR_ADDRESS=0xD2B1D1B75a0f226722b3A174dAE54e6dD14af1a1
VE_SUGAR_ADDRESS=0x0eCc2593E3a6A9be3628940Fa4D928CC257B588B
RELAY_SUGAR_ADDRESS=0x7f609cf1a99318652859aED5B00C7F5F187E0077
RELAY_REGISTRY_ADDRESS=0xBC3dc970f891ffdd3049FA3a649985CC6626d486
29 changes: 29 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,35 @@ The available methods are:
* `byId(_id: uint256) -> VeNFT` - returns the `VeNFT` struct for a specific
NFT id.

### Relay Data

`RelaySugar.vy` is deployed at `0x7f609cf1a99318652859aED5B00C7F5F187E0077`

It allows fetching Relay autocompounder/autoconverter data.
The returned data/struct of type `Relay` values represent:

* `venft_id` - token ID of the Relay veNFT
* `decimals` - Relay veNFT token decimals
* `amount` - Relay veNFT locked amount
* `voting_amount` - Relay veNFT voting power
* `voted_at` - Relay veNFT last vote timestamp
* `votes` - Relay veNFT list of pools with vote weights casted in the form of
`LpVotes`
* `token` - token address the Relay is compounding into
* `compounded` - amount of tokens compounded into in the recent epoch
* `run_at` - timestamp of last compounding
* `manager` - Relay manager
* `relay` - Relay address
* `inactive` - Relay active/inactive status
* `name` - Relay name
* `account_venft_ids` - token IDs of the account's deposits into this Relay

---

The available methods are:

* `all(_account: address) -> Relay[]` - returns a list of all `Relay` structs.

## Development

To setup the environment, build the Docker image first:
Expand Down
36 changes: 36 additions & 0 deletions tests/test_relay_sugar.py
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

0 comments on commit 85f5950

Please sign in to comment.