diff --git a/eth_defi/lagoon/vault.py b/eth_defi/lagoon/vault.py index 855ad484..ac1170b1 100644 --- a/eth_defi/lagoon/vault.py +++ b/eth_defi/lagoon/vault.py @@ -1,6 +1,7 @@ """Vault adapter for Lagoon protocol.""" from dataclasses import asdict +from decimal import Decimal from functools import cached_property from eth_typing import HexAddress, BlockIdentifier, ChecksumAddress @@ -83,6 +84,14 @@ def fetch_safe(self, address) -> Safe: client, ) + @property + def name(self) -> str: + return self.share_token.name + + @property + def symbol(self) -> str: + return self.share_token.symbol + @cached_property def vault_contract(self) -> Contract: """Get vault deployment.""" @@ -112,6 +121,10 @@ def fetch_denomination_token(self) -> TokenDetails: token_address = self.info["asset"] return fetch_erc20_details(self.web3, token_address, chain_id=self.spec.chain_id) + def fetch_share_token(self) -> TokenDetails: + token_address = self.info["address"] + return fetch_erc20_details(self.web3, token_address, chain_id=self.spec.chain_id) + def fetch_info(self) -> LagoonVaultInfo: """Use :py:meth:`info` property for cached access""" vault_info = self.fetch_vault_info() @@ -143,12 +156,8 @@ def safe_contract(self) -> Contract: return self.safe.contract @property - def name(self) -> str: - return self.info["name"] - - @property - def token_symbol(self) -> str: - return self.info["symbol"] + def valuation_manager(self) -> HexAddress: + return self.info["valuationManager"] def fetch_portfolio( self, @@ -215,15 +224,19 @@ def transact_through_module( def post_valuation_commitee( self, - portfolio: VaultPortfolio, + total_valuation: Decimal, ): """Update the valuations of this vault. - Lagoon vault does not currently track individual positions, but takes a "total value" number - Updating this number also allows deposits and redemptions to proceed + + :param total_valuation: + The vault value nominated in :py:meth:`denomination_token`. """ - raise NotImplementedError() + + diff --git a/eth_defi/vault/base.py b/eth_defi/vault/base.py index 544a2ac4..2fc44c28 100644 --- a/eth_defi/vault/base.py +++ b/eth_defi/vault/base.py @@ -1,6 +1,6 @@ """Generic Vault interface base classes""" -from abc import ABC, abstractmethod +from abc import ABC, abstractmethod, abstractproperty from dataclasses import dataclass from decimal import Decimal from functools import cached_property @@ -173,12 +173,25 @@ class VaultBase(ABC): - [ ] read vault core info - [ ] read vault investors - [ ] read vault share price + - [ ] read vault share token - [ ] deposit integration test - [ ] redemption integration - [ ] swap integration test - [ ] re-valuation integration test """ + @property + @abstractmethod + def name(self) -> str: + """Vault name.""" + pass + + @property + @abstractmethod + def symbol(self) -> str: + """Vault share token symbol""" + pass + @abstractmethod def has_block_range_event_support(self) -> bool: """Can we query delta changes by block ranges.""" @@ -213,9 +226,18 @@ def fetch_denomination_token(self) -> TokenDetails: """Use :py:method:`denomination_token` to access""" @cached_property - def denomination_token(self): + def denomination_token(self) -> TokenDetails: return self.fetch_denomination_token() + @abstractmethod + def fetch_share_token(self) -> TokenDetails: + """Use :py:method:`share_token` to access""" + + @cached_property + def share_token(self) -> TokenDetails: + """ERC-20 that presents vault shares.""" + return self.fetch_share_token() + @cached_property def info(self) -> VaultInfo: """Get info dictionary related to this deployment.""" diff --git a/tests/lagoon/test_lagoon_info.py b/tests/lagoon/test_lagoon_info.py index fbde3172..1d768087 100644 --- a/tests/lagoon/test_lagoon_info.py +++ b/tests/lagoon/test_lagoon_info.py @@ -1,6 +1,6 @@ """Lagoon Base mainnet fork based tests. -- View Safe here https://app.safe.global/home?safe=base:0x20415f3Ec0FEA974548184bdD6e67575D128953F +- Read various information out of the vault """ from decimal import Decimal @@ -27,6 +27,7 @@ def test_lagoon_info(read_only_vault: LagoonVault): info = vault.fetch_info() assert info["address"].lower() == "0xab4ac28d10a4bc279ad073b1d74bfa0e385c010c" assert info["safe"] == "0x20415f3Ec0FEA974548184bdD6e67575D128953F" + assert info["valuationManager"] == "0x20415f3Ec0FEA974548184bdD6e67575D128953F" # Hotkey, unlocked for tests assert len(info["owners"]) == 2 @@ -44,10 +45,13 @@ def test_lagoon_safe(read_only_vault: LagoonVault): assert safe.retrieve_modules() == ['0x0b2582E9Bf6AcE4E7f42883d4E91240551cf0947', '0x0Cdee1aCD67a424E476AD97bC60aa5F35D2556c9'] -def test_lagoon_denomination_token(read_only_vault: LagoonVault): +def test_lagoon_tokens(read_only_vault: LagoonVault): """We are denominated in the USDC""" vault = read_only_vault assert vault.denomination_token.symbol == "USDC" + assert vault.share_token.symbol == "XMPL" + assert vault.name == "Example" + assert vault.symbol == "XMPL" def test_lagoon_fetch_portfolio( diff --git a/tests/lagoon/test_lagoon_valuation.py b/tests/lagoon/test_lagoon_valuation.py index 8acd7675..a98b57e0 100644 --- a/tests/lagoon/test_lagoon_valuation.py +++ b/tests/lagoon/test_lagoon_valuation.py @@ -155,12 +155,12 @@ def test_lagoon_calculate_portfolio_nav( def test_lagoon_diagnose_routes( - web3: Web3, - lagoon_vault: LagoonVault, - base_usdc: TokenDetails, - base_weth: TokenDetails, - base_dino: TokenDetails, - uniswap_v2: UniswapV2Deployment, + web3: Web3, + lagoon_vault: LagoonVault, + base_usdc: TokenDetails, + base_weth: TokenDetails, + base_dino: TokenDetails, + uniswap_v2: UniswapV2Deployment, ): """Run route diagnostics. """ @@ -196,3 +196,45 @@ def test_lagoon_diagnose_routes( assert routes.loc["WETH -> USDC"]["Value"] is not None assert routes.loc["DINO -> WETH -> USDC"]["Value"] is not None assert routes.loc["DINO -> USDC"]["Value"] == "-" + + +def test_lagoon_post_valuation_commitee( + web3: Web3, + lagoon_vault: LagoonVault, + base_usdc: TokenDetails, + base_weth: TokenDetails, + base_dino: TokenDetails, + uniswap_v2: UniswapV2Deployment, +): + """Update vault NAV.""" + + vault = lagoon_vault + + universe = TradingUniverse( + spot_token_addresses={ + base_weth.address, + base_usdc.address, + base_dino.address, + } + ) + latest_block = get_almost_latest_block_number(web3) + portfolio = vault.fetch_portfolio(universe, latest_block) + assert portfolio.get_position_count() == 3 + + uniswap_v2_quoter_v2 = UniswapV2Router02Quoter(uniswap_v2.router) + + nav_calculator = NetAssetValueCalculator( + web3, + denomination_token=base_usdc, + intermediary_tokens={base_weth.address}, # Allow DINO->WETH->USDC + quoters={uniswap_v2_quoter_v2}, + debug=True, + ) + + portfolio_valuation = nav_calculator.calculate_market_sell_nav(portfolio) + + total_value = portfolio_valuation.get_total_equity() + tx_data = vault.post_valuation_commitee(total_value) + + +