diff --git a/eth_defi/lagoon/vault.py b/eth_defi/lagoon/vault.py index ac1170b1..98cba3fe 100644 --- a/eth_defi/lagoon/vault.py +++ b/eth_defi/lagoon/vault.py @@ -1,5 +1,6 @@ """Vault adapter for Lagoon protocol.""" +import logging from dataclasses import asdict from decimal import Decimal from functools import cached_property @@ -19,6 +20,9 @@ from ..token import TokenDetails, fetch_erc20_details +logger = logging.getLogger(__name__) + + class LagoonVaultInfo(VaultInfo): """TODO: Add Lagoon vault info query""" @@ -101,6 +105,15 @@ def vault_contract(self) -> Contract: self.spec.vault_address, ) + @cached_property + def vault_contract(self) -> Contract: + """Get vault deployment.""" + return get_deployed_contract( + self.web3, + "lagoon/Vault.json", + self.spec.vault_address, + ) + def fetch_vault_info(self) -> dict: """Get all information we can extract from the vault smart contracts.""" vault = self.vault_contract @@ -222,19 +235,32 @@ def transact_through_module( ) return bound_func - def post_valuation_commitee( + def post_new_valuation( self, total_valuation: Decimal, - ): + ) -> ContractFunction: """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 + Notes: + + > How can I post a valuation commitee update 1. as the valuationManager, call the function updateNewTotalAssets(_newTotalAssets) _newTotalAssets being expressed in underlying in its smallest unit for usdc, it would with its 6 decimals. Do not take into account requestDeposit and requestRedeem in your valuation + + > 2. as the safe, call the function settleDeposit() + :param total_valuation: The vault value nominated in :py:meth:`denomination_token`. + + :return: + Bound contract function that can be turned to a transaction """ + logger.info("Updating vault %s valuation to %s %s", self.address, total_valuation, self.denomination_token.symbol) + raw_amount = self.denomination_token.convert_to_raw(total_valuation) + bound_func = self.vault_contract.functions.updateNewTotalAssets(raw_amount) + return bound_func diff --git a/eth_defi/vault/base.py b/eth_defi/vault/base.py index 2fc44c28..dcec5248 100644 --- a/eth_defi/vault/base.py +++ b/eth_defi/vault/base.py @@ -174,10 +174,14 @@ class VaultBase(ABC): - [ ] read vault investors - [ ] read vault share price - [ ] read vault share token + - [ ] read all positions + - [ ] read NAV - [ ] deposit integration test - [ ] redemption integration - [ ] swap integration test - [ ] re-valuation integration test + - [ ] only asset manager allowed to swap negative test + - [ ] only valuation commitee allowed to update vault valuations (if applicable) """ @property @@ -225,6 +229,15 @@ def get_flow_manager(self) -> VaultFlowManager: def fetch_denomination_token(self) -> TokenDetails: """Use :py:method:`denomination_token` to access""" + @abstractmethod + def fetch_nav(self) -> Decimal: + """Fetch the most recent onchain NAV value. + + :return: + Vault NAV, denominated in :py:meth:`denomination_token` + """ + + @cached_property def denomination_token(self) -> TokenDetails: return self.fetch_denomination_token() diff --git a/tests/lagoon/conftest.py b/tests/lagoon/conftest.py index 06048c47..d887bc19 100644 --- a/tests/lagoon/conftest.py +++ b/tests/lagoon/conftest.py @@ -40,7 +40,13 @@ def usdc_holder() -> HexAddress: @pytest.fixture() -def anvil_base_fork(request, vault_owner, usdc_holder, asset_manager) -> AnvilLaunch: +def valuation_manager() -> HexAddress: + """Unlockable account set as the vault valuation manager.""" + return "0x8358bBFb4Afc9B1eBe4e8C93Db8bF0586BD8331a" + + +@pytest.fixture() +def anvil_base_fork(request, vault_owner, usdc_holder, asset_manager, valuation_manager) -> AnvilLaunch: """Create a testable fork of live BNB chain. :return: JSON-RPC URL for Web3 @@ -48,7 +54,7 @@ def anvil_base_fork(request, vault_owner, usdc_holder, asset_manager) -> AnvilLa assert JSON_RPC_BASE, "JSON_RPC_BASE not set" launch = fork_network_anvil( JSON_RPC_BASE, - unlocked_addresses=[vault_owner, usdc_holder, asset_manager], + unlocked_addresses=[vault_owner, usdc_holder, asset_manager, valuation_manager], ) try: yield launch @@ -164,6 +170,19 @@ def topped_up_asset_manager(web3, asset_manager): return asset_manager + +@pytest.fixture() +def topped_up_valuation_manager(web3, valuation_manager): + # Topped up with some ETH + tx_hash = web3.eth.send_transaction({ + "to": valuation_manager, + "from": web3.eth.accounts[0], + "value": 9 * 10**18, + }) + assert_transaction_success_with_explanation(web3, tx_hash) + return valuation_manager + + # Some addresses for the roles set: """ diff --git a/tests/lagoon/test_lagoon_info.py b/tests/lagoon/test_lagoon_info.py index 1d768087..07873d60 100644 --- a/tests/lagoon/test_lagoon_info.py +++ b/tests/lagoon/test_lagoon_info.py @@ -21,13 +21,13 @@ def read_only_vault(lagoon_vault) -> LagoonVault: return lagoon_vault -def test_lagoon_info(read_only_vault: LagoonVault): +def test_lagoon_core_info(read_only_vault: LagoonVault): """Get core info of Lagoon vault""" vault = read_only_vault info = vault.fetch_info() assert info["address"].lower() == "0xab4ac28d10a4bc279ad073b1d74bfa0e385c010c" assert info["safe"] == "0x20415f3Ec0FEA974548184bdD6e67575D128953F" - assert info["valuationManager"] == "0x20415f3Ec0FEA974548184bdD6e67575D128953F" # Hotkey, unlocked for tests + assert info["valuationManager"] == "0x8358bBFb4Afc9B1eBe4e8C93Db8bF0586BD8331a" # Hotkey, unlocked for tests assert len(info["owners"]) == 2 diff --git a/tests/lagoon/test_lagoon_valuation.py b/tests/lagoon/test_lagoon_valuation.py index a98b57e0..b389e8c8 100644 --- a/tests/lagoon/test_lagoon_valuation.py +++ b/tests/lagoon/test_lagoon_valuation.py @@ -3,13 +3,15 @@ from decimal import Decimal import pytest -from multicall import Multicall, Call +from eth_typing import HexAddress +from multicall import Multicall from safe_eth.eth.constants import NULL_ADDRESS from web3 import Web3 from eth_defi.lagoon.vault import LagoonVault from eth_defi.provider.broken_provider import get_almost_latest_block_number from eth_defi.token import TokenDetails +from eth_defi.trace import assert_transaction_success_with_explanation from eth_defi.uniswap_v2.constants import UNISWAP_V2_DEPLOYMENTS from eth_defi.uniswap_v2.deployment import fetch_deployment, UniswapV2Deployment from eth_defi.vault.base import TradingUniverse @@ -198,17 +200,23 @@ def test_lagoon_diagnose_routes( assert routes.loc["DINO -> USDC"]["Value"] == "-" -def test_lagoon_post_valuation_commitee( +def test_lagoon_post_valuation( web3: Web3, lagoon_vault: LagoonVault, base_usdc: TokenDetails, base_weth: TokenDetails, base_dino: TokenDetails, uniswap_v2: UniswapV2Deployment, + topped_up_valuation_manager: HexAddress, ): """Update vault NAV.""" vault = lagoon_vault + valuation_manager = topped_up_valuation_manager + + # Check value before update + nav = vault.fetch_nav() + assert nav == pytest.approx(Decimal(0.1)) universe = TradingUniverse( spot_token_addresses={ @@ -234,7 +242,14 @@ def test_lagoon_post_valuation_commitee( portfolio_valuation = nav_calculator.calculate_market_sell_nav(portfolio) total_value = portfolio_valuation.get_total_equity() - tx_data = vault.post_valuation_commitee(total_value) + bound_func = vault.post_new_valuation(total_value) + + # Unlocked by anvil + tx_hash = bound_func.transact({"from": valuation_manager}) + assert_transaction_success_with_explanation(web3, tx_hash) + # Check value after update + nav = vault.fetch_nav() + assert nav == pytest.approx(Decimal(0.1))