2021-06-07 07:10:18 -07:00
|
|
|
|
# # ⚠ Warning
|
|
|
|
|
#
|
|
|
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
|
|
|
|
# LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
|
|
|
|
# NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
|
|
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
|
|
|
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
|
#
|
|
|
|
|
# [🥭 Mango Markets](https://mango.markets/) support is available at:
|
|
|
|
|
# [Docs](https://docs.mango.markets/)
|
|
|
|
|
# [Discord](https://discord.gg/67jySBhxrg)
|
|
|
|
|
# [Twitter](https://twitter.com/mangomarkets)
|
|
|
|
|
# [Github](https://github.com/blockworks-foundation)
|
|
|
|
|
# [Email](mailto:hello@blockworks.foundation)
|
|
|
|
|
|
|
|
|
|
import typing
|
|
|
|
|
|
|
|
|
|
from decimal import Decimal
|
|
|
|
|
from solana.publickey import PublicKey
|
|
|
|
|
|
|
|
|
|
from .accountinfo import AccountInfo
|
|
|
|
|
from .addressableaccount import AddressableAccount
|
|
|
|
|
from .context import Context
|
|
|
|
|
from .layouts import layouts
|
2021-06-25 03:10:41 -07:00
|
|
|
|
from .marketlookup import MarketLookup
|
2021-06-25 07:50:37 -07:00
|
|
|
|
from .metadata import Metadata
|
|
|
|
|
from .perpmarketinfo import PerpMarketInfo
|
2021-07-01 13:24:08 -07:00
|
|
|
|
from .rootbank import RootBank
|
2021-06-25 07:50:37 -07:00
|
|
|
|
from .spotmarketinfo import SpotMarketInfo
|
2021-07-19 07:04:53 -07:00
|
|
|
|
from .token import SolToken, Token
|
2021-06-25 07:50:37 -07:00
|
|
|
|
from .tokeninfo import TokenInfo
|
2021-06-25 03:10:41 -07:00
|
|
|
|
from .tokenlookup import TokenLookup
|
2021-06-07 07:10:18 -07:00
|
|
|
|
from .tokenvalue import TokenValue
|
|
|
|
|
from .version import Version
|
|
|
|
|
|
2021-06-25 07:50:37 -07:00
|
|
|
|
|
2021-06-07 07:10:18 -07:00
|
|
|
|
# # 🥭 Group class
|
|
|
|
|
#
|
2021-06-25 07:50:37 -07:00
|
|
|
|
# `Group` defines root functionality for Mango Markets.
|
|
|
|
|
#
|
2021-06-07 07:10:18 -07:00
|
|
|
|
|
|
|
|
|
class Group(AddressableAccount):
|
2021-06-08 00:17:37 -07:00
|
|
|
|
def __init__(self, account_info: AccountInfo, version: Version, name: str,
|
2021-06-25 07:50:37 -07:00
|
|
|
|
meta_data: Metadata, tokens: typing.Sequence[typing.Optional[TokenInfo]],
|
|
|
|
|
spot_markets: typing.Sequence[typing.Optional[SpotMarketInfo]],
|
|
|
|
|
perp_markets: typing.Sequence[typing.Optional[PerpMarketInfo]],
|
|
|
|
|
oracles: typing.Sequence[PublicKey], signer_nonce: Decimal, signer_key: PublicKey,
|
2021-07-01 13:24:08 -07:00
|
|
|
|
admin: PublicKey, dex_program_id: PublicKey, cache: PublicKey, valid_interval: Decimal,
|
2021-07-13 04:43:50 -07:00
|
|
|
|
dao_vault: PublicKey, srm_vault: PublicKey, msrm_vault: PublicKey):
|
2021-06-07 07:10:18 -07:00
|
|
|
|
super().__init__(account_info)
|
|
|
|
|
self.version: Version = version
|
2021-06-08 00:17:37 -07:00
|
|
|
|
self.name: str = name
|
2021-06-25 07:50:37 -07:00
|
|
|
|
|
|
|
|
|
self.meta_data: Metadata = meta_data
|
|
|
|
|
self.tokens: typing.Sequence[typing.Optional[TokenInfo]] = tokens
|
|
|
|
|
self.spot_markets: typing.Sequence[typing.Optional[SpotMarketInfo]] = spot_markets
|
|
|
|
|
self.perp_markets: typing.Sequence[typing.Optional[PerpMarketInfo]] = perp_markets
|
|
|
|
|
self.oracles: typing.Sequence[PublicKey] = oracles
|
2021-06-07 07:10:18 -07:00
|
|
|
|
self.signer_nonce: Decimal = signer_nonce
|
|
|
|
|
self.signer_key: PublicKey = signer_key
|
|
|
|
|
self.admin: PublicKey = admin
|
2021-06-25 07:50:37 -07:00
|
|
|
|
self.dex_program_id: PublicKey = dex_program_id
|
|
|
|
|
self.cache: PublicKey = cache
|
|
|
|
|
self.valid_interval: Decimal = valid_interval
|
2021-07-13 04:43:50 -07:00
|
|
|
|
self.dao_vault: PublicKey = dao_vault
|
|
|
|
|
self.srm_vault: PublicKey = srm_vault
|
|
|
|
|
self.msrm_vault: PublicKey = msrm_vault
|
2021-06-07 07:10:18 -07:00
|
|
|
|
|
|
|
|
|
@property
|
2021-06-25 07:50:37 -07:00
|
|
|
|
def shared_quote_token(self) -> TokenInfo:
|
|
|
|
|
quote = self.tokens[-1]
|
|
|
|
|
if quote is None:
|
|
|
|
|
raise Exception(f"Could not find shared quote token for group '{self.name}'.")
|
|
|
|
|
return quote
|
2021-06-07 07:10:18 -07:00
|
|
|
|
|
|
|
|
|
@property
|
2021-06-25 07:50:37 -07:00
|
|
|
|
def base_tokens(self) -> typing.Sequence[typing.Optional[TokenInfo]]:
|
|
|
|
|
return self.tokens[:-1]
|
|
|
|
|
|
2021-06-07 07:10:18 -07:00
|
|
|
|
@staticmethod
|
2021-07-01 13:24:08 -07:00
|
|
|
|
def from_layout(context: Context, layout: layouts.GROUP, name: str, account_info: AccountInfo, version: Version, token_lookup: TokenLookup, market_lookup: MarketLookup) -> "Group":
|
|
|
|
|
meta_data: Metadata = Metadata.from_layout(layout.meta_data)
|
|
|
|
|
num_oracles: Decimal = layout.num_oracles
|
|
|
|
|
|
|
|
|
|
root_bank_addresses = [ti.root_bank for ti in layout.tokens if ti is not None and ti.root_bank is not None]
|
|
|
|
|
root_banks = RootBank.load_multiple(context, root_bank_addresses)
|
|
|
|
|
tokens: typing.List[typing.Optional[TokenInfo]] = [
|
|
|
|
|
TokenInfo.from_layout_or_none(t, token_lookup, root_banks) for t in layout.tokens]
|
|
|
|
|
|
|
|
|
|
spot_markets: typing.List[typing.Optional[SpotMarketInfo]] = [
|
|
|
|
|
SpotMarketInfo.from_layout_or_none(m, market_lookup) for m in layout.spot_markets]
|
|
|
|
|
perp_markets: typing.List[typing.Optional[PerpMarketInfo]] = [
|
|
|
|
|
PerpMarketInfo.from_layout_or_none(p) for p in layout.perp_markets]
|
|
|
|
|
oracles: typing.List[PublicKey] = list(layout.oracles)[:int(num_oracles)]
|
|
|
|
|
signer_nonce: Decimal = layout.signer_nonce
|
|
|
|
|
signer_key: PublicKey = layout.signer_key
|
|
|
|
|
admin: PublicKey = layout.admin
|
|
|
|
|
dex_program_id: PublicKey = layout.dex_program_id
|
|
|
|
|
cache: PublicKey = layout.cache
|
|
|
|
|
valid_interval: Decimal = layout.valid_interval
|
2021-07-12 02:57:05 -07:00
|
|
|
|
dao_vault: PublicKey = layout.dao_vault
|
2021-07-13 04:43:50 -07:00
|
|
|
|
srm_vault: PublicKey = layout.srm_vault
|
|
|
|
|
msrm_vault: PublicKey = layout.msrm_vault
|
2021-07-01 13:24:08 -07:00
|
|
|
|
|
2021-07-13 04:43:50 -07:00
|
|
|
|
return Group(account_info, version, name, meta_data, tokens, spot_markets, perp_markets, oracles, signer_nonce, signer_key, admin, dex_program_id, cache, valid_interval, dao_vault, srm_vault, msrm_vault)
|
2021-06-07 07:10:18 -07:00
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def parse(context: Context, account_info: AccountInfo) -> "Group":
|
|
|
|
|
data = account_info.data
|
2021-06-25 07:50:37 -07:00
|
|
|
|
if len(data) != layouts.GROUP.sizeof():
|
2021-06-07 07:10:18 -07:00
|
|
|
|
raise Exception(
|
2021-07-12 02:57:05 -07:00
|
|
|
|
f"Group data length ({len(data)}) does not match expected size ({layouts.GROUP.sizeof()})")
|
2021-06-07 07:10:18 -07:00
|
|
|
|
|
2021-06-25 07:50:37 -07:00
|
|
|
|
layout = layouts.GROUP.parse(data)
|
2021-06-28 01:48:16 -07:00
|
|
|
|
name = context.lookup_group_name(account_info.address)
|
2021-07-01 13:24:08 -07:00
|
|
|
|
return Group.from_layout(context, layout, name, account_info, Version.V1, context.token_lookup, context.market_lookup)
|
2021-06-07 07:10:18 -07:00
|
|
|
|
|
|
|
|
|
@staticmethod
|
2021-06-25 07:50:37 -07:00
|
|
|
|
def load(context: Context, address: typing.Optional[PublicKey] = None) -> "Group":
|
|
|
|
|
group_address: PublicKey = address or context.group_id
|
|
|
|
|
account_info = AccountInfo.load(context, group_address)
|
2021-06-07 07:10:18 -07:00
|
|
|
|
if account_info is None:
|
2021-06-25 07:50:37 -07:00
|
|
|
|
raise Exception(f"Group account not found at address '{group_address}'")
|
2021-06-07 07:10:18 -07:00
|
|
|
|
return Group.parse(context, account_info)
|
|
|
|
|
|
2021-07-12 10:26:35 -07:00
|
|
|
|
def find_spot_market_index(self, spot_market_address: PublicKey) -> int:
|
|
|
|
|
for index, spot in enumerate(self.spot_markets):
|
|
|
|
|
if spot is not None and spot.address == spot_market_address:
|
|
|
|
|
return index
|
|
|
|
|
|
|
|
|
|
raise Exception(f"Could not find spot market {spot_market_address} in group {self.address}")
|
|
|
|
|
|
|
|
|
|
def find_perp_market_index(self, perp_market_address: PublicKey) -> int:
|
|
|
|
|
for index, pm in enumerate(self.perp_markets):
|
|
|
|
|
if pm is not None and pm.address == perp_market_address:
|
|
|
|
|
return index
|
|
|
|
|
|
|
|
|
|
raise Exception(f"Could not find perp market {perp_market_address} in group {self.address}")
|
|
|
|
|
|
2021-07-19 07:04:53 -07:00
|
|
|
|
def find_token_info_by_token(self, token: Token) -> TokenInfo:
|
|
|
|
|
for index, token_info in enumerate(self.tokens):
|
|
|
|
|
if token_info is not None and token_info.token == token:
|
|
|
|
|
return token_info
|
|
|
|
|
|
|
|
|
|
raise Exception(f"Could not find token info for mint {token.mint} in group {self.address}")
|
|
|
|
|
|
2021-06-25 07:50:37 -07:00
|
|
|
|
def fetch_balances(self, context: Context, root_address: PublicKey) -> typing.Sequence[TokenValue]:
|
2021-06-07 07:10:18 -07:00
|
|
|
|
balances: typing.List[TokenValue] = []
|
2021-06-08 00:17:37 -07:00
|
|
|
|
sol_balance = context.fetch_sol_balance(root_address)
|
2021-06-07 07:10:18 -07:00
|
|
|
|
balances += [TokenValue(SolToken, sol_balance)]
|
|
|
|
|
|
2021-06-25 07:50:37 -07:00
|
|
|
|
for basket_token in self.tokens:
|
|
|
|
|
if basket_token is not None and basket_token.token is not None:
|
|
|
|
|
balance = TokenValue.fetch_total_value(context, root_address, basket_token.token)
|
|
|
|
|
balances += [balance]
|
2021-06-07 07:10:18 -07:00
|
|
|
|
return balances
|
|
|
|
|
|
2021-06-25 07:50:37 -07:00
|
|
|
|
def __str__(self):
|
|
|
|
|
tokens = "\n ".join([f"{token}".replace("\n", "\n ")
|
|
|
|
|
for token in self.tokens if token is not None])
|
|
|
|
|
spot_markets = "\n ".join([f"{spot_market}".replace("\n", "\n ")
|
|
|
|
|
for spot_market in self.spot_markets if spot_market is not None])
|
|
|
|
|
perp_markets = "\n ".join([f"{perp_market}".replace("\n", "\n ")
|
|
|
|
|
for perp_market in self.perp_markets if perp_market is not None])
|
|
|
|
|
oracles = "\n ".join([f"{oracle}" for oracle in self.oracles])
|
|
|
|
|
return f"""« 𝙶𝚛𝚘𝚞𝚙 {self.version} [{self.address}]
|
|
|
|
|
{self.meta_data}
|
|
|
|
|
Name: {self.name}
|
|
|
|
|
Signer [Nonce: {self.signer_nonce}]: {self.signer_key}
|
|
|
|
|
Admin: {self.admin}
|
|
|
|
|
DEX Program ID: {self.dex_program_id}
|
2021-07-01 13:24:08 -07:00
|
|
|
|
Cache: {self.cache}
|
2021-07-12 02:57:05 -07:00
|
|
|
|
DAO Vault: {self.dao_vault}
|
2021-07-13 04:43:50 -07:00
|
|
|
|
SRM Vault: {self.srm_vault}
|
|
|
|
|
MSRM Vault: {self.msrm_vault}
|
2021-06-25 07:50:37 -07:00
|
|
|
|
Valid Interval: {self.valid_interval}
|
|
|
|
|
Tokens:
|
|
|
|
|
{tokens}
|
|
|
|
|
Spot Markets:
|
|
|
|
|
{spot_markets}
|
|
|
|
|
Perp Markets:
|
|
|
|
|
{perp_markets}
|
|
|
|
|
Oracles:
|
|
|
|
|
{oracles}
|
|
|
|
|
»"""
|