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)
|
|
|
|
|
2021-12-13 05:56:02 -08:00
|
|
|
import logging
|
2021-06-07 07:10:18 -07:00
|
|
|
import typing
|
|
|
|
|
|
|
|
from decimal import Decimal
|
|
|
|
from solana.publickey import PublicKey
|
|
|
|
|
|
|
|
from .accountinfo import AccountInfo
|
|
|
|
from .addressableaccount import AddressableAccount
|
2021-11-04 08:36:14 -07:00
|
|
|
from .cache import Cache, PerpMarketCache, MarketCache
|
2021-11-08 03:54:50 -08:00
|
|
|
from .constants import SYSTEM_PROGRAM_ADDRESS
|
2021-06-07 07:10:18 -07:00
|
|
|
from .context import Context
|
2021-11-08 03:39:09 -08:00
|
|
|
from .instrumentlookup import InstrumentLookup
|
|
|
|
from .instrumentvalue import InstrumentValue
|
2021-06-07 07:10:18 -07:00
|
|
|
from .layouts import layouts
|
2021-10-02 04:28:16 -07:00
|
|
|
from .lotsizeconverter import LotSizeConverter, RaisingLotSizeConverter
|
2021-11-01 11:41:31 -07:00
|
|
|
from .marketlookup import MarketLookup
|
2021-06-25 07:50:37 -07:00
|
|
|
from .metadata import Metadata
|
2021-11-08 03:39:09 -08:00
|
|
|
from .token import Instrument, Token
|
2021-11-13 11:23:11 -08:00
|
|
|
from .tokenbank import TokenBank
|
2021-06-07 07:10:18 -07:00
|
|
|
from .version import Version
|
|
|
|
|
2021-06-25 07:50:37 -07:00
|
|
|
|
2021-11-08 03:54:50 -08:00
|
|
|
# # 🥭 GroupSlotSpotMarket class
|
|
|
|
#
|
|
|
|
class GroupSlotSpotMarket:
|
2021-11-09 05:23:36 -08:00
|
|
|
def __init__(self, address: PublicKey, maint_asset_weight: Decimal, init_asset_weight: Decimal, maint_liab_weight: Decimal, init_liab_weight: Decimal) -> None:
|
2021-11-08 03:54:50 -08:00
|
|
|
self.address: PublicKey = address
|
|
|
|
self.maint_asset_weight: Decimal = maint_asset_weight
|
|
|
|
self.init_asset_weight: Decimal = init_asset_weight
|
|
|
|
self.maint_liab_weight: Decimal = maint_liab_weight
|
|
|
|
self.init_liab_weight: Decimal = init_liab_weight
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def from_layout(layout: typing.Any) -> "GroupSlotSpotMarket":
|
|
|
|
spot_market: PublicKey = layout.spot_market
|
|
|
|
maint_asset_weight: Decimal = round(layout.maint_asset_weight, 8)
|
|
|
|
init_asset_weight: Decimal = round(layout.init_asset_weight, 8)
|
|
|
|
maint_liab_weight: Decimal = round(layout.maint_liab_weight, 8)
|
|
|
|
init_liab_weight: Decimal = round(layout.init_liab_weight, 8)
|
|
|
|
return GroupSlotSpotMarket(spot_market, maint_asset_weight, init_asset_weight, maint_liab_weight, init_liab_weight)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def from_layout_or_none(layout: typing.Any) -> typing.Optional["GroupSlotSpotMarket"]:
|
|
|
|
if (layout.spot_market is None) or (layout.spot_market == SYSTEM_PROGRAM_ADDRESS):
|
|
|
|
return None
|
|
|
|
|
|
|
|
return GroupSlotSpotMarket.from_layout(layout)
|
|
|
|
|
|
|
|
def __str__(self) -> str:
|
2021-12-13 04:06:42 -08:00
|
|
|
return f"""« GroupSlotSpotMarket [{self.address}]
|
2021-11-08 03:54:50 -08:00
|
|
|
Asset Weights:
|
|
|
|
Initial: {self.init_asset_weight}
|
|
|
|
Maintenance: {self.maint_asset_weight}
|
|
|
|
Liability Weights:
|
|
|
|
Initial: {self.init_liab_weight}
|
|
|
|
Maintenance: {self.maint_liab_weight}
|
|
|
|
»"""
|
|
|
|
|
|
|
|
def __repr__(self) -> str:
|
|
|
|
return f"{self}"
|
|
|
|
|
|
|
|
|
|
|
|
# # 🥭 GroupSlotPerpMarket class
|
|
|
|
#
|
|
|
|
class GroupSlotPerpMarket:
|
2021-11-09 05:23:36 -08:00
|
|
|
def __init__(self, address: PublicKey, maint_asset_weight: Decimal, init_asset_weight: Decimal, maint_liab_weight: Decimal, init_liab_weight: Decimal, liquidation_fee: Decimal, base_lot_size: Decimal, quote_lot_size: Decimal) -> None:
|
2021-11-08 03:54:50 -08:00
|
|
|
self.address: PublicKey = address
|
|
|
|
self.maint_asset_weight: Decimal = maint_asset_weight
|
|
|
|
self.init_asset_weight: Decimal = init_asset_weight
|
|
|
|
self.maint_liab_weight: Decimal = maint_liab_weight
|
|
|
|
self.init_liab_weight: Decimal = init_liab_weight
|
|
|
|
self.liquidation_fee: Decimal = liquidation_fee
|
|
|
|
self.base_lot_size: Decimal = base_lot_size
|
|
|
|
self.quote_lot_size: Decimal = quote_lot_size
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def from_layout(layout: typing.Any) -> "GroupSlotPerpMarket":
|
|
|
|
perp_market: PublicKey = layout.perp_market
|
|
|
|
maint_asset_weight: Decimal = round(layout.maint_asset_weight, 8)
|
|
|
|
init_asset_weight: Decimal = round(layout.init_asset_weight, 8)
|
|
|
|
maint_liab_weight: Decimal = round(layout.maint_liab_weight, 8)
|
|
|
|
init_liab_weight: Decimal = round(layout.init_liab_weight, 8)
|
|
|
|
liquidation_fee: Decimal = round(layout.liquidation_fee, 8)
|
|
|
|
base_lot_size: Decimal = layout.base_lot_size
|
|
|
|
quote_lot_size: Decimal = layout.quote_lot_size
|
|
|
|
|
|
|
|
return GroupSlotPerpMarket(perp_market, maint_asset_weight, init_asset_weight, maint_liab_weight, init_liab_weight, liquidation_fee, base_lot_size, quote_lot_size)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def from_layout_or_none(layout: typing.Any) -> typing.Optional["GroupSlotPerpMarket"]:
|
|
|
|
if (layout.perp_market is None) or (layout.perp_market == SYSTEM_PROGRAM_ADDRESS):
|
|
|
|
return None
|
|
|
|
|
|
|
|
return GroupSlotPerpMarket.from_layout(layout)
|
|
|
|
|
|
|
|
def __str__(self) -> str:
|
2021-12-13 04:06:42 -08:00
|
|
|
return f"""« GroupSlotPerpMarket [{self.address}]
|
2021-11-08 03:54:50 -08:00
|
|
|
Asset Weights:
|
|
|
|
Initial: {self.init_asset_weight}
|
|
|
|
Maintenance: {self.maint_asset_weight}
|
|
|
|
Liability Weights:
|
|
|
|
Initial: {self.init_liab_weight}
|
|
|
|
Maintenance: {self.maint_liab_weight}
|
|
|
|
Liquidation Fee: {self.liquidation_fee}
|
|
|
|
Base Lot Size: {self.base_lot_size}
|
|
|
|
Quote Lot Size: {self.quote_lot_size}
|
|
|
|
»"""
|
|
|
|
|
|
|
|
def __repr__(self) -> str:
|
|
|
|
return f"{self}"
|
|
|
|
|
|
|
|
|
2021-11-08 03:39:09 -08:00
|
|
|
# # 🥭 GroupSlot class
|
2021-08-03 10:32:12 -07:00
|
|
|
#
|
2021-11-08 03:39:09 -08:00
|
|
|
# `GroupSlot` gathers indexed slot items together instead of separate arrays.
|
2021-08-03 10:32:12 -07:00
|
|
|
#
|
2021-11-08 03:39:09 -08:00
|
|
|
class GroupSlot:
|
2021-11-13 11:23:11 -08:00
|
|
|
def __init__(self, index: int, base_instrument: Instrument, base_token_bank: typing.Optional[TokenBank], quote_token_bank: TokenBank, spot_market_info: typing.Optional[GroupSlotSpotMarket], perp_market_info: typing.Optional[GroupSlotPerpMarket], perp_lot_size_converter: LotSizeConverter, oracle: PublicKey) -> None:
|
2021-11-09 13:08:06 -08:00
|
|
|
self.index: int = index
|
2021-11-08 03:39:09 -08:00
|
|
|
self.base_instrument: Instrument = base_instrument
|
2021-11-13 11:23:11 -08:00
|
|
|
self.base_token_bank: typing.Optional[TokenBank] = base_token_bank
|
|
|
|
self.quote_token_bank: TokenBank = quote_token_bank
|
2021-11-10 09:46:54 -08:00
|
|
|
self.spot_market: typing.Optional[GroupSlotSpotMarket] = spot_market_info
|
|
|
|
self.perp_market: typing.Optional[GroupSlotPerpMarket] = perp_market_info
|
2021-10-02 04:28:16 -07:00
|
|
|
self.perp_lot_size_converter: LotSizeConverter = perp_lot_size_converter
|
2021-08-03 10:32:12 -07:00
|
|
|
self.oracle: PublicKey = oracle
|
|
|
|
|
2022-02-07 09:50:00 -08:00
|
|
|
@property
|
|
|
|
def spot_market_symbol(self) -> str:
|
|
|
|
return f"{self.base_instrument.symbol}/{self.quote_token_bank.token.symbol}"
|
|
|
|
|
|
|
|
@property
|
|
|
|
def perp_market_symbol(self) -> str:
|
|
|
|
return f"{self.base_instrument.symbol}-PERP"
|
|
|
|
|
2021-08-03 10:32:12 -07:00
|
|
|
def __str__(self) -> str:
|
2021-11-13 11:23:11 -08:00
|
|
|
base_token_bank = f"{self.base_token_bank}".replace("\n", "\n ")
|
|
|
|
quote_token_bank = f"{self.quote_token_bank}".replace("\n", "\n ")
|
2021-11-10 09:46:54 -08:00
|
|
|
spot_market_info = f"{self.spot_market}".replace("\n", "\n ")
|
|
|
|
perp_market_info = f"{self.perp_market}".replace("\n", "\n ")
|
2021-12-13 04:06:42 -08:00
|
|
|
return f"""« GroupSlot[{self.index}] {self.base_instrument}
|
2021-11-08 03:39:09 -08:00
|
|
|
Base Token Info:
|
2021-11-13 11:23:11 -08:00
|
|
|
{base_token_bank}
|
2021-11-08 03:39:09 -08:00
|
|
|
Quote Token Info:
|
2021-11-13 11:23:11 -08:00
|
|
|
{quote_token_bank}
|
2021-08-03 10:32:12 -07:00
|
|
|
Oracle: {self.oracle}
|
|
|
|
Spot Market:
|
|
|
|
{spot_market_info}
|
|
|
|
Perp Market:
|
|
|
|
{perp_market_info}
|
|
|
|
»"""
|
|
|
|
|
|
|
|
def __repr__(self) -> str:
|
|
|
|
return f"{self}"
|
|
|
|
|
|
|
|
|
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-08-03 10:32:12 -07:00
|
|
|
meta_data: Metadata,
|
2021-11-13 11:23:11 -08:00
|
|
|
shared_quote: TokenBank,
|
2021-11-08 03:39:09 -08:00
|
|
|
slot_indices: typing.Sequence[bool],
|
|
|
|
slots: typing.Sequence[GroupSlot],
|
2021-08-03 10:32:12 -07:00
|
|
|
signer_nonce: Decimal, signer_key: PublicKey,
|
2021-08-26 02:31:02 -07:00
|
|
|
admin: PublicKey, serum_program_address: PublicKey, cache: PublicKey, valid_interval: Decimal,
|
2022-01-18 04:09:15 -08:00
|
|
|
insurance_vault: PublicKey, srm_vault: PublicKey, msrm_vault: PublicKey, fees_vault: PublicKey,
|
2022-02-09 10:31:29 -08:00
|
|
|
max_mango_accounts: Decimal, num_mango_accounts: Decimal,
|
|
|
|
referral_surcharge_centibps: Decimal, referral_share_centibps: Decimal,
|
|
|
|
referral_mngo_required: Decimal) -> None:
|
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
|
2021-11-13 11:23:11 -08:00
|
|
|
self.shared_quote: TokenBank = shared_quote
|
2021-11-08 03:39:09 -08:00
|
|
|
self.slot_indices: typing.Sequence[bool] = slot_indices
|
|
|
|
self.slots: typing.Sequence[GroupSlot] = slots
|
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-08-26 02:31:02 -07:00
|
|
|
self.serum_program_address: PublicKey = serum_program_address
|
2021-06-25 07:50:37 -07:00
|
|
|
self.cache: PublicKey = cache
|
|
|
|
self.valid_interval: Decimal = valid_interval
|
2021-10-04 10:27:07 -07:00
|
|
|
self.insurance_vault: PublicKey = insurance_vault
|
2021-07-13 04:43:50 -07:00
|
|
|
self.srm_vault: PublicKey = srm_vault
|
|
|
|
self.msrm_vault: PublicKey = msrm_vault
|
2021-10-04 10:27:07 -07:00
|
|
|
self.fees_vault: PublicKey = fees_vault
|
2022-01-18 04:09:15 -08:00
|
|
|
self.max_mango_accounts: Decimal = max_mango_accounts
|
|
|
|
self.num_mango_accounts: Decimal = num_mango_accounts
|
2022-02-09 10:31:29 -08:00
|
|
|
self.referral_surcharge_centibps: Decimal = referral_surcharge_centibps
|
|
|
|
self.referral_share_centibps: Decimal = referral_share_centibps
|
|
|
|
self.referral_mngo_required: Decimal = referral_mngo_required
|
2021-06-07 07:10:18 -07:00
|
|
|
|
|
|
|
@property
|
2021-11-08 03:39:09 -08:00
|
|
|
def shared_quote_token(self) -> Token:
|
|
|
|
return Token.ensure(self.shared_quote.token)
|
2021-06-07 07:10:18 -07:00
|
|
|
|
|
|
|
@property
|
2021-11-13 11:23:11 -08:00
|
|
|
def liquidity_incentive_token_bank(self) -> TokenBank:
|
|
|
|
for token_bank in self.tokens:
|
|
|
|
if token_bank.token.symbol_matches("MNGO"):
|
|
|
|
return token_bank
|
2021-11-09 13:08:06 -08:00
|
|
|
|
|
|
|
raise Exception(f"Could not find token info for symbol 'MNGO' in group {self.address}")
|
2021-08-03 10:32:12 -07:00
|
|
|
|
|
|
|
@property
|
2021-11-08 03:39:09 -08:00
|
|
|
def liquidity_incentive_token(self) -> Token:
|
2021-11-13 11:23:11 -08:00
|
|
|
return Token.ensure(self.liquidity_incentive_token_bank.token)
|
2021-08-03 10:32:12 -07:00
|
|
|
|
|
|
|
@property
|
2021-11-13 11:23:11 -08:00
|
|
|
def tokens(self) -> typing.Sequence[TokenBank]:
|
2021-11-08 03:39:09 -08:00
|
|
|
return [*self.base_tokens, self.shared_quote]
|
2021-08-03 10:32:12 -07:00
|
|
|
|
|
|
|
@property
|
2021-11-13 11:23:11 -08:00
|
|
|
def tokens_by_index(self) -> typing.Sequence[typing.Optional[TokenBank]]:
|
2021-11-08 03:39:09 -08:00
|
|
|
return [*self.base_tokens_by_index, self.shared_quote]
|
2021-06-25 07:50:37 -07:00
|
|
|
|
2021-08-30 16:30:22 -07:00
|
|
|
@property
|
2021-11-08 03:39:09 -08:00
|
|
|
def slots_by_index(self) -> typing.Sequence[typing.Optional[GroupSlot]]:
|
|
|
|
mapped_items: typing.List[typing.Optional[GroupSlot]] = []
|
|
|
|
slot_counter = 0
|
|
|
|
for available in self.slot_indices:
|
|
|
|
if available:
|
|
|
|
mapped_items += [self.slots[slot_counter]]
|
|
|
|
slot_counter += 1
|
|
|
|
else:
|
|
|
|
mapped_items += [None]
|
|
|
|
|
|
|
|
return mapped_items
|
|
|
|
|
|
|
|
@property
|
2021-11-13 11:23:11 -08:00
|
|
|
def base_tokens(self) -> typing.Sequence[TokenBank]:
|
|
|
|
return [slot.base_token_bank for slot in self.slots if slot.base_token_bank is not None]
|
2021-11-08 03:39:09 -08:00
|
|
|
|
|
|
|
@property
|
2021-11-13 11:23:11 -08:00
|
|
|
def base_tokens_by_index(self) -> typing.Sequence[typing.Optional[TokenBank]]:
|
|
|
|
return [slot.base_token_bank if slot is not None else None for slot in self.slots_by_index]
|
2021-11-08 03:39:09 -08:00
|
|
|
|
|
|
|
@property
|
|
|
|
def oracles(self) -> typing.Sequence[PublicKey]:
|
|
|
|
return [slot.oracle for slot in self.slots if slot.oracle is not None]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def oracles_by_index(self) -> typing.Sequence[typing.Optional[PublicKey]]:
|
|
|
|
return [slot.oracle if slot is not None else None for slot in self.slots_by_index]
|
|
|
|
|
|
|
|
@property
|
2021-11-08 03:54:50 -08:00
|
|
|
def spot_markets(self) -> typing.Sequence[GroupSlotSpotMarket]:
|
2021-11-10 09:46:54 -08:00
|
|
|
return [slot.spot_market for slot in self.slots if slot.spot_market is not None]
|
2021-11-08 03:39:09 -08:00
|
|
|
|
|
|
|
@property
|
2021-11-08 03:54:50 -08:00
|
|
|
def spot_markets_by_index(self) -> typing.Sequence[typing.Optional[GroupSlotSpotMarket]]:
|
2021-11-10 09:46:54 -08:00
|
|
|
return [slot.spot_market if slot is not None else None for slot in self.slots_by_index]
|
2021-11-08 03:39:09 -08:00
|
|
|
|
|
|
|
@property
|
2021-11-08 03:54:50 -08:00
|
|
|
def perp_markets(self) -> typing.Sequence[GroupSlotPerpMarket]:
|
2021-11-10 09:46:54 -08:00
|
|
|
return [slot.perp_market for slot in self.slots if slot.perp_market is not None]
|
2021-11-08 03:39:09 -08:00
|
|
|
|
|
|
|
@property
|
2021-11-08 03:54:50 -08:00
|
|
|
def perp_markets_by_index(self) -> typing.Sequence[typing.Optional[GroupSlotPerpMarket]]:
|
2021-11-10 09:46:54 -08:00
|
|
|
return [slot.perp_market if slot is not None else None for slot in self.slots_by_index]
|
2021-08-30 16:30:22 -07:00
|
|
|
|
2021-06-07 07:10:18 -07:00
|
|
|
@staticmethod
|
2021-11-11 03:56:29 -08:00
|
|
|
def from_layout(layout: typing.Any, name: str, account_info: AccountInfo, version: Version, instrument_lookup: InstrumentLookup, market_lookup: MarketLookup) -> "Group":
|
2021-07-01 13:24:08 -07:00
|
|
|
meta_data: Metadata = Metadata.from_layout(layout.meta_data)
|
2021-11-13 11:23:11 -08:00
|
|
|
tokens: typing.List[typing.Optional[TokenBank]] = [
|
|
|
|
TokenBank.from_layout_or_none(t, instrument_lookup) for t in layout.tokens]
|
2021-07-01 13:24:08 -07:00
|
|
|
|
2021-11-08 03:39:09 -08:00
|
|
|
# By convention, the shared quote token is always at the end.
|
2021-11-13 11:23:11 -08:00
|
|
|
quote_token_bank: typing.Optional[TokenBank] = tokens[-1]
|
|
|
|
if quote_token_bank is None:
|
2021-08-03 10:32:12 -07:00
|
|
|
raise Exception("Could not find quote token info at end of group tokens.")
|
2021-11-08 03:39:09 -08:00
|
|
|
slots: typing.List[GroupSlot] = []
|
|
|
|
in_slots: typing.List[bool] = []
|
2021-11-01 11:41:31 -07:00
|
|
|
for index in range(len(tokens) - 1):
|
2021-11-08 03:54:50 -08:00
|
|
|
spot_market_info: typing.Optional[GroupSlotSpotMarket] = GroupSlotSpotMarket.from_layout_or_none(
|
2021-11-01 11:41:31 -07:00
|
|
|
layout.spot_markets[index])
|
2021-11-08 03:54:50 -08:00
|
|
|
perp_market_info: typing.Optional[GroupSlotPerpMarket] = GroupSlotPerpMarket.from_layout_or_none(
|
2021-11-01 11:41:31 -07:00
|
|
|
layout.perp_markets[index])
|
|
|
|
if (spot_market_info is None) and (perp_market_info is None):
|
2021-11-08 03:39:09 -08:00
|
|
|
in_slots += [False]
|
2021-11-01 11:41:31 -07:00
|
|
|
else:
|
2021-10-02 04:28:16 -07:00
|
|
|
perp_lot_size_converter: LotSizeConverter = RaisingLotSizeConverter()
|
2021-11-13 11:23:11 -08:00
|
|
|
base_token_bank: typing.Optional[TokenBank] = tokens[index]
|
2021-11-08 03:39:09 -08:00
|
|
|
base_instrument: Instrument
|
2021-11-13 11:23:11 -08:00
|
|
|
if base_token_bank is not None:
|
|
|
|
base_instrument = base_token_bank.token
|
2021-11-08 03:39:09 -08:00
|
|
|
else:
|
2021-11-01 11:41:31 -07:00
|
|
|
# It's possible there's no underlying SPL token and we have a pure PERP market.
|
|
|
|
if perp_market_info is None:
|
|
|
|
raise Exception(f"Cannot find base token or perp market info for index {index}")
|
|
|
|
perp_market = market_lookup.find_by_address(perp_market_info.address)
|
|
|
|
if perp_market is None:
|
2022-01-06 08:34:18 -08:00
|
|
|
in_slots += [False]
|
|
|
|
logging.warning(
|
|
|
|
f"Group cannot find base token or perp market for index {index} - {perp_market_info}")
|
|
|
|
continue
|
|
|
|
|
|
|
|
base_instrument = perp_market.base
|
|
|
|
|
2021-10-02 04:28:16 -07:00
|
|
|
if perp_market_info is not None:
|
|
|
|
perp_lot_size_converter = LotSizeConverter(
|
2021-11-13 11:23:11 -08:00
|
|
|
base_instrument, perp_market_info.base_lot_size, quote_token_bank.token, perp_market_info.quote_lot_size)
|
2021-10-02 04:28:16 -07:00
|
|
|
|
2021-08-03 10:32:12 -07:00
|
|
|
oracle: PublicKey = layout.oracles[index]
|
2021-11-13 11:23:11 -08:00
|
|
|
slot: GroupSlot = GroupSlot(index, base_instrument, base_token_bank, quote_token_bank,
|
2021-11-09 13:08:06 -08:00
|
|
|
spot_market_info, perp_market_info, perp_lot_size_converter, oracle)
|
2021-11-08 03:39:09 -08:00
|
|
|
slots += [slot]
|
|
|
|
in_slots += [True]
|
2021-08-03 10:32:12 -07:00
|
|
|
|
2021-07-01 13:24:08 -07:00
|
|
|
signer_nonce: Decimal = layout.signer_nonce
|
|
|
|
signer_key: PublicKey = layout.signer_key
|
|
|
|
admin: PublicKey = layout.admin
|
2021-08-26 02:31:02 -07:00
|
|
|
serum_program_address: PublicKey = layout.serum_program_address
|
2021-11-11 03:56:29 -08:00
|
|
|
cache_address: PublicKey = layout.cache
|
2021-07-01 13:24:08 -07:00
|
|
|
valid_interval: Decimal = layout.valid_interval
|
2021-10-04 10:27:07 -07:00
|
|
|
insurance_vault: PublicKey = layout.insurance_vault
|
2021-07-13 04:43:50 -07:00
|
|
|
srm_vault: PublicKey = layout.srm_vault
|
|
|
|
msrm_vault: PublicKey = layout.msrm_vault
|
2021-10-04 10:27:07 -07:00
|
|
|
fees_vault: PublicKey = layout.fees_vault
|
2022-01-18 04:09:15 -08:00
|
|
|
max_mango_accounts: Decimal = layout.max_mango_accounts
|
|
|
|
num_mango_accounts: Decimal = layout.num_mango_accounts
|
2021-07-01 13:24:08 -07:00
|
|
|
|
2022-02-09 10:31:29 -08:00
|
|
|
referral_surcharge_centibps: Decimal = layout.referral_surcharge_centibps
|
|
|
|
referral_share_centibps: Decimal = layout.referral_share_centibps
|
|
|
|
referral_mngo_required: Decimal = layout.referral_mngo_required
|
|
|
|
|
|
|
|
return Group(account_info, version, name, meta_data, quote_token_bank, in_slots, slots, signer_nonce, signer_key, admin, serum_program_address, cache_address, valid_interval, insurance_vault, srm_vault, msrm_vault, fees_vault, max_mango_accounts, num_mango_accounts, referral_surcharge_centibps, referral_share_centibps, referral_mngo_required)
|
2021-06-07 07:10:18 -07:00
|
|
|
|
|
|
|
@staticmethod
|
2021-11-11 03:56:29 -08:00
|
|
|
def parse(account_info: AccountInfo, name: str, instrument_lookup: InstrumentLookup, market_lookup: MarketLookup) -> "Group":
|
2021-06-07 07:10:18 -07:00
|
|
|
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-10-04 10:27:07 -07:00
|
|
|
layout = layouts.GROUP.parse(data)
|
2021-11-11 03:56:29 -08:00
|
|
|
return Group.from_layout(layout, name, account_info, Version.V3, instrument_lookup, market_lookup)
|
2021-10-04 10:27:07 -07:00
|
|
|
|
|
|
|
@staticmethod
|
2021-11-11 03:56:29 -08:00
|
|
|
def parse_with_context(context: Context, account_info: AccountInfo) -> "Group":
|
|
|
|
name = context.lookup_group_name(account_info.address)
|
|
|
|
return Group.parse(account_info, name, context.instrument_lookup, context.market_lookup)
|
2021-08-03 10:32:12 -07:00
|
|
|
|
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":
|
2021-08-26 02:31:02 -07:00
|
|
|
group_address: PublicKey = address or context.group_address
|
2021-06-25 07:50:37 -07:00
|
|
|
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-11-11 03:56:29 -08:00
|
|
|
|
|
|
|
name = context.lookup_group_name(account_info.address)
|
|
|
|
return Group.parse(account_info, name, context.instrument_lookup, context.market_lookup)
|
2021-06-07 07:10:18 -07:00
|
|
|
|
2021-11-09 13:08:06 -08:00
|
|
|
def slot_by_spot_market_address(self, spot_market_address: PublicKey) -> GroupSlot:
|
|
|
|
for slot in self.slots:
|
2021-11-10 09:46:54 -08:00
|
|
|
if slot.spot_market is not None and slot.spot_market.address == spot_market_address:
|
2021-11-09 13:08:06 -08:00
|
|
|
return slot
|
2021-07-12 10:26:35 -07:00
|
|
|
|
|
|
|
raise Exception(f"Could not find spot market {spot_market_address} in group {self.address}")
|
|
|
|
|
2021-11-09 13:08:06 -08:00
|
|
|
def slot_by_perp_market_address(self, perp_market_address: PublicKey) -> GroupSlot:
|
|
|
|
for slot in self.slots:
|
2021-11-10 09:46:54 -08:00
|
|
|
if slot.perp_market is not None and slot.perp_market.address == perp_market_address:
|
2021-11-09 13:08:06 -08:00
|
|
|
return slot
|
2021-07-12 10:26:35 -07:00
|
|
|
|
|
|
|
raise Exception(f"Could not find perp market {perp_market_address} in group {self.address}")
|
|
|
|
|
2021-11-09 13:08:06 -08:00
|
|
|
def slot_by_instrument_or_none(self, instrument: Instrument) -> typing.Optional[GroupSlot]:
|
|
|
|
for slot in self.slots:
|
|
|
|
if slot.base_instrument == instrument:
|
|
|
|
return slot
|
2021-10-04 10:27:07 -07:00
|
|
|
|
|
|
|
return None
|
|
|
|
|
2021-11-09 13:08:06 -08:00
|
|
|
def slot_by_instrument(self, instrument: Instrument) -> GroupSlot:
|
|
|
|
slot: typing.Optional[GroupSlot] = self.slot_by_instrument_or_none(instrument)
|
|
|
|
if slot is not None:
|
|
|
|
return slot
|
2021-10-04 10:27:07 -07:00
|
|
|
|
2021-11-12 11:31:24 -08:00
|
|
|
raise Exception(f"Could not find slot for {instrument} in group {self.address}")
|
|
|
|
|
2021-11-13 11:23:11 -08:00
|
|
|
def token_bank_by_instrument(self, instrument: Instrument) -> TokenBank:
|
|
|
|
for token_bank in self.tokens:
|
|
|
|
if token_bank.token == instrument:
|
|
|
|
return token_bank
|
2021-11-12 11:31:24 -08:00
|
|
|
|
2021-11-09 12:15:43 -08:00
|
|
|
raise Exception(f"Could not find token {instrument} in group {self.address}")
|
2021-10-04 10:27:07 -07:00
|
|
|
|
2021-11-08 03:39:09 -08:00
|
|
|
def token_price_from_cache(self, cache: Cache, token: Instrument) -> InstrumentValue:
|
2021-12-15 03:32:22 -08:00
|
|
|
if token == self.shared_quote_token:
|
|
|
|
# 1 USDC is always worth 1 USDC
|
|
|
|
return InstrumentValue(self.shared_quote_token, Decimal(1))
|
|
|
|
else:
|
|
|
|
market_cache: MarketCache = self.market_cache_from_cache(cache, token)
|
|
|
|
return market_cache.adjusted_price(token, self.shared_quote_token)
|
2021-10-04 10:27:07 -07:00
|
|
|
|
2021-11-08 03:39:09 -08:00
|
|
|
def perp_market_cache_from_cache(self, cache: Cache, token: Instrument) -> typing.Optional[PerpMarketCache]:
|
2021-11-04 08:36:14 -07:00
|
|
|
market_cache: MarketCache = self.market_cache_from_cache(cache, token)
|
|
|
|
return market_cache.perp_market
|
|
|
|
|
2022-01-06 08:34:18 -08:00
|
|
|
def market_cache_from_cache_or_none(self, cache: Cache, instrument: Instrument) -> typing.Optional[MarketCache]:
|
|
|
|
slot: typing.Optional[GroupSlot] = self.slot_by_instrument_or_none(instrument)
|
|
|
|
if slot is None:
|
|
|
|
return None
|
2021-11-09 13:08:06 -08:00
|
|
|
instrument_index: int = slot.index
|
2021-11-09 12:15:43 -08:00
|
|
|
return cache.market_cache_for_index(instrument_index)
|
2021-10-04 10:27:07 -07:00
|
|
|
|
2022-01-06 08:34:18 -08:00
|
|
|
def market_cache_from_cache(self, cache: Cache, instrument: Instrument) -> MarketCache:
|
|
|
|
market_cache: typing.Optional[MarketCache] = self.market_cache_from_cache_or_none(cache, instrument)
|
|
|
|
if market_cache is not None:
|
|
|
|
return market_cache
|
|
|
|
raise Exception(f"Could not find market cache for instrument {instrument.symbol}")
|
|
|
|
|
2021-11-11 03:56:29 -08:00
|
|
|
def fetch_cache(self, context: Context) -> Cache:
|
|
|
|
return Cache.load(context, self.cache)
|
|
|
|
|
2022-02-09 10:31:29 -08:00
|
|
|
def derive_referrer_record_address(self, context: Context, id: str) -> PublicKey:
|
|
|
|
if not isinstance(id, str):
|
|
|
|
raise Exception(f"Referrer ID '{id}' is not a string")
|
|
|
|
|
|
|
|
id_bytes = id.encode('utf-8')
|
|
|
|
if len(id_bytes) > 32:
|
|
|
|
raise Exception(f"Referrer ID '{id}' is too long - maximum is 32 bytes")
|
|
|
|
|
|
|
|
id_bytes_padded = id_bytes.ljust(32, b"\0")
|
|
|
|
|
|
|
|
referrer_record_address_and_nonce: typing.Tuple[PublicKey, int] = PublicKey.find_program_address(
|
|
|
|
[
|
|
|
|
bytes(self.address),
|
|
|
|
b"ReferrerIdRecord",
|
|
|
|
id_bytes_padded
|
|
|
|
],
|
|
|
|
context.mango_program_address
|
|
|
|
)
|
|
|
|
|
|
|
|
return referrer_record_address_and_nonce[0]
|
|
|
|
|
2021-08-01 10:03:46 -07:00
|
|
|
def __str__(self) -> str:
|
2021-11-08 03:39:09 -08:00
|
|
|
slot_count = len(self.slots)
|
|
|
|
slots = "\n ".join([f"{item}".replace("\n", "\n ") for item in self.slots])
|
2021-12-13 04:06:42 -08:00
|
|
|
return f"""« Group {self.version} [{self.address}]
|
2021-06-25 07:50:37 -07:00
|
|
|
{self.meta_data}
|
|
|
|
Name: {self.name}
|
|
|
|
Signer [Nonce: {self.signer_nonce}]: {self.signer_key}
|
|
|
|
Admin: {self.admin}
|
2021-08-26 02:31:02 -07:00
|
|
|
DEX Program ID: {self.serum_program_address}
|
2021-07-01 13:24:08 -07:00
|
|
|
Cache: {self.cache}
|
2021-10-04 10:27:07 -07:00
|
|
|
Insurance Vault: {self.insurance_vault}
|
2021-07-13 04:43:50 -07:00
|
|
|
SRM Vault: {self.srm_vault}
|
|
|
|
MSRM Vault: {self.msrm_vault}
|
2021-10-04 10:27:07 -07:00
|
|
|
Fees Vault: {self.fees_vault}
|
2022-01-18 04:09:15 -08:00
|
|
|
Num Accounts: {self.num_mango_accounts:,} out of {self.max_mango_accounts:,}
|
2021-06-25 07:50:37 -07:00
|
|
|
Valid Interval: {self.valid_interval}
|
2022-02-09 10:31:29 -08:00
|
|
|
Referral:
|
|
|
|
Surcharge: {self.referral_surcharge_centibps}
|
|
|
|
Share: {self.referral_share_centibps}
|
|
|
|
MNGO Required: {self.referral_mngo_required}
|
2021-11-08 03:39:09 -08:00
|
|
|
Basket [{slot_count} markets]:
|
|
|
|
{slots}
|
2021-06-25 07:50:37 -07:00
|
|
|
»"""
|