Skip to content

Commit

Permalink
Start Lagoon valuation work (#243)
Browse files Browse the repository at this point in the history
- Add pure onchain data source-based portfolio valuation, in the preparation for valuation committee hooks
- Clean up documentation regarding vaults
  • Loading branch information
miohtama authored Dec 3, 2024
1 parent e503322 commit 5076546
Show file tree
Hide file tree
Showing 9 changed files with 1,004 additions and 13 deletions.
17 changes: 17 additions & 0 deletions docs/source/api/vault/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Vault framework
---------------

A generic high-level Python framework to integrate different vault providers.

- Provide an abstract base class and toolkit to interact with vault providers from Python applications

- The main use case is automated trading with vault-managed capital

- For more details see :py:class:`eth_defi.vault.base.VaultBase`

.. autosummary::
:toctree: _autosummary_velvet
:recursive:

eth_defi.vault.base
eth_defi.vault.valuation
9 changes: 6 additions & 3 deletions eth_defi/balances.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
from collections import Counter
from dataclasses import dataclass
from decimal import Decimal
from itertools import islice
from typing import Dict, Optional, Set, Collection
from typing import Dict, Optional, Collection

import cachetools
import requests.exceptions
Expand All @@ -19,6 +18,7 @@
from eth_defi.event import fetch_all_events
from eth_defi.provider.anvil import is_anvil, is_mainnet_fork
from eth_defi.provider.broken_provider import get_almost_latest_block_number
from eth_defi.provider.named import get_provider_name
from eth_defi.token import fetch_erc20_details, DEFAULT_TOKEN_CACHE

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -327,11 +327,14 @@ def _handler(success, value):

chain_id = web3.eth.chain_id

rpc_name = get_provider_name(web3.provider)

logger.info(
"Looking up token balances for %d addresses, chunk size %d, gas limit %d",
"Looking up token balances for %d addresses, chunk size %d, gas limit %d, using provider %s",
len(tokens),
chunk_size,
gas_limit,
rpc_name,
)

tokens = list(tokens)
Expand Down
2 changes: 2 additions & 0 deletions eth_defi/lagoon/valuation_commitee.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"""Lagoon vault NAV handling."""

15 changes: 15 additions & 0 deletions eth_defi/lagoon/vault.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,3 +167,18 @@ def transact_through_module(
operation,
)
return bound_func

def post_valuation_commitee(
self,
portfolio: VaultPortfolio,
):
"""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
"""
raise NotImplementedError()



2 changes: 1 addition & 1 deletion eth_defi/token.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def chain_id(self) -> int:
return self.contract.w3.eth.chain_id

@property
def address(self) -> TokenAddress:
def address(self) -> HexAddress:
"""The address of this token."""
return self.contract.address

Expand Down
101 changes: 92 additions & 9 deletions eth_defi/vault/base.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
"""Generic Vault interface base classes"""

from abc import ABC, abstractmethod
from dataclasses import dataclass
from decimal import Decimal
from typing import Iterable, TypedDict
from typing import TypedDict

from eth.typing import BlockRange
from eth_typing import BlockIdentifier, HexAddress
from web3 import Web3

from eth_defi.token import TokenAddress

from eth_defi.token import TokenAddress, fetch_erc20_details

class VaultEvent:
pass


@dataclass(slots=True, frozen=True)
class VaultSpec:
"""Unique id for a vault"""

#: Ethereum chain id
chain_id: int

#: Vault smart contract address or whatever is the primary address for unravelling a vault deployment for a vault protocol
vault_address: HexAddress

def __post_init__(self):
Expand All @@ -26,7 +30,13 @@ def __post_init__(self):


class VaultInfo(TypedDict):
"""Vault-protocol specific intormation about the vault."""
"""Vault-protocol specific intormation about the vault.
- A dictionary of data we gathered about the vault deployment,
like various smart contracts associated with the vault
- Not standardised yet
"""


class VaultDeploymentParameters(TypedDict):
Expand All @@ -35,19 +45,60 @@ class VaultDeploymentParameters(TypedDict):

@dataclass
class TradingUniverse:
"""Input needed to deploy a vault."""
"""Describe assets vault can manage.
- Because of brainrotten and awful ERC-20 token standard, the vault does not know what tokens it owns
and this needs to be specific offchain
"""

spot_token_addresses: set[TokenAddress]


@dataclass
class VaultPortfolio:
"""Input needed to deploy a vault."""
"""Get the vault asset balances.
- Takes :py:class:`TradingUniverse` as an input and resolves all relevant balances the vault holds for this trading universe
- Because of brainrotten and awful ERC-20 token standard, the vault does not know what tokens it owns
and this needs to be specific offchain
- See :py:meth:`VaultBase.fetch_portfolio`
"""

spot_erc20: dict[HexAddress, Decimal]

def __post_init__(self):
for token, value in self.spot_erc20.items():
assert type(token) == str
assert isinstance(value, Decimal)

@property
def tokens(self) -> set[HexAddress]:
"""Get list of tokens held in this portfolio"""
return set(self.spot_erc20.keys())

def is_spot_only(self) -> bool:
"""Do we have only ERC-20 hold positions in this portfolio"""
return True # Other positiosn not supported yet

def get_position_count(self):
return len(self.spot_erc20)

def get_raw_spot_balances(self, web3: Web3) -> dict[HexAddress, int]:
"""Convert spot balances to raw token balances"""
chain_id = web3.eth.chain_id
return {addr: fetch_erc20_details(web3, addr, chain_id=chain_id).convert_to_raw(value) for addr, value in self.spot_erc20.items()}

spot_erc20: dict[TokenAddress, Decimal]


class VaultFlowManager(ABC):
"""Manage deposit/redemption events
- Create a replay of flow events that happened for a vault within a specific block range
- Not implemented yet
"""

@abstractmethod
def fetch_pending_deposits(
Expand Down Expand Up @@ -82,8 +133,40 @@ def fetch_processed_redemptions(
class VaultBase(ABC):
"""Base class for vault protocol adapters.
Allows automated interaction with different `vault protocols <https://tradingstrategy.ai/glossary/vault>`__.
Supported protocols include
- Velvet Capital :py:class:`eth_defi.velvet.vault.VelvetVault`
- Lagoon Finance :py:class:`eth_defi.lagoon.vault.LagoonVault`
Code exists, but does not confirm the interface yet:
- Enzyme Finance :py:class:`eth_defi.lagoon.enzyme.vault.Vault`
What this wraper class does:
- Takes :py:class:`VaultSpec` as a constructor argument and builds a proxy class
for accessing the vault based on this
Vault functionality that needs to be supported
- Fetching the current balances, deposits or redemptions
- Either using naive polling approach with :py:method:`fetch_portfolio`
- Listen to vault events for deposits and redemptions using :py:meth:`get_flow_manager`
- Get vault information with :py:method:`fetch_info`
- No standardised data structures or functions yet
- Build a swap through a vault
- No standardised data structure yet
- Update vault position valuations
- No standardised data structure yet
For code examples see `tests/lagoon` and `tests/velvet`.
"""

@abstractmethod
Expand Down
Loading

0 comments on commit 5076546

Please sign in to comment.