diff --git a/Dockerfile b/Dockerfile index 9ab15ca..0343570 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,8 @@ RUN npm install -g ganache COPY . /app WORKDIR /app -RUN pip install --no-cache-dir -r requirements.txt +RUN pip install "cython<3.0.0" && pip install --no-build-isolation pyyaml==5.4.1 +RUN pip install -r requirements.txt RUN brownie networks modify optimism-test host=https://goerli.optimism.io RUN brownie networks modify optimism-main host=https://optimism-mainnet.wallet.coinbase.com diff --git a/contracts/RelaySugar.vy b/contracts/RelaySugar.vy index 294679f..1f37c5a 100644 --- a/contracts/RelaySugar.vy +++ b/contracts/RelaySugar.vy @@ -8,17 +8,24 @@ MAX_RELAYS: constant(uint256) = 50 MAX_RESULTS: constant(uint256) = 1000 MAX_PAIRS: constant(uint256) = 30 +MAX_REGISTRIES: constant(uint256) = 20 WEEK: constant(uint256) = 7 * 24 * 60 * 60 struct LpVotes: lp: address weight: uint256 +struct ManagedVenft: + id: uint256 + amount: uint256 + earned: uint256 + struct Relay: venft_id: uint256 decimals: uint8 amount: uint128 voting_amount: uint256 + used_voting_amount: uint256 voted_at: uint256 votes: DynArray[LpVotes, MAX_PAIRS] token: address @@ -29,7 +36,7 @@ struct Relay: relay: address inactive: bool name: String[100] - account_venft_ids: DynArray[uint256, MAX_RESULTS] + account_venfts: DynArray[ManagedVenft, MAX_RESULTS] interface IERC20: @@ -52,6 +59,11 @@ interface IVotingEscrow: def locked(_venft_id: uint256) -> (uint128, uint256, bool): view def ownerToNFTokenIdList(_account: address, _index: uint256) -> uint256: view def voted(_venft_id: uint256) -> bool: view + def managedToLocked(_managed_venft_id: uint256) -> address: view + def weights(_venft_id: uint256, _managed_venft_id: uint256) -> uint256: view + +interface IReward: + def earned(_token: address, _venft_id: uint256) -> uint256: view interface IRelayRegistry: def getAll() -> DynArray[address, MAX_RELAYS]: view @@ -70,17 +82,17 @@ interface IRelay: def getRoleMember(_role: bytes32, _index: uint256) -> address: view # Vars -registry: public(IRelayRegistry) +registries: public(DynArray[address, MAX_REGISTRIES]) voter: public(IVoter) ve: public(IVotingEscrow) token: public(address) @external -def __init__(_registry: address, _voter: address): +def __init__(_registries: DynArray[address, MAX_REGISTRIES], _voter: address): """ @dev Set up our external registry and voter contracts """ - self.registry = IRelayRegistry(_registry) + self.registries = _registries self.voter = IVoter(_voter) self.ve = IVotingEscrow(self.voter.ve()) self.token = self.ve.token() @@ -102,21 +114,26 @@ def _relays(_account: address) -> DynArray[Relay, MAX_RELAYS]: @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): + for registry_index in range(0, MAX_REGISTRIES): + if registry_index == len(self.registries): break + + relay_registry: IRelayRegistry = IRelayRegistry(self.registries[registry_index]) + factories: DynArray[address, MAX_RELAYS] = relay_registry.getAll() - 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): + for factory_index in range(0, MAX_RELAYS): + if factory_index == len(factories): break - relay: Relay = self._byAddress(addresses[index], _account) - relays.append(relay) + 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 @@ -133,7 +150,7 @@ def _byAddress(_relay: address, _account: address) -> Relay: relay: IRelay = IRelay(_relay) managed_id: uint256 = relay.mTokenId() - account_venft_ids: DynArray[uint256, MAX_RESULTS] = empty(DynArray[uint256, MAX_RESULTS]) + account_venfts: DynArray[ManagedVenft, MAX_RESULTS] = empty(DynArray[ManagedVenft, MAX_RESULTS]) for venft_index in range(MAX_RESULTS): account_venft_id: uint256 = self.ve.ownerToNFTokenIdList(_account, venft_index) @@ -143,7 +160,15 @@ def _byAddress(_relay: address, _account: address) -> Relay: 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) + locked_reward: IReward = IReward(self.ve.managedToLocked(account_venft_manager_id)) + venft_weight: uint256 = self.ve.weights(account_venft_id, account_venft_manager_id) + earned: uint256 = locked_reward.earned(self.token, account_venft_id) + + account_venfts.append(ManagedVenft({ + id: account_venft_id, + amount: venft_weight, + earned: earned + })) votes: DynArray[LpVotes, MAX_PAIRS] = [] amount: uint128 = self.ve.locked(managed_id)[0] @@ -195,6 +220,7 @@ def _byAddress(_relay: address, _account: address) -> Relay: decimals: self.ve.decimals(), amount: amount, voting_amount: self.ve.balanceOfNFT(managed_id), + used_voting_amount: vote_weight, voted_at: last_voted, votes: votes, token: relay.token(), @@ -205,5 +231,5 @@ def _byAddress(_relay: address, _account: address) -> Relay: relay: _relay, inactive: inactive, name: relay.name(), - account_venft_ids: account_venft_ids + account_venfts: account_venfts }) diff --git a/contracts/VeSugar.vy b/contracts/VeSugar.vy index 3fc43e7..7347c19 100644 --- a/contracts/VeSugar.vy +++ b/contracts/VeSugar.vy @@ -20,12 +20,14 @@ struct VeNFT: decimals: uint8 amount: uint128 voting_amount: uint256 + governance_amount: uint256 rebase_amount: uint256 expires_at: uint256 voted_at: uint256 votes: DynArray[LpVotes, MAX_PAIRS] token: address permanent: bool + delegate_id: uint256 # Our contracts / Interfaces @@ -51,6 +53,11 @@ interface IVotingEscrow: def locked(_venft_id: uint256) -> (uint128, uint256, bool): view def ownerToNFTokenIdList(_account: address, _index: uint256) -> uint256: view def voted(_venft_id: uint256) -> bool: view + def delegates(_venft_id: uint256) -> uint256: view + def idToManaged(_venft_id: uint256) -> uint256: view + +interface IGovernor: + def getVotes(_venft_id: uint256, _timepoint: uint256) -> uint256: view # Vars @@ -58,11 +65,12 @@ voter: public(IVoter) token: public(address) ve: public(IVotingEscrow) dist: public(IRewardsDistributor) +gov: public(IGovernor) # Methods @external -def __init__(_voter: address, _rewards_distributor: address): +def __init__(_voter: address, _rewards_distributor: address, _gov: address): """ @dev Sets up our external contract addresses """ @@ -70,6 +78,7 @@ def __init__(_voter: address, _rewards_distributor: address): self.ve = IVotingEscrow(self.voter.ve()) self.token = self.ve.token() self.dist = IRewardsDistributor(_rewards_distributor) + self.gov = IGovernor(_gov) @external @view @@ -146,7 +155,12 @@ def _byId(_id: uint256) -> VeNFT: amount, expires_at, perma = self.ve.locked(_id) last_voted: uint256 = 0 - if self.ve.voted(_id): + governance_amount: uint256 = self.gov.getVotes(_id, block.timestamp) + + delegate_id: uint256 = self.ve.delegates(_id) + managed_id: uint256 = self.ve.idToManaged(_id) + + if managed_id != 0 or self.ve.voted(_id): last_voted = self.voter.lastVoted(_id) vote_weight: uint256 = self.voter.usedWeights(_id) @@ -179,10 +193,12 @@ def _byId(_id: uint256) -> VeNFT: amount: amount, voting_amount: self.ve.balanceOfNFT(_id), + governance_amount: governance_amount, rebase_amount: self.dist.claimable(_id), expires_at: expires_at, voted_at: last_voted, votes: votes, token: self.token, - permanent: perma + permanent: perma, + delegate_id: delegate_id }) diff --git a/env.example b/env.example index cba4d85..c2451eb 100644 --- a/env.example +++ b/env.example @@ -3,6 +3,6 @@ REGISTRY_ADDRESS=0xF4c67CdEAaB8360370F41514d06e32CcD8aA1d7B DIST_ADDRESS=0x9D4736EC60715e71aFe72973f7885DCBC21EA99b CONVERTOR_ADDRESS=0x585Af0b397AC42dbeF7f18395426BF878634f18D LP_SUGAR_ADDRESS=0x6eDCAb198EAdDBDA3865f813A83F6bC9012F16e9 -VE_SUGAR_ADDRESS=0x0eCc2593E3a6A9be3628940Fa4D928CC257B588B -RELAY_SUGAR_ADDRESS=0x7f609cf1a99318652859aED5B00C7F5F187E0077 -RELAY_REGISTRY_ADDRESS=0xBC3dc970f891ffdd3049FA3a649985CC6626d486 +VE_SUGAR_ADDRESS=0x37403dBd6f1b583ea244F7956fF9e37EF45c63eB +RELAY_SUGAR_ADDRESS=0x062185EEF2726EFc11880856CD356FA2Ac2B38Ff +RELAY_REGISTRY_ADDRESS=0xe9F00f2e61CB0c6fb00A2e457546aCbF0fC303C2 diff --git a/readme.md b/readme.md index 4dd3273..0e6d1c4 100644 --- a/readme.md +++ b/readme.md @@ -145,7 +145,7 @@ To fetch a list of rewards for a specific veNFT, this method is available: ### Vote-Escrow Locked NFT (veNFT) Data -`VeSugar.vy` is deployed at `0x0eCc2593E3a6A9be3628940Fa4D928CC257B588B` +`VeSugar.vy` is deployed at `0x37403dBd6f1b583ea244F7956fF9e37EF45c63eB` It allows fetching on-chain veNFT data (including the rewards accrued). The returned data/struct of type `VeNFT` values represent: @@ -155,6 +155,7 @@ The returned data/struct of type `VeNFT` values represent: * `decimals` - veNFT token decimals * `amount` - veNFT locked amount * `voting_amount` - veNFT voting power + * `governance_amount` - veNFT voting power in governance * `rebase_amount` - veNFT accrued reabses amount * `expires_at` - veNFT lock expiration timestamp * `voted_at` - veNFT last vote timestamp @@ -162,6 +163,7 @@ The returned data/struct of type `VeNFT` values represent: `LpVotes` * `token` - veNFT locked token address * `permanent` - veNFT permanent lock enabled flag + * `delegate_id` - token ID of the veNFT being delegated to The pool votes struct values represent: * `lp` - the pool address @@ -180,7 +182,7 @@ The available methods are: ### Relay Data -`RelaySugar.vy` is deployed at `0xeBf8F5818D429785A584693599b695AFc3BeE3c6` +`RelaySugar.vy` is deployed at `0x062185EEF2726EFc11880856CD356FA2Ac2B38Ff` It allows fetching Relay autocompounder/autoconverter data. The returned data/struct of type `Relay` values represent: @@ -189,6 +191,7 @@ The returned data/struct of type `Relay` values represent: * `decimals` - Relay veNFT token decimals * `amount` - Relay veNFT locked amount * `voting_amount` - Relay veNFT voting power + * `used_voting_amount` - Relay veNFT voting power used for last vote * `voted_at` - Relay veNFT last vote timestamp * `votes` - Relay veNFT list of pools with vote weights casted in the form of `LpVotes` @@ -199,7 +202,12 @@ The returned data/struct of type `Relay` values represent: * `relay` - Relay address * `inactive` - Relay active/inactive status * `name` - Relay name - * `account_venft_ids` - token IDs of the account's deposits into this Relay + * `account_venfts` - List of veNFTs deposited into this Relay by the account in the form of `ManagedVenft` + +The managed veNFT deposit struct values represent: + * `id` - the token ID of the veNFT + * `amount` - the weight of the veNFT + * `earned` - earned emissions of the veNFT --- diff --git a/tests/test_relay_sugar.py b/tests/test_relay_sugar.py index b7479e0..38815c8 100644 --- a/tests/test_relay_sugar.py +++ b/tests/test_relay_sugar.py @@ -23,8 +23,9 @@ def RelayStruct(sugar_contract): def test_initial_state(sugar_contract): assert sugar_contract.voter() == os.getenv('VOTER_ADDRESS') - assert sugar_contract.registry() == \ - os.getenv('RELAY_REGISTRY_ADDRESS') + assert sugar_contract.registries(0) == os.getenv('RELAY_REGISTRY_ADDRESS') + assert sugar_contract.ve() is not None + assert sugar_contract.token() is not None def test_all(sugar_contract, RelayStruct): @@ -33,4 +34,4 @@ def test_all(sugar_contract, RelayStruct): sugar_contract.all(ADDRESS_ZERO) )) - assert relays is not None + assert len(relays) > 5 diff --git a/tests/test_ve_sugar.py b/tests/test_ve_sugar.py index 301a9e3..77fc40d 100644 --- a/tests/test_ve_sugar.py +++ b/tests/test_ve_sugar.py @@ -12,14 +12,6 @@ def sugar_contract(VeSugar, accounts): yield VeSugar.at(os.getenv('VE_SUGAR_ADDRESS')) -@pytest.fixture -def RewardStruct(sugar_contract): - method_output = sugar_contract.rewards.abi['outputs'][0] - members = list(map(lambda _e: _e['name'], method_output['components'])) - - yield namedtuple('RewardStruct', members) - - @pytest.fixture def VeNFTStruct(sugar_contract): method_output = sugar_contract.byId.abi['outputs'][0] @@ -30,8 +22,8 @@ def VeNFTStruct(sugar_contract): def test_initial_state(sugar_contract): assert sugar_contract.voter() == os.getenv('VOTER_ADDRESS') - assert sugar_contract.rewards_distributor() == \ - os.getenv('REWARDS_DIST_ADDRESS') + assert sugar_contract.dist() == \ + os.getenv('DIST_ADDRESS') assert sugar_contract.ve() is not None @@ -39,11 +31,10 @@ def test_byId(sugar_contract, VeNFTStruct): venft = VeNFTStruct(*sugar_contract.byId(1)) assert venft is not None - assert len(venft) == 11 + assert len(venft) == 13 assert venft.id is not None assert len(venft.votes) > 0 assert venft.voted_at > 0 - assert venft.attachments > 0 def test_byAccount(sugar_contract, VeNFTStruct): @@ -54,7 +45,7 @@ def test_byAccount(sugar_contract, VeNFTStruct): )) assert venft is not None - assert len(venft) == 11 + assert len(venft) == 13 assert venft.account == acc_venft[0].account @@ -92,12 +83,3 @@ def test_all_limit_offset(sugar_contract, VeNFTStruct): assert venft1.id == second_venft.id assert venft1.account == second_venft.account - - -def test_rewards(sugar_contract, RewardStruct): - rewards = list(map( - lambda _r: RewardStruct(*_r), - sugar_contract.rewards(100, 0, 1) - )) - - assert len(rewards) > 0