From a6288753fa063b274d836c3be4a643f446a158c8 Mon Sep 17 00:00:00 2001 From: Mikko Ohtamaa Date: Tue, 3 Dec 2024 16:41:11 +0100 Subject: [PATCH] Working on Lagoon settlement --- eth_defi/lagoon/vault.py | 27 +++++++++++++++++++++++++++ eth_defi/vault/base.py | 1 - tests/lagoon/conftest.py | 23 ++++++++++++++++++++++- tests/lagoon/test_lagoon_valuation.py | 14 +++++++++----- 4 files changed, 58 insertions(+), 7 deletions(-) diff --git a/eth_defi/lagoon/vault.py b/eth_defi/lagoon/vault.py index 98cba3fe..8b97aced 100644 --- a/eth_defi/lagoon/vault.py +++ b/eth_defi/lagoon/vault.py @@ -146,6 +146,21 @@ def fetch_info(self) -> LagoonVaultInfo: del safe_info_dict["address"] # Key conflict return vault_info | safe_info_dict + def fetch_nav(self) -> Decimal: + """Fetch the most recent onchain NAV value. + + - In the case of Lagoon, this is the last value written in the contract with + `updateNewTotalAssets()` and ` settleDeposit()` + + - TODO: `updateNewTotalAssets()` there is no way to read pending asset update on chain + + :return: + Vault NAV, denominated in :py:meth:`denomination_token` + """ + token = self.denomination_token + raw_amount = self.vault_contract.functions.totalAssets().call() + return token.convert_to_decimals(raw_amount) + @property def address(self) -> HexAddress: """Get the vault smart contract address.""" @@ -262,6 +277,18 @@ def post_new_valuation( bound_func = self.vault_contract.functions.updateNewTotalAssets(raw_amount) return bound_func + def settle(self) -> ContractFunction: + """Settle the new valuation and deposits. + + - settleDeposit will also settle the redeems request if possible + + - if there is nothing to settle: no deposit and redeem requests you can still call settleDeposit/settleRedeem to validate the new nav + """ + logger.info("Settling vault %s valuation", ) + bound_func = self.vault_contract.functions.settleDeposit() + return bound_func + + diff --git a/eth_defi/vault/base.py b/eth_defi/vault/base.py index dcec5248..8b325e60 100644 --- a/eth_defi/vault/base.py +++ b/eth_defi/vault/base.py @@ -237,7 +237,6 @@ def fetch_nav(self) -> Decimal: 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 d887bc19..3217331f 100644 --- a/tests/lagoon/conftest.py +++ b/tests/lagoon/conftest.py @@ -1,8 +1,11 @@ """Base mainnet fork based tests for Lagoon. +Explore the static deployment which we fork from the Base mainnet: + - Vault UI: https://trading-stategy-users-frontend.vercel.app/vault/8453/0xab4ac28d10a4bc279ad073b1d74bfa0e385c010c +- Vault contract: https://basescan.org/address/0xab4ac28d10a4bc279ad073b1d74bfa0e385c010c#readProxyContract - Safe UI: https://app.safe.global/home?safe=base:0x20415f3Ec0FEA974548184bdD6e67575D128953F -- Contract: https://basescan.org/address/0x20415f3Ec0FEA974548184bdD6e67575D128953F#readProxyContract +- Safe contract: https://basescan.org/address/0x20415f3Ec0FEA974548184bdD6e67575D128953F#readProxyContract - Roles: https://app.safe.global/apps/open?safe=base:0x20415f3Ec0FEA974548184bdD6e67575D128953F&appUrl=https%3A%2F%2Fzodiac.gnosisguild.org%2F """ import os @@ -45,6 +48,12 @@ def valuation_manager() -> HexAddress: return "0x8358bBFb4Afc9B1eBe4e8C93Db8bF0586BD8331a" +@pytest.fixture() +def safe_address() -> HexAddress: + """Unlockable Safe multisig as spoofed Anvil account.""" + return "0x20415f3Ec0FEA974548184bdD6e67575D128953F" + + @pytest.fixture() def anvil_base_fork(request, vault_owner, usdc_holder, asset_manager, valuation_manager) -> AnvilLaunch: """Create a testable fork of live BNB chain. @@ -183,6 +192,18 @@ def topped_up_valuation_manager(web3, valuation_manager): return valuation_manager +@pytest.fixture() +def spoofed_safe(web3, safe_address): + # Topped up with some ETH + tx_hash = web3.eth.send_transaction({ + "to": safe_address, + "from": web3.eth.accounts[0], + "value": 9 * 10**18, + }) + assert_transaction_success_with_explanation(web3, tx_hash) + return safe_address + + # Some addresses for the roles set: """ diff --git a/tests/lagoon/test_lagoon_valuation.py b/tests/lagoon/test_lagoon_valuation.py index b389e8c8..01cba428 100644 --- a/tests/lagoon/test_lagoon_valuation.py +++ b/tests/lagoon/test_lagoon_valuation.py @@ -200,6 +200,7 @@ def test_lagoon_diagnose_routes( assert routes.loc["DINO -> USDC"]["Value"] == "-" +@pytest.mark.skip(reason="Unfinished") def test_lagoon_post_valuation( web3: Web3, lagoon_vault: LagoonVault, @@ -208,6 +209,7 @@ def test_lagoon_post_valuation( base_dino: TokenDetails, uniswap_v2: UniswapV2Deployment, topped_up_valuation_manager: HexAddress, + spoofed_safe: HexAddress, ): """Update vault NAV.""" @@ -216,7 +218,7 @@ def test_lagoon_post_valuation( # Check value before update nav = vault.fetch_nav() - assert nav == pytest.approx(Decimal(0.1)) + assert nav == pytest.approx(Decimal(0)) # settle() never called for this vault universe = TradingUniverse( spot_token_addresses={ @@ -241,15 +243,17 @@ def test_lagoon_post_valuation( portfolio_valuation = nav_calculator.calculate_market_sell_nav(portfolio) + # First post the new valuation as valuation manager total_value = portfolio_valuation.get_total_equity() bound_func = vault.post_new_valuation(total_value) + tx_hash = bound_func.transact({"from": valuation_manager}) # Unlocked by anvil + assert_transaction_success_with_explanation(web3, tx_hash) - # Unlocked by anvil - tx_hash = bound_func.transact({"from": valuation_manager}) + # Then settle the valuation as the vault owner + bound_func = vault.settle() + tx_hash = bound_func.transact({"from": spoofed_safe}) assert_transaction_success_with_explanation(web3, tx_hash) # Check value after update nav = vault.fetch_nav() assert nav == pytest.approx(Decimal(0.1)) - -