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

V2 autocompounder #28

Merged
merged 9 commits into from
Sep 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading